How Distint Method is Used by the Framework

Problem Statement

Please go through my previous article first, as in:

http://www.c-sharpcorner.com/UploadFile/tirthacs/remove-an-item-form-observable-collection-using-remove/

Again, the problem is with the understanding of how the equals method is used by the framework. After speaking to my teammates and beginners, I understood that they learned all the extension methods using Strings and all worked fine. But when it comes to objects they didn't get the expected results. The solution is again to correctly implement the Equals method. I will extend this article to be more specific to business requirements.

Note: This example also works fine with the Contains() method. Both are similar.

Problem

We want that all redundant ID values are be removed from the collection as in the following:

  1. class Person  
  2. {  
  3. public string Name { getset; }  
  4. public int ID { getset; }  
  5.   
  6. public override string ToString()  
  7. {  
  8. return "Name:" + Name + " & ID: " + ID;  
  9. }  
  10. }  
Let's do the distinct operation:
  1. var collection = new ObservableCollection<Person>(){new Person{Name="Arunava",ID=1},new Person{Name="Arunava",ID=2},new Person{Name="Arun",ID=1}};  
  2.   
  3. var res = collection.Distinct();  
  4. res.ToList().ForEach(c =>Console.WriteLine(" " + c));  
  5. Console.ReadLine();  
The output is not what we desired:

redundant ID value removed from the collection

Solution

Let's include the Equals() and GetHashCode() in our model class as in the following:
  1. class Person  
  2. {  
  3. public string Name { getset; }  
  4. public int ID { getset; }  
  5.   
  6. public override string ToString()  
  7. {  
  8. return "Name:" + Name + " & ID: " + ID;  
  9. }  
  10.   
  11. public override bool Equals(object obj)  
  12. {  
  13. // Check for null  
  14. if (ReferenceEquals(obj, null))  
  15. return false;  
  16.   
  17. // Check for same reference  
  18. if (ReferenceEquals(this, obj))  
  19. return true;  
  20.   
  21. var person = (Person)obj;  
  22. return this.ID == person.ID;  
  23.   
  24. }  
  25.   
  26. public override int GetHashCode()  
  27. {  
  28. return ID ^ 7;  
  29. }  
  30. }  
Let's run the same program and see that the output is as we desired:

output of model class

Extension

What if the requirements shift to properties that are now the distinct row, in other words two rows that have the same Name? For this, the default Distinct will not work. Fortunately, Microsoft provide an overload of Distinct that takes an EqualityComparer. To use it, we need to define our own custom equality comparer. I am making it generic enough so that any property or property combination will work fine. Let's define the Comparer first:
  1. class PersonEqualityComparer : IEqualityComparer<Person>  
  2. {  
  3. public Func<Person, object> KeySelector { getset; }  
  4. public PersonEqualityComparer(Func<Person, object> selector)  
  5. {  
  6. KeySelector = selector;  
  7. }  
  8.   
  9. public bool Equals(Person x, Person y)  
  10. {  
  11. return KeySelector(x).Equals(KeySelector(y));  
  12. }  
  13.   
  14. public int GetHashCode(Person obj)  
  15. {  
  16. return KeySelector(obj).GetHashCode();  
  17. }  
  18. }  
I used the Function delegate for the comparison that abstracted the general Equal() method. Now let's modify the client to be as in the following:
  1. var collection = new ObservableCollection<Person>(){new Person{Name="Arunava",ID=1},new Person{Name="Arunava",ID=2},new Person{Name="Arun",ID=1}};  
  2.   
  3. var res=collection.Distinct(new PersonEqualityComparer(c=>c.Name));  
  4. res.ToList().ForEach(c =>Console.WriteLine(" " + c));  
  5. Console.ReadLine();  
Now the Distinct works on the Person Name and the output is as follows:

output after modify the client

We can also use the property combination as follows:
  1. var res = collection.Distinct(new PersonEqualityComparer(c => new { c.Name, c.ID }));  
I hope this helps.