Problem: Custom Object implements EqualityComparer and IEquatable, but Dictionary doesn't always use these methods.
Context: I have created a helper class, FilePath for dealing with file paths instead of treating them as strings. The helper class is responsible for determining if two file paths are equal. I then need to store FilePaths in a Dictionary<FilePath, object>.
Goal:
Assert.True(new FilePath(#"c:\temp\file.txt").Equals(
new FilePath(#"C:\TeMp\FIle.tXt"));
var dict = new Dictionary<FilePath, object>
{
{new FilePath(#"c:\temp\file.txt"), new object()}
}
Assert.True(dict.ContainsKey(new FilePath(#"c:\temp\file.txt"));
Assert.True(dict.ContainsKey(new FilePath(#"C:\TeMp\FIle.tXt"));
I've created my FilePath class:
public class FilePath : EqualityComparer, IEquatable
{
private string _fullPath;
public FilePath (string fullPath)
{
_fullPath = Path.GetFullPath(fullPath);
}
public override bool Equals(FilePath x, FilePath y)
{
if (null == x || null == y)
return false;
return (x._fullPath.Equals(y._fullPath, StringComparison.InvariantCultureIgnoreCase));
}
public override int GetHashCode(FilePath obj)
{
return obj._fullPath.GetHashCode();
}
public bool Equals(FilePath other)
{
return Equals(this, other);
}
public override bool Equals(object obj)
{
return Equals(this, obj as FilePathSimple);
}
}
Question:
Assert 1 and 2 pass, but Assert 3 fails:
Assert.True(dict.ContainsKey(new FilePath(#"C:\TeMp\FIle.tXt"));
I needed to override GetHashCode() as well:
public override int GetHashCode()
{
return _fullPath.ToLower().GetHashCode();
}
References: What's the role of GetHashCode in the IEqualityComparer<T> in .NET?
Compiler Warning (level 3) CS0659
Related
Code:
public class Coll : KeyedCollection<byte[], MyObject>
{
protected override byte[] GetKeyForItem(MyObject item) => item.Key;
}
public class EquComparer : IEqualityComparer<byte[]>
{
public bool Equals(byte[]? x, byte[]? y)
{
if (x is null && y is null) return true;
if (x is null) return false;
if (y is null) return false;
return x.SequenceEqual(y);
}
public int GetHashCode([DisallowNull] byte[] obj)
{
int result = Int32.MinValue;
foreach (var b in obj)
{
result += b;
}
return result;
}
}
My key is byte[]. I want to set the default equality comparer to compare keys with to something using byte[]::SequenceEqual() to keep two items with the same keys from being added.
Is there a way to do this?
Edit: As other have pointed out I could use the constructor to specify a non default equality comparer. I am certain it will be forgotten at some point giving rise to a bug that will be difficult to find. That is why I want to add some code to the class that makes my custom equality comparer the default for that class.
The KeyedCollection<TKey,TItem> class has a constructor that accepts an IEqualityComparer<TKey>. You could invoke this constructor when instantiating the derived class:
public class Coll : KeyedCollection<byte[], MyObject>
{
public Coll() : base(new EquComparer()) { }
protected override byte[] GetKeyForItem(MyObject item) => item.Key;
}
We have GUIDs as identifiers in our systems. As it's easy to mess up and pass the id of one entity into a method that expects the id of another entity (lets say you pass the OrderId to the InvoiceId by mistake because it's all Guids) we created our own types for Guids, so the compiler can easily tell me "hey, don't pass an OrderId here, I expect an InvoiceId".
So basically, we have lots of wrappers around Guid. Those wrappers work well, they are basically copies of the Guid interface delegating all the work to their internally stored Guid.
One thing that I cannot figure out is that Assert.AreEqual(a, b) on two of our custom identifiers will fail. It calls object.Equals(a, b) that in turn calls a == b and that will not call my operator == but instead call something else and return false. It does not for Guid though and I cannot figure out what I missed.
What do I need to implement for my custom types to actually work and return true on object.Equals(a, b) given that it already does on operator ==?
namespace ConsoleApp13
{
using System;
using System.Runtime.InteropServices;
//// Same signature, interfaces and and attributes as
//// https://referencesource.microsoft.com/#mscorlib/system/guid.cs
[StructLayout(LayoutKind.Sequential)]
[Serializable]
[ComVisible(true)]
// not accessible for me: [System.Runtime.Versioning.NonVersionable]
public struct CustomId : IFormattable, IComparable, IComparable<CustomId>, IEquatable<CustomId>
{
public static readonly CustomId Empty = new CustomId();
private readonly Guid internalGuid;
private CustomId(Guid guid)
{
this.internalGuid = guid;
}
public static bool operator ==(CustomId a, CustomId b)
{
return a.internalGuid == b.internalGuid;
}
public static bool operator !=(CustomId a, CustomId b)
{
return !(a.internalGuid == b.internalGuid);
}
public static CustomId NewGuid()
{
return new CustomId(Guid.NewGuid());
}
public static implicit operator Guid(CustomId value)
{
return value.internalGuid;
}
public static explicit operator CustomId(Guid value)
{
return new CustomId(value);
}
public override string ToString()
{
return "[" + this.GetType().Name + ":" + this.internalGuid.ToString("D") + "]";
}
public override int GetHashCode()
{
return this.internalGuid.GetHashCode();
}
public override bool Equals(object obj)
{
return this.internalGuid.Equals(obj);
}
public bool Equals(CustomId other)
{
return this.internalGuid.Equals(other.internalGuid);
}
public int CompareTo(object obj)
{
return this.internalGuid.CompareTo(obj);
}
public int CompareTo(CustomId other)
{
return this.internalGuid.CompareTo(other.internalGuid);
}
public string ToString(string format, IFormatProvider formatProvider)
{
return this.internalGuid.ToString(format, formatProvider);
}
}
internal static class Program
{
internal static void Main()
{
{
var a = CustomId.NewGuid();
var b = a;
// shows true false
Console.WriteLine("{0} {1}", a == b, object.Equals(a, b));
}
{
var a = Guid.NewGuid();
var b = a;
// shows true true
Console.WriteLine("{0} {1}", a == b, object.Equals(a, b));
}
Console.WriteLine(#"Done.");
Console.ReadLine();
}
}
}
Your code here:
public override bool Equals(object obj)
{
return this.internalGuid.Equals(obj);
}
is going to unwrap itself, but it doesn't unwrap the other instance, so : it will always fail if obj is a CustomId, as the Guid won't expect to be handed a CustomId (it wants a Guid). Perhaps this should be:
public bool Equals(object obj) => obj is CustomId cid && cid == this;
Note that CompareTo should probably be similar:
public int CompareTo(object obj) => obj is CustomId cid ? this.CompareTo(cid) : -1;
The answer from Marc Gravell is correct but I want to note something.
It calls object.Equals(a, b) that in turn calls a == b
This is a wrong assumption. object.Equals(a, b) will call a.Equals(b) if both of them are not null. Subtle difference but it is a difference:
https://referencesource.microsoft.com/#mscorlib/system/object.cs,d9262ceecc1719ab
public static bool Equals(Object objA, Object objB)
{
if (objA==objB) {
return true;
}
if (objA==null || objB==null) {
return false;
}
return objA.Equals(objB);
}
I created class with overriden Equals. The problem is that Distinct method doesn't work for my class.
class MyClass
{
public int Item1 { get; private set; }
public int Item2 { get; private set; }
public MyClass(int item1, int item2)=>(Item1,Item2)=(item1,item2);
public override bool Equals(object obj)
{
var other = obj as MyClass;
if (other == null)
{
return false;
}
return (this.Item1 == other.Item1 && this.Item2 == other.Item2);
}
}
class Program
{
static void Main(string[] args)
{
MyClass x = new MyClass(1, 0);
MyClass y = new MyClass(1, 0);
var list = new List<MyClass>();
list.Add(x);
list.Add(y);
bool b = x.Equals(y)); //True
var distincts = list.Distinct(); //Doesn't work, contains both
}
}
How can I fix that and why it doesn't use my Equals in Distinct?
Distinct docs:
Returns distinct elements from a sequence by using the default equality comparer to compare values.
Let's see what the default equality comparer does:
The Default property checks whether type T implements the System.IEquatable<T> interface and, if so, returns an EqualityComparer<T> that uses that implementation. Otherwise, it returns an EqualityComparer<T> that uses the overrides of Object.Equals and Object.GetHashCode provided by T.
So basically, to make this work, you either:
implement GetHashCode as well
implement IEquatable<T>
Call the overload of Distinct that accepts a custom equality comparer.
If I were you, I would choose the second one because you need to change the least of your code.
class MyClass: IEquatable<MyClass> {
...
public bool Equals(MyClass obj)
{
if (obj == null)
{
return false;
}
return (this.Item1 == obj.Item1 && this.Item2 == obj.Item2);
}
}
You have to override GetHashCode as well:
public override int GetHashCode()
{
return Item1; // or something
}
Distinct first compares the hashcodes, which should be computed faster than the actual Equals. Equals is only further evaulated if the hashcodes are equal for two instances.
You need to implement IEquatable<MyClass> in MyClass and provide your own implementation of GetHashCode and Equals method.
see this for more information.
class MyClass
{
public int Item1 { get; private set; }
public int Item2 { get; private set; }
public MyClass(int item1, int item2)=>(Item1,Item2)=(item1,item2);
public override bool Equals(object obj)
{
var other = obj as MyClass;
if (other == null)
{
return false;
}
return (this.Item1 == other.Item1 && this.Item2 == other.Item2);
}
public override int GetHashCode()
{
return this.Item1;
}
}
I have two classes which both derive from the same parent:
public class People{
public string BetterFoot;
public override bool Equals(object obj){
if (obj == null || this.GetType() != obj.GetType())
return false;
People o = (People)obj;
return (this.BetterFoot == o.BetterFoot);
}
public class LeftiesOrRighties: People{
public string BetterHand;
public override bool Equals(object obj){
if (obj == null || this.GetType() != obj.GetType())
return false;
LeftiesOrRighties o = (LeftiesOrRighties)obj;
return (this.BetterFoot == o.BetterFoot) &&
(this.BetterHand == o.BetterHand)
}
}
public class Ambidextrous: People{
public string FavoriteHand;
}
(There are GetHashCodes in there, too, but I know that they work.)
I'd like to compare collections of them, based on their root Equals():
ThoseOneHanded = new List<LeftiesOrRighties>(){new LeftiesOrRighties(){BetterFoot = "L"}};
ThoseTwoHanded = new List<Ambidextrous>(){new Ambidextrous(){BetterFoot = "L"}};
//using NUnit
Assert.That ((People)ThoseOneHanded[0], Is.EqualTo((People)ThoseTwoHanded[0])));
Unfortunately, this returns false.
Why? Shouldn't the casting make them (for all intents and purposes, if not exactly) the same type, and thus use the base methods? And if not, how do I truly cast the underlying type back to People?
Cast doesn't change the object itself, so the result of GetType will always be the same and so your this.GetType() != obj.GetType() will be true and so the function will return false.
The following logic could potentially gain the behaviour you want (and you don't need to cast to People)
public class People
{
public string BetterFoot;
public override bool Equals(object obj)
{
var o = obj as People;
if (o == null) return false;
return (this.BetterFoot = o.BetterFoot);
}
public class LeftiesOrRighties: People
{
public string BetterHand;
public override bool Equals(object obj)
{
var o = obj as LeftiesOrRighties;
if ( o == null) return base.Equals(obj);
return (this.BetterFoot = o.BetterFoot) && (this.BetterHand = o.BetterHand)
}
}
public class Ambidextrous: People
{
public string FavoriteHand;
}
As Bob Vale pointed out, the cast does not change type.
The standard solution used across the .NET Framework is to use custom object implementing IEqualityComparer or its generic variant. Then your compare/find method takes 2 objects/collections and uses comparer to perform the custom comparison.
I.e. many LINQ methods take custom compare to find/filter objects like
Enumerable.Distinct
public static IEnumerable<TSource> Distinct<TSource>(
this IEnumerable<TSource> source,
IEqualityComparer<TSource> comparer
)
Sample comparer:
class Last3BitsComparer : IEqualityComparer<int>
{
public bool Equals(int b1, int b2)
{
return (b1 & 3) == (b2 & 3);
}
public int GetHashCode(int bx)
{
return bx & 3;
}
}
I'm having troubles with the Except() method.
Instead of returning the difference, it returns the original set.
I've tried implementing the IEquatable and IEqualityComparer in the Account class.
I've also tried creating a separate IEqualityComparer class for Account.
When the Except() method is called from main, it doesn't seem to call my custom Equals() method, but when I tried Count(), it did call the custom GetHashCode() method!
I'm sure I made a trivial mistake somewhere and I hope a fresh pair of eyes can help me.
main:
IEnumerable<Account> everyPartnerID =
from partner in dataContext.Partners
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> hasAccountPartnerID =
from partner in dataContext.Partners
from account in dataContext.Accounts
where
!partner.ID.Equals(Guid.Empty) &&
account.IDPartner.Equals(partner.ID) &&
account.Username.Equals("Special")
select new Account { IDPartner = partner.ID, Name = partner.Name };
IEnumerable<Account> noAccountPartnerID =
everyPartnerID.Except(
hasAccountPartnerID,
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)));
Account:
public class Account : IEquatable<Account>
{
public Guid IDPartner{ get; set; }
public string Name{ get; set; }
/* #region IEquatable<Account> Members
public bool Equals(Account other)
{
return this.IDPartner.Equals(other.IDPartner);
}
#endregion*/
}
LambdaComparer:
public class LambdaComparer<T> : IEqualityComparer<T>
{
private readonly Func<T, T, bool> _lambdaComparer;
private readonly Func<T, int> _lambdaHash;
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => o.GetHashCode())
{
}
public LambdaComparer(Func<T, T, bool> lambdaComparer, Func<T, int> lambdaHash)
{
if (lambdaComparer == null)
throw new ArgumentNullException("lambdaComparer");
if (lambdaHash == null)
throw new ArgumentNullException("lambdaHash");
_lambdaComparer = lambdaComparer;
_lambdaHash = lambdaHash;
}
public bool Equals(T x, T y)
{
return _lambdaComparer(x, y);
}
public int GetHashCode(T obj)
{
return _lambdaHash(obj);
}
}
Basically your LambdaComparer class is broken when you pass in just a single function, because it uses the "identity hash code" provider if you don't provide anything else. The hash code is used by Except, and that's what's causing the problem.
Three options here:
Implement your own ExceptBy method and then preferably contribute it to MoreLINQ which contains that sort of thing.
Use a different implementation of IEqualityComparer<T>. I have a ProjectionEqualityComparer class you can use in MiscUtil - or you can use the code as posted in another question.
Pass a lambda expression into your LambdaComparer code to use for the hash:
new LambdaComparer<Account>((x, y) => x.IDPartner.Equals(y.IDPartner)),
x => x.IDPartner.GetHashCode());
You could also quickly fix your LambdaComparer to work when only the equality parameters are supplied like this:
public LambdaComparer(Func<T, T, bool> lambdaComparer) :
this(lambdaComparer, o => 1)
{
}
Look here, how to use and implementing IEqualityComparer in way with linq.Except and beyond.
https://www.dreamincode.net/forums/topic/352582-linq-by-example-3-methods-using-iequalitycomparer/
public class Department {
public string Code { get; set; }
public string Name { get; set; }
}
public class DepartmentComparer : IEqualityComparer {
// equal if their Codes are equal
public bool Equals(Department x, Department y) {
// reference the same objects?
if (Object.ReferenceEquals(x, y)) return true;
// is either null?
if (Object.ReferenceEquals(x, null) || Object.ReferenceEquals(y, null))
return false;
return x.Code == y.Code;
}
public int GetHashCode(Department dept) {
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(dept, null)) return 0;
return dept.Code.GetHashCode();
}
}
IEnumerable<Department> deptExcept = departments.Except(departments2,
new DepartmentComparer());
foreach (Department dept in deptExcept) {
Console.WriteLine("{0} {1}", dept.Code, dept.Name);
}
// departments not in departments2: AC, Accounts.
IMO, this answer above is the simplest solution compared to other solutions for this problem. I tweaked it such that I use the same logic for the Object class's Equals() and GetHasCode(). The benefit is that this solution is completely transparent to the client linq expression.
public class Ericsson4GCell
{
public string CellName { get; set; }
public string OtherDependantProperty { get; set; }
public override bool Equals(Object y)
{
var rhsCell = y as Ericsson4GCell;
// reference the same objects?
if (Object.ReferenceEquals(this, rhsCell)) return true;
// is either null?
if (Object.ReferenceEquals(this, null) || Object.ReferenceEquals(rhsCell, null))
return false;
return this.CellName == rhsCell.CellName;
}
public override int GetHashCode()
{
// If Equals() returns true for a pair of objects
// then GetHashCode() must return the same value for these objects.
// if null default to 0
if (Object.ReferenceEquals(this, null)) return 0;
return this.CellName.GetHashCode();
}
}