C# ObservableCollection.IndexOf(...) returns -1 - c#

I wrote this code to check a Collection to find objects with the same value, but it returns the index -1 and causes an IndexOutOfRangeException. Can anyone help find my mistake?
List<MyFileInfo> selectedItemsList = dataInbox.SelectedItems.Cast<MyFileInfo>().ToList();
foreach (MyFileInfo file in selectedItemsList)
{
if (!file.AdditionalColumn.Equals(""))
{
inDB = new ZeichnungInDB(file.FileInfo.Name, file.AdditionalColumn, file.AdditionalColumn2, file.FileInfo.Extension,
txtAenderungExtern.Text, file.AdditionalColumn3,
int.Parse(txtProjectNumber.Text), txtTag.Text, bemerkung, anhangPfad, cmbDokumententyp.Text, false);
if (zeichnungCollection.Count > 0)
{
if (zeichnungCollection[zeichnungCollection.IndexOf(inDB)].Zeichnungsnummer != inDB.Zeichnungsnummer &&
zeichnungCollection[zeichnungCollection.IndexOf(inDB)].Extension != inDB.Extension)
{
zeichnungCollection.Add(inDB);
}
else
{
sameData = true;
}
}
else
{
zeichnungCollection.Add(inDB);
}
}
}

You are creating a new instance of an object, you are then attempting to find the Index of that object where your collection actually holds reference to a different instance.
You can use FindIndex with ToList to pass in a predicate and find the index of the object where a condition is true.
https://msdn.microsoft.com/en-us/library/x1xzf2ca(v=vs.110).aspx
Alternatively, you could use FirstOrDefault with some null checking if you'd prefer to keep it as an ObservableCollection
https://msdn.microsoft.com/en-us/library/bb340482(v=vs.110).aspx

Assume MyFileInfo looks like this:
public class MyFileInfo
{
public string Name { get; set; }
}
Now try to use it like this:
List<MyFileInfo> selectedItemsList = new List<MyFileInfo>
{
new MyFileInfo { Name = "One" },
new MyFileInfo { Name = "Two" },
};
MyFileInfo two = new MyFileInfo { Name = "Two" };
int index = selectedItemsList.IndexOf(two); // index == -1
IndexOf is looking for identical instance references, which it doesn't find, and so returns -1.
If you do this instead, though, the references are the same:
MyFileInfo two = new MyFileInfo { Name = "Two" };
List<MyFileInfo> selectedItemsList = new List<MyFileInfo>
{
new MyFileInfo { Name = "One" },
two,
};
int index = selectedItemsList.IndexOf(two); // index == 1
This is due to the default implementation of the Equals method, which just compares for reference equality. If you override Equals in MyFileInfo, you get to decide what Equals means. For example:
public class MyFileInfo
{
public string Name { get; set; }
public override bool Equals(object obj)
{
if (obj?.GetType() == typeof(MyFileInfo))
{
return ((MyFileInfo)obj).Name == Name;
}
return false;
}
}
This will find any object with the same Name.
Using methods with predicates is another option, which allows you to define what Equals means on the fly, e.g.:
List<MyFileInfo> selectedItemsList = new List<MyFileInfo>
{
new MyFileInfo { Name = "One" },
new MyFileInfo { Name = "Two" },
};
MyFileInfo two = new MyFileInfo { Name = "Two" };
int index = selectedItemsList.FindIndex(info => info.Name == two.Name);
Which also finds items with the same Name.
Note: If you override Equals in any class that might be used as a dictionary (hash table) key, you should also override GetHashCode. Here's a discussion. And there are considerations for implementing various other interfaces such as IEquatable<T>, especially for structs (value objects), which I guess is out of scope for this question.
Edit: Why it's important to override GetHashCode when overriding Equals

Related

C# How to find index of a object in listbox datasource (binded with List<T>) by Linq

