Creating a IEqualityComparer<IEnumerable<T>>
Solution 1
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.
Solution 2
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
}
}
Jon
Updated on June 28, 2022Comments
-
Jon almost 2 years
I'm using xUnit and it doesn't have a way to determine if 2
IEnumerable<T>
are equal ifT
is custom type.I've tried using
LINQ SequenceEqual
but again as the instances ofT
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!
-
Jon over 10 yearsSo put public bool Equals(IEnumerable<KeywordSchedule> x, IEnumerable<KeywordSchedule> y) on the KeywordSchedule class?
-
Sriram Sakthivel over 10 yearsNo It should be in
KeywordScheduleComparer
class. If not clear I'll update full code -
Jon over 10 yearsThanks. Why are there 2 comparers?
-
Servy over 10 years@Jon Because you need to be able to compare two different types of things, the underlying items, and sequences of items. If you only needed to compare one type you'd only need one comparer.
-
Servy over 10 yearsYour first comparer should return
obj.Id.GetHashCode()
instead. ThatId
is very important. -
Sriram Sakthivel over 10 years@Jon I think servy answered your question. Or you could simply do
bool res = one.SequenceEqual(two, new KScheduleComparer());
and you can get rid of second comparer. -
Jon over 10 years@BradWilson Thats what I was doing I think in question I think so not sure the difference
-
Jon over 10 yearsAh thanks, I wasnt too far off. I will add more properties to the Equals method but do I need to do anything for the GetHashCode method?
-
Brad Wilson over 10 yearsTechnically you're supposed to implement GetHashCode, but if the comparer is for xUnit.net only, you can skip it, as we don't use GetHashCode. Just leave yourself a note to implement it if you ever decide to use the thing for real. :)
-
Hilarion over 2 yearsAlthough I agree, that FluentAssertions are a good solution to many such issues, your example does not directly address the question. I.e. you are not asserting if two lists are equal (you only have one) and, if I remember correctly, default use of
BeEquivalentTo
disregards the order of items in compared collections, which is probably not the desired result (as the example in the question usedSequenceEqual
). Also, when using FluentAssetions, you actually don't need to haveEquals
overriden in theMyClass
- you can provide comparison rules to FluentAssertions to compare by properties.