Linq Except returning entire first set with custom comparer

I have two List<MyObject>, with MyObject like so

public class MyObject
{
  public string Item {get; set;};
  public string Country {get; set;};
  public int Phone {get; set;};
}

I wish to find objects that are in List A but not in List B, however the objects do not share reference so I want to compare by all the properties on the object. I have this which overrides IEqualityComparer

    public class MyObjectComparer : IEqualityComparer<MyObject>
    {
        private PropertyInfo[] publicInstanceProperties;

        public MyObjectComparer()
        {
            publicInstanceProperties = typeof(MyObject).GetProperties(BindingFlags.Public | BindingFlags.Instance);
        }

        public bool Equals(MyObject x, MyObject y)
        {
            foreach (PropertyInfo property in publicInstanceProperties)
            {
                if (property.CanRead && property.GetIndexParameters().Length == 0 && !property.GetValue(x).Equals(property.GetValue(y)))
                {
                    return false;
                }
            }
            return true;
        }

        public int GetHashCode(MyObject obj)
        {
            long hashCodes = 0;
            foreach (PropertyInfo property in typeof(MyObject).GetProperties())
            {
                var value = property.GetValue(obj) ?? 0;
                hashCodes += value.GetHashCode();
            }

            return (int)(hashCodes % int.MaxValue);
        }
    }

And I call it like this:

var comparer = new MyObjectComparer();
var result = listA.Except(listB, comparer);

However when I do this the result is always just the entire contents of list A, even if the properties of an object in list A and list B are identical. For instance, if I have

var listA = new List<MyObject>
{
  new MyObject
  {
    Item = "ItemName",
    Country = "Spain",
    Phone = 123456789
  },
  new MyObject
  {
    Item = "DifferentName",
    Country = "Portugal",
    Phone = 00000
  }
};

var listB = new List<MyObject>
{
  new MyObject
  {
    Item = "ItemName",
    Country = "Spain",
    Phone = 123456789
  }
};

Then my code returns the entire set of listA, even though it should only return the second item in listA. Please let me know if I am failing to understand how this works. Thank you.

1 answer

  • answered 2020-02-13 02:18 Phong

    Demo on dotnet fiddle

    I've run your code and it gives us the result as expected.

    var comparer = new MyObjectComparer();
    var result = listA.Except(listB, comparer).ToList();
    
    Console.WriteLine("Count: " + result.Count);     
    Console.WriteLine(JsonConvert.SerializeObject(result[0]));
    

    Output

    Count: 1
    {"Item":"DifferentName","Country":"Portugal","Phone":0}