Creating a IEqualityComparer<IEnumerable<T>> - c#

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!

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.

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
}
}

You could do this more elegantly using FluentAssertions library. It has plenty assertion methods for collections.
public class MyClass
{
public int ID { get; set; }
public string Name { get; set; }
protected bool Equals(MyClass other)
{
return ID == other.ID;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
if (obj.GetType() != GetType()) return false;
return Equals((MyClass) obj);
}
public override int GetHashCode()
{
unchecked
{
return (ID*397) ^ (Name != null ? Name.GetHashCode() : 0);
}
}
}
public class ExampleTest
{
[Fact]
public void TestForEquality()
{
var obj1 = new MyClass { ID = 42, Name = "Rock" };
var obj2 = new MyClass { ID = 42, Name = "Paper" };
var obj3 = new MyClass { ID = 42, Name = "Scissors" };
var obj4 = new MyClass { ID = 42, Name = "Lizard" };
var list1 = new List<MyClass> {obj1, obj2};
list1.Should().BeEquivalentTo(obj3, obj4);
}
}

Related

How do you write a GetHashCode method for an object made of a string and a collection of int32?

There is a class of Products:
public class ProductWithFeatures
{
public string Name { get; set; }
public ICollection<Feature> Features { get; set; }
}
public class Feature
{
public int Id { get; set; }
public Feature(int Id)
{
this.Id = Id;
}
}
I want to write an IEqualityComparer for this (i already have one for Feature).
The one for Feature is like this:
public class FeatureComparer : IEqualityComparer<Feature>
{
public bool Equals(Feature x, Feature y)
{
return x.Id == y.Id;
}
public int GetHashCode(Feature obj)
{
return obj.Id;
}
}
And what i wrote so far on the other one is this:
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && LinqHomework.FeatureComparer.Equals(x.Features, y.Features);
}
public int GetHashCode(ProductWithFeatures obj)
{
}
}
I can't find an answer anywhere about this. Does anybody know how to write it?
Two ProductWithFeaturess are equal if they have the same name, and have the same features in the same order.
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && x.Features.SequenceEqual(y.Features, new LinqHomework.FeatureComparer());
}
public int GetHashCode(ProductWithFeatures obj)
{
int hash = obj.Name.GetHashCode();
var featureComparer = new LinqHomework.FeatureComparer();
foreach (var feature in obj.Features)
{
hash = hash * 23 + featureComparer.GetHashCode(feature);
}
return hash;
}
}
This is a simple approach, which can be improved in a number of ways.
First, let's give our FeatureComparer a Default property, so we don't need to keep creating new instances:
public class FeatureComparer : IEqualityComparer<Feature>
{
public static FeatureComparer Default { get; } = new FeatureComparer();
// ... as before
}
This lets us write:
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
return x.Name == y.Name && x.Features.SequenceEqual(y.Features, LinqHomework.FeatureComparer.Default);
}
public int GetHashCode(ProductWithFeatures obj)
{
int hash = obj.Name.GetHashCode();
foreach (var feature in obj.Features)
{
hash = hash * 23 + LinqHomework.FeatureComparer.Default.GetHashCode(feature);
}
return hash;
}
}
We're also not handling the case where our methods are passed null, or the name of a feature is null, so let's deal with those. We can also test whether x and y are the same object in Equals.
We'll also do the integer operations in an unchecked block in case it overflows (and the assembly is compiled with /checked).
Note that we use ReferenceEquals instead of ==, in case you end up implementing the == operator in your types.
public class ProductComparer : IEqualityComparer<LinqHomework.ProductWithFeatures>
{
public bool Equals(ProductWithFeatures x, ProductWithFeatures y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
if (x.Name != y.Name)
return false;
if (ReferenceEquals(x.Features, y.Features))
return true;
if (ReferenceEquals(x.Features, null) || ReferenceEquals(y.Features, null))
return false;
if (!x.Features.SequenceEquals(y.Features, LinqHomework.FeatureComparer.Default))
return false;
return true;
}
public int GetHashCode(ProductWithFeatures obj)
{
if (ReferenceEquals(obj, null))
return 0;
unchecked
{
int hash = obj.Name?.GetHashCode() ?? 0;
if (!ReferenceEquals(obj.Features, null))
{
foreach (var feature in obj.Features)
{
hash = hash * 23 + LinqHomework.FeatureComparer.Default.GetHashCode(feature);
}
return hash;
}
}
}
}
It's really up to you. I personally would go for something like
public int GetHashCode( ProductWithFeatures obj )
{
string toHash = obj.Name;
foreach( var feature in obj.Features )
toHash += feature.GetHashCode();
return toHash.GetHashCode();
}
It's not the nicest code ever, but it does what it's supposed to do.

