Compare generic Collection? - c#

I have a
ObservableCollection<BasicClass> allCollection;
ObservableCollection<BasicClass> selectedCollection;
where
BasicClass
{
public Name {get;set;}
public Age {get;set;}
}
Now I added many BasicClass items to allCollection and only selected BasicClass to selectedCollection
SomeWhere I want to add items in selectedCollection which are not there in allCollection.
I tried this
foreach(var a in allCollection)
{
foreach(var s in selectedCollection)
if(a.Name!=s.Name)
//selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
But the problem is that this code is adding new BasicClass for each and every unmatched name,
but my actuall requirement is, for each Name of allCollection compare all selectedCollection items. If it is not there then add else move for next Item.
LINQ solution could help this? Actually I achieved this by more if and flags but That looks ver hectic.
My traditional solution
foreach(var a in allCollection)
{
bool same = false;
foreach(var s in selectedCollection)
if(a.Name==s.Name)
same=true;
}
if(same==false)
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
And I hate this..
EDIT:
I don't want compare collection to collection.
I want to compare collection1 value to collection2 all values, and if it not there then I want to add

Are you sure you don't just need this?
foreach(var a in allCollection)
{
if (!selectedCollection.Contains(a))
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
EDIT
I've just seen your comment below about matching on name only, so the above is not really what you want:). Try this approach instead:
foreach(var a in allCollection)
{
if (!selectedCollection.Any(s => a.Name == s.Name))
{
selectedCollection.Add(new BasicClass {Name =a.Name, Age=a.Age});
}
}
EDIT
As Chris suggested you could also use "Except" to create a collection. I'm not sure this gains much, it may be quicker but it involves writing the comparer code and creates a new temporary collection. However, it is pretty succinct E.g. Once you had the comparaer written you would just need this to add your missing items to the collection:
selectedCollection.Concat(allCollection.Except(selectedCollection));

So basically you need a 'where-not-in'? Linq->Except is the way to go, to filter on BasicClass.name only implement the IEqualityComparer for Except.

I'm not sure I understood your requirements correctly, so i may be missing the point...
Your BasicClass should implement the IEquatable<BasicClass> interface, so that two instances of BasicClass can be compared for equality:
class BasicClass : IEquatable<BasicClass>
{
public Name {get;set;}
public Age {get;set;}
public bool Equals(BasicClass other)
{
if (other == null)
return false;
return string.Equals(this.Name, other.Name);
}
public override int GetHashCode()
{
return Name == null ? 0 : Name.GetHashCode();
}
}
Now you can use the Except method to find items that are in allCollection but not in selectedCollection:
BasicClass[] notSelected = allCollection.Except(selectedCollection).ToArray();
foreach(BasicClass item in notSelected)
{
selectedCollection.Add(item);
}
Alternatively, you can implement a IEqualityComparer<BasicClass> and pass it to Except (instead of implementing IEquatable<BasicClass> in BasicClass)

You're right, this is more easily accomplished with Linq:
var itemsToAdd = allCollection.Except(selectedCollection);
foreach (var item in itemsToAdd)
selectedCollection.Add(item);
On the other hand, this is just going to make both lists contain the exact same items. Sure this is what you want?
If BasicItem overrides 'Equals' and 'GetHashCode' based off of Name, then this is all you need. If it doesn't, then you will also need to implement an IEqualityComparer:
//Allows us to compare BasicItems as if Name is the key
class NameComparer: IEqualityComparer<BasicItem>
{
public bool Equals(BasicItem first, BasicItem second)
{
return first.Name == second.Name;
}
public int GetHashCode(BasicItem value)
{
return value.Name.GetHashCode;
}
}
You now pass an instance of this class to Except:
var itemsToAdd = allCollections.Except(selectedCollection, new NameComparer());

Related

Checking whether a property value equals to a predefined one in a list of objects

I have two classes like this
public class Stock
{
public StockItem Item;
public string Location;
public int Quantity;
public string Price;
}
public class StockItem
{
public string Code;
public string Name;
public string Unit;
}
And I have a list that contains multiple instances of the Stock class
var stockList = new List<Stock>();
I am trying to determine whether the Name property of each instance inside the list is equal to a predefined string. Currently, I am looping through the list like this
foreach (var stock in stockList)
{
if (stock.Item.Name.ToLower() == "test")
{
Console.WriteLine("Found the class")
break;
}
}
However, I was wondering if there was a more efficient way to do this using Linq or something similar to the .Contains method. Is there a simpler or more efficient way to accomplish this?
whether the Name property of each instance inside the list is equal
to a predefined string
Not much more efficient but simpler:
bool allAreEqual = stockList.All(x => x.Item.Name.Equals("test", StringComparison.OrdinalIgnoreCase))
If you instead want to find the first which matches the condition(what your loop really does):
Stock si = stockList.FirstOrDefault(x => x.Item.Name.Equals("test", StringComparison.OrdinalIgnoreCase));
Now you know if such a Stock exists(si != null) at all and you got it.
All in linq will return True or false
stockList.All(p => p.Item.Name.ToLower() == "test");
You can use the Linq Any() method:
bool containsItemName = stockList.Any(x => x.Item.Name.Equals("MyName", StringComparison.InvariantCultureIgnoreCase));
Are you really looking at all instances? From your question, it seems as if Anymight be the way to go, see here.
stockList.Any(p => p.Item.Name.ToLower() == "test");
You can get a result what you wanted by calling Any
bool result = stockList.Any(
stock => stock.Item.Name.Equals("text", StringComparison.InvariantCultureIgnoreCase)
);
In this code, the parameter name stock can be changed whatever you want.

Can't remove item from Iesi.Collections.Generic ISet<T>

I have a collection of type:
Iesi.Collections.Generic
public ISet<ItemBinding> ItemBindings { get; set; }
where ItemBinding is Domain.Model
I initialize the collection in this way:
ItemBindings = new HashedSet<ItemBinding>();
and I fill the collection with members.
When i want to remove an item from this collection i can't remove it.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
even the itemToRemove has the same hashCode as the item from the collection.
Also I tried in to find the item in collection, keep it in a variable, and remove it:
private void OnRemove(ItemBinding itemToRemove) {
var foundItem = ItemBindings.Single( x => x.Id == itemToRemove.Id); // always if found
ItemBindings.Remove(foundItem);
}
but this doesn't work.
An workaround which works ok is this:
private void OnRemove(ItemBinding itemToRemove) {
var backUpItems = new List<ItemBinding>(ItemBindings);
backUpItems.Remove(itemToRemove);
ItemBindings.Clear();
ItemBindings.AddAll(backUpItems);
}
but this is an dirty workaround. I'm trying to do this simple Remove in an elegant manner :).
CHANGE the TYPE
If I change the type from ISet in IList it works ok.
public IList<ItemBinding> ItemBindings { get; set; }
ItemBindings = new List<ItemBinding>();
When i want to remove an item from this collection IT IS REMOVED.
private void OnRemove(ItemBinding itemToRemove) {
ItemBindings.Remove(itemToRemove);
}
What i'm missing in the way that i can't remove items from ISet ... ?
Thank you for suggestions, solutions.
This is very simple problem. Just download dotPeek 1.2 and fire up symbol server & you can then check into the ACTUAL implementation of ISet.Remove() and see why it's being picky. As #HansPassant said, it's probably the case of GetHashCode(), or the actual implementation of HashedSet
As for my speculations; take a look at DictionarySet(base class of HashedSet):
https://www.symbolsource.org/Public/Metadata/Default/Project/NHibernate/3.0.0.Alpha2/Release/All/Iesi.Collections/Iesi.Collections/Generic/DictionarySet.cs
As you can see, Remove() uses Contains() to actually test if it should remove element or not. What does Contains() do? Basically it's wrapper around Dictionary.Contains()
http://msdn.microsoft.com/en-us/library/ms182358(v=vs.80).aspx
GetHashCode returns a value based on the current instance that is
suited for hashing algorithms and data structures such as a hash
table. Two objects that are the same type and are equal must return
the same hash code to ensure that instances of
System.Collections.HashTable and System.Collections.Generic.Dictionary
work correctly.
Notice it's important that:
1) Your GetHashCode() can't change. This means that all the fields which are used by GetHashCode() can't change. As soon as you insert element into Dictionary, its GetHashCode() will be called and it's being put to specific bucket. You can't recover it when you have different GetHashCode() later. After the GetHashCode() has passed, your Equals method will be called. Make sure your fields are immutable.
Relevant source from Dictionary:
private int FindEntry(TKey key)
{
if ((object) key == null)
ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key);
if (this.buckets != null)
{
int num = this.comparer.GetHashCode(key) & int.MaxValue;
for (int index = this.buckets[num % this.buckets.Length]; index >= 0; index = this.entries[index].next)
{
if (this.entries[index].hashCode == num && this.comparer.Equals(this.entries[index].key, key))
return index;
}
}
return -1;
}
See this thread how to override Equals() AND GetHashCode():
Why is it important to override GetHashCode when Equals method is overridden?
Notice the answer by #Albic, in that thread.
I cannot reproduce this behaviour.
private class ItemBinding
{
public string ID { get; set; }
}
[TestMethod]
public void TestMethod1()
{
System.Collections.Generic.HashSet<ItemBinding> set = new System.Collections.Generic.HashSet<ItemBinding>();
ItemBinding item1 = new ItemBinding() { ID = "Jaffa" };
set.Add(item1);
Assert.IsTrue(set.Count == 1);
set.Remove(item1);
Assert.IsTrue(set.Count == 0);
ItemBinding item2 = new ItemBinding() { ID = "Moon" };
set.Add(item2);
ItemBinding item3 = new ItemBinding() { ID = "Moon" };
Assert.IsTrue(item2.GetHashCode() != item3.GetHashCode());
Assert.IsTrue(set.Remove(item3) == false);
Assert.IsTrue(set.Count == 1);
}
The above test shows Hashset working as expected. Is is possible you are falling into the trap shown in the second test of comparing two instances of a class that have the same values, but are in fact different class instances (therefore fail the GetHashCode equality test ?).
If you can alter the posted code here to more accurately represent your particular problem, that would be helpful.
You can try using...
private void OnRemove(ItemBinding itemToRemove)
{
ItemBindings.RemoveWhere(x => x.Id == itemToRemove.Id);
}