I have a viewmodel class like this:
public class ViewItem
{
private No as int;
private Name as string;
...Getter-Setter go here...
}
I have a listbox name LbxItemBox
I binded it with datasource is:
List<ViewItem> DataList;
LbxItemBox.Datasource = DataList;
Data final are:
Item1: 1, "Frank"
Item2: 2, "Bob"
Item3: 3, "Johh"
Item4: 4, "Lucis"
How I can find index of a object model like this in LbxItemBox:
ViewItem ViewX = new ViewItem();
ViewX.No = 3;
ViewX.Name = "John";
I tried simple way, not Linq:
int IndexMatched = LbxItemBox.Items.IndexOf(ViewX);
but return -1;
How can I use Linq to find index of what I need?
Thanks for your helps
There are a couple ways you can achieve this. First, you can override Equals for your ViewItem class. This will cause the IndexOf call with a new instance of ViewItem to say that any object you already have in the list is equivalent to a newly created object with the same information. For your class you have listed here is how you could do that:
public class ViewItem
{
private int No { get; set; }
private string Name { get; set; }
public ViewItem(int no, string name)
{
this.No = no;
this.Name = name;
}
public override bool Equals(Object obj)
{
// Check for null values and compare run-time types.
if (obj == null || GetType() != obj.GetType())
return false;
ViewItem other = (ViewItem) obj;
return (No == other.No);
}
}
Your example above should return the right index, given that Equals has been overridden. For more information on correctly implementing Equals and GetHashCode see MSDN or http://www.loganfranken.com/blog/687/overriding-equals-in-c-part-1/.
An alternative way to handle this using linq if you are not looking for true equality would be to match on certain properties of your ViewItem class. Here is an example of doing so:
List<ViewItem> items = new List<ViewItem>() { new ViewItem(1, "John"), new ViewItem(2, "Jake"), new ViewItem(2, "Frank")};
var john = new ViewItem(1, "John");
var frankInd = items.FindIndex(i => i.Name == "Frank");
Console.WriteLine(items.IndexOf(john));
Console.WriteLine(frankInd);
Using LINQ (Enumerable.Cast<TResult> Method):
int IndexMatched = LbxItemBox.Items.Cast<ViewItem>().ToList().FindIndex(x => x.No == ViewX.No);

SequenceEqual different results

in the code below calling SequenceEqual on generic list return true (as expected) when List is defined with class generic type (EquatableClass.Equals<> is called).
If list is defined with IEquatable interface, Equals method is not called and result is false (object.Equals is called instead, not in code).
The question is, why the EquatableClass.Equals<> method is not called in the second case?
public class EquatableClass : IEquatable<EquatableClass>
{
public string Name { get; set; }
public bool Equals(EquatableClass other) => this.Name.Equals(other.Name);
}
static void Main(string[] args)
{
var A = new List<EquatableClass> { new EquatableClass { Name = "A" } };
var B = new List<EquatableClass> { new EquatableClass { Name = "A" } };
var result1 = A.SequenceEqual(B); // == true;
var AA = new List<IEquatable<EquatableClass>> { new EquatableClass { Name = "A" } };
var BB = new List<IEquatable<EquatableClass>> { new EquatableClass { Name = "A" } };
var result2 = AA.SequenceEqual(BB); // == false;
}
The SequenceEqual<T> method will try to see if it can convert T to IEquatable<T>, and if it can, it will use IEquatable<T>.Equals for equality.
When you have a List<EquatableClass> it will then try to convert EquatableClass to IEquatable<EquatableClass>, and that succeeds, so it uses the appropriate Equals method.
When you have a List<IEquatable<EquatableClass>> it will then try to convert IEquatable<EquatableClass> to IEquatable<IEquatable<EquatableClass>>, and that will fail, because the actual object doesn't implement IEquatable<IEquatable<EquatableClass>>, so it resorts to the default behavior of using object.Equals(object), which you don't override.

How to do not select case of two items if you have only one item in list