EqualityComparer on a nested object

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; }
}
There are three things wrong in your code:
You are not passing the equality comparer to Except method, so it's not being used.
Your GetHashCode implementation in Foo is wrong, it returns different results for same objects, so the Equals method is never called.
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)

How to compare two dictionaries by value, even if the key or value are reference types?

I want to compare two Dictionary<SomeClass, List<AnotherClass>>. The dictionaries should be compared regardless of KeyValuePairs order. In the comments, ordering the values and then using SequenceEquals was suggested, but I am not sure how to Sort a dictionary (also, even sorting the lists could help, it's not possible as far as I understand because the generic type of the list is not guaranteed to be IComparable).
When I try to use the Equals method, I always get false since it checks whether the Lists are reference equals. I want it to check if the Lists are value equals. How to accomplish that?
So for example, let's say I have the following dictionaries:
var dictionary1 = new Dictionary<Day, List<WorkSession>>
{
{ Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } },
{ Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
{ Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } }
};
var dictionary2 = new Dictionary<Day, List<WorkSession>>
{
{ Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } },
{ Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
{ Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } }
};
WorkSession:
class WorkSession : IEquatable<WorkSession>
{
public string Entrance { get; private set; }
public string Exit { get; private set; }
public WorkSession(string entrance, string exit)
{
Entrance = entrance;
Exit = exit;
}
public override bool Equals(object obj)
{
return Equals(obj as WorkSession);
}
public bool Equals(WorkSession other)
{
return other != null &&
Entrance == other.Entrance &&
Exit == other.Exit;
}
public override int GetHashCode()
{
var hashCode = 1257807568;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Entrance);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Exit);
return hashCode;
}
public static bool operator ==(WorkSession session1, WorkSession session2)
{
return EqualityComparer<WorkSession>.Default.Equals(session1, session2);
}
public static bool operator !=(WorkSession session1, WorkSession session2)
{
return !(session1 == session2);
}
}
I want two compare these dictionaries, and the result should be True. How can I accomplish that?
A possible solution is below. The equality checks are slightly tightened up compared to some of the other answers. For examples some null checks are added, and the values are checked in a different way (basically not just checking that the Values are the same but that they are the same for a given key).
Also, when comparing the lists, the data is sorted by all of the properties of WorkSession - just in case that two different WorkSession values have the same hash code. A better long term solution would be for WorkSession to implement IComparable.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
namespace MattConsole
{
class Program
{
static void Main(string[] args)
{
var x = new Dictionary<Day, List<WorkSession>>
{
{ Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } },
{ Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
{ Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } }
};
var y = new Dictionary<Day, List<WorkSession>>
{
{ Day.Sunday, new List<WorkSession>() { new WorkSession("10:00", "00:00") } },
{ Day.Monday, new List<WorkSession>() { new WorkSession("20:00", "00:00") } },
{ Day.Tuesday, new List<WorkSession>() { new WorkSession("22:00", "00:00") } }
};
var w = new MyCustomComparer();
var shouldBeTrue = w.Equals(x, y);
Console.WriteLine(shouldBeTrue);
x[Day.Wednesday] = new List<WorkSession>() { new WorkSession("10:00", "00:00") };
x[Day.Thursday] = new List<WorkSession>() { new WorkSession("10:01", "00:01") };
y[Day.Thursday] = new List<WorkSession>() { new WorkSession("10:00", "00:00") };
y[Day.Wednesday] = new List<WorkSession>() { new WorkSession("10:01", "00:01") };
var shouldBeFalse = w.Equals(x, y);
Console.WriteLine(shouldBeFalse);
Console.ReadLine();
}
}
public class MyCustomComparer : IEqualityComparer<Dictionary<Day, List<WorkSession>>>
{
public bool Equals(Dictionary<Day, List<WorkSession>> x, Dictionary<Day, List<WorkSession>> y)
{
if (ReferenceEquals(x, null))
return ReferenceEquals(y, null);
if (ReferenceEquals(y, null))
return false;
if (x.Count != y.Count)
return false;
if (!x.Keys.OrderBy(z => z).SequenceEqual(y.Keys.OrderBy(z => z)))
return false;
foreach (var kvp in x)
{
List<WorkSession> matching;
if (y.TryGetValue(kvp.Key, out matching))
{
if (ReferenceEquals(matching, null))
return ReferenceEquals(kvp.Value, null);
if (ReferenceEquals(kvp.Value, null))
return false;
// ordering by hash code is not strictly necessary
if (
!matching.OrderBy(z => z.GetHashCode())
.ThenBy(z => z.Entrance).ThenBy(z => z.Exit)
.SequenceEqual(
kvp.Value.OrderBy(z => z.GetHashCode())
.ThenBy(z => z.Entrance).ThenBy(z => z.Exit)))
return false;
}
else
return false;
}
return true;
}
public int GetHashCode(Dictionary<Day, List<WorkSession>> obj)
{
throw new NotImplementedException();
}
}
public class WorkSession : IEquatable<WorkSession>
{
public string Entrance { get; private set; }
public string Exit { get; private set; }
public WorkSession(string entrance, string exit)
{
Entrance = entrance;
Exit = exit;
}
public override bool Equals(object obj)
{
return Equals(obj as WorkSession);
}
public bool Equals(WorkSession other)
{
return other != null &&
Entrance == other.Entrance &&
Exit == other.Exit;
}
public override int GetHashCode()
{
var hashCode = 1257807568;
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Entrance);
hashCode = hashCode * -1521134295 + EqualityComparer<string>.Default.GetHashCode(Exit);
return hashCode;
}
public static bool operator ==(WorkSession session1, WorkSession session2)
{
return EqualityComparer<WorkSession>.Default.Equals(session1, session2);
}
public static bool operator !=(WorkSession session1, WorkSession session2)
{
return !(session1 == session2);
}
}
}
Here is something crude that works that might get you started. There will be some edge cases you need to think about and adjust code accordingly.
class Program
{
static void Main(string[] args)
{
var x = new Dictionary<SomeClass, List<AnotherClass>>();
var y = new Dictionary<SomeClass, List<AnotherClass>>();
x.Add(new SomeClass { SomeNumericProperty = 1 }, new List<AnotherClass> { new AnotherClass { SomeStringProperty = "1" } });
y.Add(new SomeClass { SomeNumericProperty = 1 }, new List<AnotherClass> { new AnotherClass { SomeStringProperty = "1" } });
var w = new MyCustomComparer();
var z = w.Equals(x, y);
}
}
public class MyCustomComparer : IEqualityComparer<Dictionary<SomeClass, List<AnotherClass>>>
{
public bool Equals(Dictionary<SomeClass, List<AnotherClass>> x, Dictionary<SomeClass, List<AnotherClass>> y)
{
var keysAreEqual = x.Keys.OrderBy(o => o.GetHashCode()).SequenceEqual(y.Keys.OrderBy(o => o.GetHashCode()));
var valuesAreEqual = x.SelectMany(o => o.Value).OrderBy(o => o.GetHashCode()).SequenceEqual(y.SelectMany(o => o.Value).OrderBy(o => o.GetHashCode()));
return keysAreEqual && valuesAreEqual;
}
public int GetHashCode(Dictionary<SomeClass, List<AnotherClass>> obj)
{
throw new NotImplementedException();
}
}
public class AnotherClass
{
protected bool Equals(AnotherClass other)
{
return string.Equals(SomeStringProperty, other.SomeStringProperty);
}
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((AnotherClass)obj);
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + SomeStringProperty.GetHashCode();
return hash;
}
public string SomeStringProperty { get; set; }
}
public class SomeClass
{
public int SomeNumericProperty { get; set; }
protected bool Equals(SomeClass other)
{
return SomeNumericProperty == other.SomeNumericProperty;
}
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((SomeClass)obj);
}
public override int GetHashCode()
{
int hash = 13;
hash = (hash * 7) + SomeNumericProperty.GetHashCode();
return hash;
}
}

