Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating a IEqualityComparer<IEnumerable<T>>

I'm using xUnit and it doesn't have a way to determine if 2 IEnumerable<T> are equal if T is custom type.

I've tried using LINQ SequenceEqual but again as the instances of T are different this returns false;

Here is a basic test with a non-working IEqualityComparer

    [Fact]
    public void FactMethodName()
    {
        var one = new[] { new KeywordSchedule() { Id = 1 } };
        var two = new[] { new KeywordSchedule() { Id = 1 } };

        Assert.Equal(one, two, new KeywordScheduleComparer());
    }

public class KeywordScheduleComparer : IEqualityComparer<IEnumerable<KeywordSchedule>>
{
    public bool Equals(IEnumerable<KeywordSchedule> x, IEnumerable<KeywordSchedule> y)
    {
        return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y));
    }

    public int GetHashCode(IEnumerable<KeywordSchedule> obj)
    {
        if (obj == null)
            return 0;

        return unchecked(obj.Select(e => e.GetHashCode()).Aggregate(0, (a, b) => a + b));  // BAD
    }
}

I'm using this in an integration test, so I insert data from a IEnumerable into a DB at the start, then call my SUT to retrieve data from DB and compare.

If you can help me get a collection comparison working I'd appreciate it!

like image 639
Jon Avatar asked Sep 10 '13 13:09

Jon


2 Answers

I just verified that this works fine with xUnit.net 1.9.2:

public class MyClass
{
    public int ID { get; set; }
    public string Name { get; set; }
}

public class MyClassComparer : IEqualityComparer<MyClass>
{
    public bool Equals(MyClass x, MyClass y)
    {
        return x.ID == y.ID;
    }

    public int GetHashCode(MyClass obj)
    {
        return obj.ID.GetHashCode();
    }
}

public class ExampleTest
{
    [Fact]
    public void TestForEquality()
    {
        var obj1 = new MyClass { ID = 42, Name = "Brad" };
        var obj2 = new MyClass { ID = 42, Name = "Joe" };

        Assert.Equal(new[] { obj1 }, new[] { obj2 }, new MyClassComparer());
    }
}

So I'm not 100% clear why you need the extra comparer. Just the single comparer should be sufficient.

like image 185
Brad Wilson Avatar answered Oct 15 '22 09:10

Brad Wilson


Well, your implementation is pending. You implemented custom comparer for IEnumerable<KeywordSchedule> but forgot to implement the same for KeywordSchedule.

x.SequenceEqual Still uses Comparer<T>.Default so it goes for reference comaprison and hence result is false.

public class KScheduleComparer : IEqualityComparer<KeywordSchedule>
{
    public bool Equals(KeywordSchedule x, KeywordSchedule y)
    {
        return x.Id == y.Id;                
    }

    public int GetHashCode(KeywordSchedule obj)
    {
        return obj.GetHashCode();
    }
}

Then modify your Equals method in KeywordScheduleComparer class as below

public class KeywordScheduleComparer : IEqualityComparer<IEnumerable<KeywordSchedule>>
{
    public bool Equals(IEnumerable<KeywordSchedule> x, IEnumerable<KeywordSchedule> y)
    {
        return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y, new KScheduleComparer()));
    }

    public int GetHashCode(IEnumerable<KeywordSchedule> obj)
    {
        if (obj == null)
            return 0;

        return unchecked(obj.Select(e => e.GetHashCode()).Aggregate(0, (a, b) => a + b));  // BAD
    }
}
like image 29
Sriram Sakthivel Avatar answered Oct 15 '22 08:10

Sriram Sakthivel