2

I would like to compare two list of nested objects. If the parent objects Id differ and/or any of the childrens Id or Baz property differs, I want to consider them changed.

I've implemented my own version of Equals and GetHashCode below, but despite using my own equalitycomparer, Except() still yields a result, while I expect the objects to be equal.

var foo1 = new Foo
{
    Id = 1,
    Bars = new List<Bar>
    {
        new Bar
        {
            Id = 1,
            Baz = 1.5
        },
        new Bar
        {
            Id = 1,
            Baz = 1.5
        }
    }
};

var foo2 = new Foo
{
    Id = 1,
    Bars = new List<Bar>
    {
        new Bar
        {
            Id = 1,
            Baz = 1.5
        },
        new Bar
        {
            Id = 1,
            Baz = 1.5
        }
    }
};

var diff = new[] { foo1 }.Except(new[] { foo2 });
public class Foo
{
    private sealed class IdBarsEqualityComparer : IEqualityComparer<Foo>
    {
        public bool Equals(Foo x, Foo y)
        {
            if (ReferenceEquals(x, y)) return true;
            if (ReferenceEquals(x, null)) return false;
            if (ReferenceEquals(y, null)) return false;
            if (x.GetType() != y.GetType()) return false;
            return x.Id == y.Id && Equals(x.Bars, y.Bars);
        }

        public int GetHashCode(Foo obj)
        {
            unchecked
            {
                return (obj.Id * 397) ^ (obj.Bars != null ? obj.Bars.GetHashCode() : 0);
            }
        }
    }

    public static IEqualityComparer<Foo> IdBarsComparer { get; } = new IdBarsEqualityComparer();

    public int Id { get; set; }
    public List<Bar> Bars { get; set; }
}

public class Bar
{
    protected bool Equals(Bar other)
    {
        return Id == other.Id && Baz.Equals(other.Baz);
    }

    public override bool Equals(object obj)
    {
        if (ReferenceEquals(null, obj)) return false;
        if (ReferenceEquals(this, obj)) return true;
        if (obj.GetType() != this.GetType()) return false;
        return Equals((Bar) obj);
    }

    public override int GetHashCode()
    {
        unchecked
        {
            return (Id * 397) ^ Baz.GetHashCode();
        }
    }

    public int Id { get; set; }
    public double Baz { get; set; }
}
filur
  • 2,116
  • 6
  • 24
  • 47

1 Answers1

4

There are three things wrong in your code:

  1. You are not passing the equality comparer to Except method, so it's not being used.
  2. Your GetHashCode implementation in Foo is wrong, it returns different results for same objects, so the Equals method is never called.
  3. You are calling equals on two lists: Equals(x.Bars, y.Bars), this checks for reference equality. You can use SequenceEqual instead to compare elements one by one: x.Bars.SequenceEqual(y.Bars)
Selman Genç
  • 100,147
  • 13
  • 119
  • 184
  • Thanks, 1. was a typo in the sample code. Can you give an example on what 2. should look like? Regarding 3. would `x.Bars.All(y.Bars.Contains)` work too? – filur Aug 26 '18 at 15:54
  • 1
    @filur for 2. : have a look at this: https://stackoverflow.com/questions/8094867/good-gethashcode-override-for-list-of-foo-objects-respecting-the-order and for the 3. https://stackoverflow.com/questions/22173762/check-if-two-lists-are-equal/22173821#22173821 – Selman Genç Aug 26 '18 at 16:26