Use inherit class for instance List in C#

Hello stackoverflow community!
Let's start!
I have one small class Person:
class Person
{
public string name { get; set; }
}
Аnd it has a descendant Employee:
class Employee : Person
{
public int salary { get; set; }
}
And second descendant is Guest:
class Guest: Person
{
public int id { get; set; }
}
Ok! looks good :)
Now I want to display a list of all employees OR guests in a single control ListView
I made a class (it really necessary) for list management PeopleList:
class PeopleList
{
public List<Person> People { get; set; }
...
public void LoadListFromFile()
{
// Load logic
this.People = new List<?>(source);
}
}
Do you see this question mark? No? Look at the code again!
How to create an instance of List that I can use my class something like this:
// This instance with the list of Person objects
PeopleList list = new PeopleList();
foreach (Employee e in list.People)
{
Debug.WriteLine(e.salary.toString());
}
// This instance with the list of Guest objects
PeopleList list = new PeopleList();
foreach (Guest g in list.People)
{
Debug.WriteLine(g.id.toString());
}
P.S. I'm new in c# and I think that I have a problem in architecture. And maybe you point me to the pattern solves my problem. I really need your help! Thank you!
I think you're after OfType, in the System.Linq library:
foreach (Employee e in personList.OfType<Employee>())
{
Debug.WriteLine(e.salary.toString());
}
foreach (Guest g in personList.OfType<Guest>())
{
Debug.WriteLine(g.id.toString());
}
The only field in Person that is shared with all of the decedents is the field 'name', therefore, if you are casting each item in your list to Person you will only have access to 'name'.
You have a few options to solve this issue. 1) you could move all of the common fields into the base class, but this is probably not want since it defeats the point of an object hierarchy, or 2) you can type check, using the "Is" keyword, each person in the list so see what type of person it is and then cast that Person to the appropriate decedent class before you operate on it.
For example:
foreach (Person p in list.People)
{
if(p is Employee)
{
Debug.WriteLine(((Employee)p).salary.toString());
}
if(p is Guest)
{
Debug.WriteLine(((Guest)p).Id.toString());
}
}
Here is an alternate more clear way of casting
foreach (Person p in list.People)
{
if(p is Employee)
{
Employee employee = p as Employee;
Debug.WriteLine(employee.salary.toString());
}
if(p is Guest)
{
Guest guest = p as Guest;
Debug.WriteLine(guest.Id.toString());
}
}
Additionally you can learn more about type checking using the "is" keyword here.
Also just for clarity, since it might not be obvious, some others have suggested that you use OfType in linq but this more of a way to filter like object from a list of mixed objects as opposed to actually type checking each one.
Enjoy!
As the comments are saying, you can do the following;
public void LoadListFromFile()
{
// Load logic
this.People = new List<Person>(source);
}
And then, cast where appropriate;
foreach (Person e in list.People.Where(p => p.GetType() == typeof(Employee)))
{
Debug.WriteLine(((Employee)e).salary.toString());
}
I think in your particular example, the inheritance structure is not adding much benefit, but I digress.
In your example, the easiest solution would be to override the ToString method in each one of your classes.
Having to know the exact type of the object before hand before you run some sort of calculation or display puts the onus of that calculation onto the calling code, if you make a 3rd type, you would have to update all possible references, giving you a maintenance nightmare.
The feel the logic of how to represent itself should be the responsibility (in your example) of the object itself.
This is what overriding ToString gives you, the responsibility of representing itself is then given to the thing that knows best (the object you're asking).

How to merge 2 or more Lists implementing same interface

Let say I have 2 lists
public List<TypeA> TypeARecords {get; set;}
public List<TypeB> TypeBRecords {get; set;}
Both TypeA and TypeB implements same Interface (let say IBaseRecord)
Now I have a read only property that returns list of all records
public List<IBaseRecord> AllRecords
{
get
{
var allRecs = new List<IBaseRecord>();
foreach ( var rec in TypeARecords)
allRecs.Add(rec);
foreach ( var rec in TypeBRecords)
allRecs.Add(rec);
return allRecs;
}
}
This works but I am sure there is more effective or just smarter way to do same thing
Any ideas?
You can make an iterator that returns the items in the list:
public IEnumerable<IBaseRecord> GetAllRecords {
foreach (var rec in TypeARecords) {
yield return rec;
}
foreach (var rec in TypeBRecords) {
yield return rec;
}
}
This way you don't have to create a new list with all the items, it will just read from the existing lists.
Edit:
As Stan R. suggested, you can use the ToList method to create a copy of the list:
List<IBaseRecord> work = obj.GetAllRecords().ToList();
This is a bit better than having a property that returns a new list, as the ownership of the list gets clearer. Also, a property should not do such heavy lifting as creating lists, at least not every time the property is read.
Your way.
public List<IBaseRecord> AllRecords
{
get
{
return new List<IBaseRecord>().
Concat(TypeARecords.OfType<IBaseRecord>()).
Concat(TypeBRecords.OfType<IBaseRecord>()).
ToList();
}
}
Better way.
public IEnumerable<IBaseRecord> AllRecords
{
get
{
foreach (var i in TypeARecords) yield return i;
foreach (var i in TypeBRecords) yield return i;
}
}
Best way IMHO.
public IEnumerable<IBaseRecord> AllRecords
{
get
{
return TypeARecords.Concat<IBaseRecord>(TypeBRecords);
}
}
Unless you use the common interface to declare your 2 lists, you can't do this until C# 4 without something similar to what you mentioned (or the linq equivalent).
You can use List.AddRange(), but that's still an O(n) operation, implying that it iterates over all of the members being added, so it's just essentially syntatic sugar for what you're already doing.
Presumably you don't want to modify either ListA or ListB, so you will have to iterate in order to create the new pointers for your new list.
What Blindy means is unless you do
public List<IBaseRecord> TypeARecords {get; set;}
public List<IBaseRecord> TypeBRecords {get; set;}
Then you can do something like
public IEnumerable<IBaseRecord> AllRecords
{
get
{
return Enumerable.Concat(TypeARecords, TypeBRecords);
}
}
public List<IBaseRecord> AllRecords
{
get
{
return TypeARecords.Cast<IBaseRecord>()
.Concat(TypeBRecords.Cast<IBaseRecord>()).ToList();
}
}

C#: Class return value - or is there a better way?

I created a class that had one member of type List. I could then add to this using ClassNameInstance.Add().
We are now using some code from a third-party that will automatically use any class I create and its values. Except lists. It only returns the first element in the list.
What I need is a comma-separated version returned instead for this third-parties code to use. Now I could just append the Strings to a String member, but this doesn't look as nice as the .Add() method.
So I wanted to create my own Class that I could have an Add() method for but could access its single value like so:
MyClass1.Add("Test1");
MyClass1.Add("Test2");
Console.WriteLine(MyClass2);
The output I would like would be Test1, Test2. I hope this makes sense!
UPDATE1:
Seems the above may not have been clear.
public class MyClass1
{
????
}
public class MyClass2
{
MyClass1 mc1 { get; set; }
String name { get; set; }
}
The third party code will use MyClass2 and its assigned values. When I used List instead of MyClass1 I only get the first value in the list, but need a CSV list as a String returned.
MyClass2 mc2 = new MyClass2();
mc2.mc1.Add("Test1");
mc2.mc1.Add("Test2");
Console.WriteLine(mc2.mc1) should output -> Test1, Test2
Hope that clears things up some more!
Thanks everyone! :)
UPDATE2:
It seems everyone is suggesting the same thing - use ToString().
Unfortunately, the third-party code will look at my class and determines the members type and value automatically. This means that I am not able to pass the code the value that would be returned by calling ToString().
I kind of need the add/remove functionality of a List<> but when used its value returns as a single CSV string.
I'm not sure that you need an extra class here:
List<string> list = new List<string>();
list.Add("Test1");
list.Add("Test2");
Console.WriteLine(string.Join(", ", list.ToArray());
You could wrap this behaviour in a class just to get it automatically invoked on ToString:
public sealed class StringJoiningList
{
private readonly List<string> list = new List<string>();
public void Add(string item)
{
list.Add(item);
}
public override string ToString()
{
return string.Join(", ", list.ToArray());
}
}
Now that's assuming you just want a List<string> - it also doesn't let you access the items other than by the ToString() method. All of that is fixable of course, if you could give more information. You could even just derive from List<T>:
public class StringJoiningList<T> : List<T>
{
public override string ToString()
{
return string.Join(", ", this.Select(x => x.ToString()).ToArray());
}
}
I'm not entirely sure I like the idea, but it depends on what you need to do...
A couple of easy options would be to override the ToString() method of your class (so that when it is used in this context it returns a comma-delimited list of items) or you can provide a property that flattens the list.
You need to implement the ToString method.
Here's an extension I wrote to join the items in an IEnumerable<T>
public static string Join<T>(this IEnumerable<T> collection,
string separator, Func<T, object> selector)
{
if (null == separator)
throw new ArgumentException("separator");
if (null == selector)
throw new ArgumentException("selector");
Func func = item =>
{
var #object = selector(item);
return (null != #object) ? #object.ToString() : string.Empty;
};
return string.Join(separator, collection.Select(func).ToArray());
}
It can be used like this:
List<Person> list = new List<Person>()
{
new Person() { FirstName = "Magic", LastName = "Johnson" },
new Person() { FirstName = "Michael", LastName = "Jordon" },
new Person() { FirstName = "Larry", LastName = "Bird" }
};
var value = list.Join(", ", p => p.FirstName);
// value is "Magic, Michael, Larry"
Not really the answer I wanted, but it seems that I couldn't do what I wanted.
In the class where I wanted to have my second class as a member, I made that member a String so the third-party code would work as I wanted.
I created a second class that had a List<String> as a public member and then a Method called .ToCSV() to output the list as a csv string. I added a .Add() method to add the strings to the list to save having to go Class1.Class2.Add().
Then just before the third-party code would use my class, I do:
MyClass1.MyString = MyClass2.ToCSV();
Thus giving me basically what I wanted in a round about way!!
Thanks for everyone's input and help!
Neil
The simplest solution is probably to inherit from the StringCollection class and just override ToString. Otherwise this class already has everything you are asking about.
public class MyClass : System.Collections.Specialized.StringCollection
{
public override string ToString()
{
var c = new string[this.Count];
this.CopyTo(c,0);
return string.Join(",", c);
}
}
Then you can use it just like you were trying to before. No need to get fancy with generics for this simple usage.
Edit:
Classes themselves don't have a return value, so the 3rd party code must be reading properties. You should be able to override ToString() as described above then expose a property that returns the ToString(). That would probably make the string value visible to the 3rd party code
public class MyClass : System.Collections.Specialized.StringCollection
{
public string MyValue
{
get { return this.ToString(); }
}
public override string ToString()
{
var c = new string[this.Count];
this.CopyTo(c,0);
return string.Join(",", c);
}
}

Categories