Distinct with encapsulated equality

I have a collection of objects where I want to find distinct values based on several properties.
I could do this:
var distinct = myValues.GroupBy(p => new { A = p.P1, B = p.P2 });
But I want to encapsulate the equality sementics. Something like this:
public interface IKey<T>
{
bool KeyEquals(T other);
}
public class MyClass : IKey<MyClass>
{
public string P1 { get; set; }
public string P2 { get; set; }
public bool KeyEquals(MyClass other)
{
if(object.ReferenceEquals(this, other)
return true;
if(other == null)
return false;
return this.P1 == other.P1 && this.P2 == other.P2;
}
}
Is there an O(N) way to get distinct values using my KeyEquals function?
If you can't change MyClass, you can implement an IEqualityComparer:
class MyClassComparer : IEqualityComparer<MyClass>
{
public bool Equals(MyClass m1, MyClass m2)
{
return m1.KeyEquals(m2);
}
public int GetHashCode(MyClass m)
{
return (m.P1.GetHashCode() *23 ) + (m.P2.GetHashCode() * 17);
}
}
And pass it to GroupBy
var distinct = myValues.GroupBy(p => p, new MyClassComparer());

Linq Distinct does not function as per forum and Microsoft examples

using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
namespace UnitTest.Model
{
[TestFixture]
public class SampleEquatableObjectTest
{
[Test]
public void TwoIdenticalUsersComparedEqualTrue()
{
var user1 = new SampleObject { Id = 1, Name = "Test User" };
var user2 = new SampleObject { Id = 1, Name = "Test User" };
Assert.IsTrue(user1.Equals(user2));
}
[Test]
public void TwoDifferentUsersComparedEqualFalse()
{
var user1 = new SampleObject { Id = 1, Name = "Test User 1" };
var user2 = new SampleObject { Id = 2, Name = "Test User 2" };
Assert.IsFalse(user1.Equals(user2));
}
[Test]
public void CollectionOfUsersReturnsDistinctList()
{
var userList = new List<SampleObject>
{
new SampleObject {Id = 1, Name = "Test User"},
new SampleObject {Id = 1, Name = "Test User 1"},
new SampleObject {Id = 2, Name = "Test User 2"}
};
Assert.AreEqual(userList.Count, 3);
var result = userList.Distinct();
Assert.AreEqual(result.Count(), 2);
var multipleTest = (from r in result group r by new { r.Id } into multGroup where multGroup.Count() > 1 select multGroup.Key).Any();
Assert.IsFalse(multipleTest);
}
public class SampleObject : IEquatable<SampleObject>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(SampleObject other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null) || ReferenceEquals(this, null))
return false;
return Id.Equals(other.Id);
}
}
}
}
The distinct method in this test case does not return a distinct list. The assert for count will fail. I looked at other similar questions and Microsoft examples but they look exactly like the code I have in the test. Any input?
You also need to override the GetHashCode() and Equals methods from the Object class. For more information see this corresponding FXCOP violation.
Then your tests will work like expected.
public class SampleObject : IEquatable<SampleObject>
{
public int Id { get; set; }
public string Name { get; set; }
public bool Equals(SampleObject other)
{
if (ReferenceEquals(this, other))
return true;
if (ReferenceEquals(other, null) || ReferenceEquals(this, null))
return false;
return Id.Equals(other.Id);
}
public override int GetHashCode()
{
return Id;
}
public override bool Equals(object obj)
{
return Equals(obj as SampleObject);
}
}
You're implementing an interface without defining both members to the specification of the interface. Your code needs to look like this:
public bool Equals(SampleObject x, SampleObject y)
{
if (ReferenceEquals(x, y))
return true;
if (ReferenceEquals(x, null) || ReferenceEquals(y, null))
return false;
return x.Id.Equals(y.Id);
}
public int GetHashCode(SampleObject obj)
{
public int GetHashCode(SampleObject obj)
{
if (Object.ReferenceEquals(obj, null)) return 0;
int hashId = obj.Id == null ? 0 : obj.Id.GetHashCode();
int hashName = obj.Name == null ? 0 : obj.Name.GetHashCode();
return hashId ^ hashName; // or what ever you want you hash to be, hashID would work just as well.
}
}

Categories

Resources