Maybe it is silly question, but I am quite new in C#. I have such a object:
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" }
}
};
And I have method where I am validating such structure:
var badRequests = new Dictionary<Func<bool>, string>
{
[() => myStructure.Name == null] = "Name parameter cannot be empty or null string",
[() => (myStructure.ItemsRequest[0].Type == ItemsType.Green &&
myStructure.ItemsRequest[0].Id == myStructure.ItemsRequest[0].Id && yStructure.ItemsRequest[1].Type == ItemsType.Red &&
myStructure.ItemsRequest[1].Id == myStructure.ItemsRequest[1].Id)] = "could not be created with one with these types",
};
This validation DO NOT pass, because there aren't 2 items in my object, but I am getting such a error:
An exception of type 'System.ArgumentOutOfRangeException' occurred in
mscorlib.dll but was not handled in user code
Additional information: Index was out of range. Must be non-negative
and less than the size of the collection.
How can I handle it when I have only one item in list and do not get error? As I understand it is because of indexes [] .
Edit: When I have such a structure with one item:
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" }
}
};
And in dictionary I validate only this function, it pass succesfully, because I select only one item with specific entries.
[() => myStructure.ItemsRequest.Select(x => x.Type == ItemsType.Green && x.Id == "0020012321").FirstOrDefault()] = "Type cannot be Green",
But is there are any possibilities to be independent of indexes and select as many as you like items from object?
The following code has been tested and works fine, if this doesn't work (as you previously mentioned on a comment) then there must be something else wrong with your code.
I have changed the Dictionary<Func<bool>, string> to Dictionary<Predicate<MyStructure>, string>, Predicate<T> will return true or false based on your condition - and it will take a parameter of T, which in this case will be a parameter of type MyStructure - this allows you to define your collection outside of the method.
class Program
{
// Create a Dictionary of Key type Predicate<MyStructure>, Predicate returns true or false - no need for a Func<bool>
private static Dictionary<Predicate<MyStructure>, string> badRequests = new Dictionary<Predicate<MyStructure>, string>
{
[p => string.IsNullOrWhiteSpace(p.Name)] = "Name parameter cannot be empty or null string",
[p => p.Items.Count == 2 /* <- This is important, as René pointed out in his answer */ && p.Items[0].Type == ItemsType.Green &&
p.Items[0].Id == "0020012321" && p.Items[1].Type == ItemsType.Red &&
p.Items[1].Id == "9023546547"
] = "could not be created with one with these types"
};
static void Main(string[] args)
{
// Initialize object
MyStructure data = new MyStructure()
{
Name = "Test",
DateTime = "2017-07-14T00:00:00.000Z",
Items = new List<ItemsRequest>
{
new ItemsRequest() { Type = ItemsType.Green, Id = "0020012321" },
new ItemsRequest() { Type = ItemsType.Red, Id = "9023546547" }
}
};
// Call badRequests Dictionary with data to fetch Value
string myString = badRequests.FirstOrDefault(p => p.Key.Invoke(data)).Value;
Console.ReadKey();
}
public class MyStructure
{
public string Name { get; set; }
public string DateTime { get; set; }
public List<ItemsRequest> Items { get; set; }
}
public class ItemsRequest
{
public string Id { get; set; }
public ItemsType Type { get; set; }
}
public enum ItemsType
{
Green, Red
}
}
Simply add test the Count of the list before accessing:
() => (myStructure.ItemsRequest.Count == 2 && // <-- add this line
myStructure.ItemsRequest[0].Type == ItemsType.Green &&
myStructure.ItemsRequest[0].Id == "0020012321" &&
myStructure.ItemsRequest[1].Type == ItemsType.Red &&
myStructure.ItemsRequest[1].Id == "9023546547")
The expression is evaluated "short-circuited", so if Count is not 2, the rest of the expression is not evaluated and so you don't try to access index 1 of the list if there is no second element.

C#: Compare two ArrayList of custom class and find duplicates

