2

So I am trying to find the difference between 2 lists of type Person. This is the person class:

class Person
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(int age, string firstName, string lastName)
    {
        this.Age = age;
        this.FirstName = firstName;
        this.LastName = lastName;
    }
}

and in my code, I create 2 variables, List<Person> list1 and List<Person> list2.

I fill list1 with the following variables:

list1.Add(new Person(20, "first1", "last1"));
list1.Add(new Person(30, "first2", "last2"));
list1.Add(new Person(40, "first3", "last3"));
list1.Add(new Person(50, "first4", "last4"));

and fill list2 with the following:

list2.Add(new Person(30, "first2", "last2"));
list2.Add(new Person(50, "first4", "last4"));

My goal is to have another list (List<Person> list3) with list1[0] and list[2] since that is what is not included in list2. I tried using list3 = list1.Except(list2).ToList(); but that just returns a copy of list1 and if i do list3 = list2.Except(list1).ToList(); it returns a copy of list2. How do I solve this? Am I using Except() right?

RoadRunner
  • 25,803
  • 6
  • 42
  • 75
Lavamaster
  • 87
  • 1
  • 6
  • 1
    Does this answer your question? [Difference between two lists](https://stackoverflow.com/questions/5636438/difference-between-two-lists) The exact duplicate, as well as [Get the differences between 2 lists](https://stackoverflow.com/questions/10810438/get-the-differences-between-2-lists) They can be found after 1-2 minutes of googling – Pavel Anikhouski May 10 '20 at 07:35

3 Answers3

4

The underlying issue here is that you need to ask yourself, what makes these objects equal? Just because you have assigned them equal property values, does NOT make them equal. For example, although list1[1] and list2[0], seem identical, they are completely different instances of the Person object. Therefore, you need a way to tell when one Person object is "equal" to another.Generate Equals and GetHashCode method overrides in Visual Studio.

This may help as well also, Overriding Equals in C#

Hope this helps, please mark this as the answer if this does. Good luck! Here is the code which works:

class Program
{
    static void Main(string[] args)
    {
        var list1 = new List<Person>();
        list1.Add(new Person(20, "first1", "last1"));
        list1.Add(new Person(30, "first2", "last2"));
        list1.Add(new Person(40, "first3", "last3"));
        list1.Add(new Person(50, "first4", "last4"));

        var list2 = new List<Person>();
        list2.Add(new Person(30, "first2", "last2"));
        list2.Add(new Person(50, "first4", "last4"));

        var list3 = list1.Except(list2).ToList();
    }
}

class Person : IEquatable<Person>
{
    public int Age { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }

    public Person(int age, string firstName, string lastName)
    {
        this.Age = age;
        this.FirstName = firstName;
        this.LastName = lastName;
    }

    public override bool Equals(object obj)
    {
        return Equals(obj as Person);
    }

    public bool Equals(Person otherPerson)
    {
        return otherPerson != null && this.Age == otherPerson.Age && this.FirstName == otherPerson.FirstName && this.LastName == otherPerson.LastName;
    }

    public override int GetHashCode()
    {
        return this.Age.GetHashCode() + this.FirstName.GetHashCode() + this.LastName.GetHashCode();
    }
}
Jeff
  • 972
  • 5
  • 11
  • Precisely. You can get a better hash function with less code by using tuples `GetHashCode => (Age, FirstName, LastName).GetHashCode();`. You might also do the same for equals. – Aluan Haddad May 10 '20 at 01:48
  • Thank you so much, I spend a long time trying to figure out why it was acting all weird. – Lavamaster May 10 '20 at 01:52
  • `Equals` override should take into account reference equality and use `ReferenceEquals` method – Pavel Anikhouski May 10 '20 at 07:36
  • 1
    I love this answer, but one thing should be made clear here. Objects that override `GetHashCode` should be immutable. If their hash code changes during their lifetime it breaks any dictionary that uses them as a key. – Enigmativity May 10 '20 at 08:38
  • Or limit the calculation of GetHashCode to immutable properties only. In this case there aren't any but that's easily remedied. However the custom EqualityComparer is probably better suited over IEquatable since the former won't be used by dictionaries implicitly – pinkfloydx33 May 10 '20 at 09:01
1

The equality check used by Except will be ReferenceEquals (are they literally the same object instance) because this is the default way of comparing objects for equality unless specified otherwise.
If you want Except to compare your Person class by the Age, FirstName, and LastName properties then you need to override the Equals and GetHashCode methods on the Person class.

Once you've done this list1.Except(list2).ToList() should work

Ross Gurbutt
  • 969
  • 1
  • 8
  • 15
1

You could also create a IEqualityComparer<T> class that overrides Equals() and GetHashCode():

public class PersonEqualityComparer : IEqualityComparer<Person>
{
    public bool Equals(Person x, Person y)
    {
        if (ReferenceEquals(x, y))
        {
            return true;
        }

        if (x is null || y is null)
        {
            return false;
        }

        return x.Age == y.Age && 
               x.FirstName == y.FirstName && 
               x.LastName == y.LastName;
    }

    public int GetHashCode(Person obj)
    {
        if (obj == null)
        {
            return 0;
        }

        return HashCode.Combine(obj.Age, obj.FirstName, obj.LastName);
    }
}

Then pass this comparer to Except to get the difference:

var list3 = list1.Except(list2, new PersonEqualityComparer());

Full demo on dotnetfiddle.net

RoadRunner
  • 25,803
  • 6
  • 42
  • 75