I have two arrays of ArrayList.
public class ProductDetails
{
public string id;
public string description;
public float rate;
}
ArrayList products1 = new ArrayList();
ArrayList products2 = new ArrayList();
ArrayList duplicateProducts = new ArrayList();
Now what I want is to get all the products (with all the fields of ProductDetails class) having duplicate description in both products1 and products2.
I can run two for/while loops as traditional way, but that would be very slow specially if I will be having over 10k elements in both arrays.
So probably something can be done with LINQ.
If you want to use linQ, you need write your own EqualityComparer where you override both methods Equals and GetHashCode()
public class ProductDetails
{
public string id {get; set;}
public string description {get; set;}
public float rate {get; set;}
}
public class ProductComparer : IEqualityComparer<ProductDetails>
{
public bool Equals(ProductDetails x, ProductDetails y)
{
//Check whether the objects are the same object.
if (Object.ReferenceEquals(x, y)) return true;
//Check whether the products' properties are equal.
return x != null && y != null && x.id.Equals(y.id) && x.description.Equals(y.description);
}
public int GetHashCode(ProductDetails obj)
{
//Get hash code for the description field if it is not null.
int hashProductDesc = obj.description == null ? 0 : obj.description.GetHashCode();
//Get hash code for the idfield.
int hashProductId = obj.id.GetHashCode();
//Calculate the hash code for the product.
return hashProductDesc ^ hashProductId ;
}
}
Now, supposing you have this objects:
ProductDetails [] items1= { new ProductDetails { description= "aa", id= 9, rating=2.0f },
new ProductDetails { description= "b", id= 4, rating=2.0f} };
ProductDetails [] items= { new ProductDetails { description= "aa", id= 9, rating=1.0f },
new ProductDetails { description= "c", id= 12, rating=2.0f } };
IEnumerable<ProductDetails> duplicates =
items1.Intersect(items2, new ProductComparer());
Consider overriding the System.Object.Equals method.
public class ProductDetails
{
public string id;
public string description;
public float rate;
public override bool Equals(object obj)
{
if(obj is ProductDetails == null)
return false;
if(ReferenceEquals(obj,this))
return true;
ProductDetails p = (ProductDetails)obj;
return description == p.description;
}
}
Filtering would then be as simple as:
var result = products1.Where(product=>products2.Contains(product));
EDIT:
Do consider that this implementation is not optimal..
Moreover- it has been proposed in the comments to your question that you use a data base.
This way performance will be optimized - as per the database implementation In any case- the overhead will not be yours.
However, you can optimize this code by using a Dictionary or a HashSet:
Overload the System.Object.GetHashCode method:
public override int GetHashCode()
{
return description.GetHashCode();
}
You can now do this:
var hashSet = new HashSet<ProductDetails>(products1);
var result = products2.Where(product=>hashSet.Contains(product));
Which will boost your performance to an extent since lookup will be less costly.
10k elements is nothing, however make sure you use proper collection types. ArrayList is long deprecated, use List<ProductDetails>.
Next step is implementing proper Equals and GetHashCode overrides for your class. The assumption here is that description is the key since that's what you care about from a duplication point of view:
public class ProductDetails
{
public string id;
public string description;
public float rate;
public override bool Equals(object obj)
{
var p = obj as ProductDetails;
return ReferenceEquals(p, null) ? false : description == obj.description;
}
public override int GetHashCode() => description.GetHashCode();
}
Now we have options. One easy and efficient way of doing this is using a hash set:
var set = new HashSet<ProductDetails>();
var products1 = new List<ProductDetails>(); // fill it
var products2 = new List<ProductDetails>(); // fill it
// shove everything in the first list in the set
foreach(var item in products1)
set.Add(item);
// and simply test the elements in the second set
foreach(var item in products2)
if(set.Contains(item))
{
// item.description was already used in products1, handle it here
}
This gives you linear (O(n)) time-complexity, best you can get.

Convert list to dictionary

I have a list of these objects that I would like to convert to a dictionary:
public class Thing
{
public Guid ParentKey { get; set; }
public int Position { get; set; }
public bool Unconfigured { get; set; }
public void Replace(Thing thing);
}
I would like the key and value to be Thing objects where the ParentKey and Position are the same. The key object must have Unconfigured set to true and the value would be a sibling object that has the same ParentKey and Position but a false Unconfigured state. I'm trying to create a pairs of objects that have the same ParentKey, Position but have different Unconfigured states.
Example:
var things = new List<Thing>();
things.Add(new Thing { ParentKey = new Guid("8a1211d2-f42b-4dd2-b6a3-7f4ab4a44a8d"), Position = 1, Unconfigured = true });
things.Add(new Thing { ParentKey = new Guid("8a1211d2-f42b-4dd2-b6a3-7f4ab4a44a8d"), Position = 1, Unconfigured = false });
things.Add(new Thing { ParentKey = new Guid("35f22dba-7789-49f4-8982-c9ea075175cc"), Position = 2, Unconfigured = false });
I expect a dictionary with one item. The key for that item is the first thing object added and the value is the second thing object added.
The intended use of this is to call the replace method:
foreach (var entry in things)
{
entry.Key.Replace(entry.Value);
}
A dictionary may not be an appropriate object for this, I'm open to other suggestions.
Try this solution:
var dic = things.ToDictionary(
t => new Thing
{
ParentKey = t.ParentKey,
Position = t.Position,
Unconfigured = true
},
t => new Thing
{
ParentKey = t.ParentKey,
Position = t.Position,
Unconfigured = false
}
);
I think you may need to resort to a foreach loop, you can try the code below:
List<Thing> things = new List<Thing>();
Dictionary<Thing, Thing> dictionary = new Dictionary<Thing,Thing>();
var keys = from thing in things
where thing.Unconfigured
select thing;
var values = from thing in things
where !thing.Unconfigured
select thing;
foreach (Thing key in keys)
{
var value = (from thing in values
where thing.ParentKey == key.ParentKey &&
thing.Position == key.Position
select thing).FirstOrDefault();
dictionary.Add(key, value);
}
Of course the code assumes that you only have pairs in the List if there may be more than a pair of objects related in the way you described this would need revision

Categories

Resources