问题
I have A and B classes both implementing interface I.
public interface I
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class A : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
}
public class B : I
{
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private string readonly _someBRelatedStuff;
private double readonly _someOtherBRelatedStuff;
// Rest of class...
}
Sometimes I want to test equality between A and B (usually when comparing lists of A and lists of B) based on the equality of their I properties (SomeInt, SomeBool, SomeFloat), so I implemented IEquatable<I> on both and I compare them based on their shared I properties values.
The problem is that I already have an implementation for GetHashCode() on both A and B that produces different hashes because I'm taking into account additional members.
B does not depend on A so I use interface I to compare them and it has a list of properties with getters.
I read in a StackOverflow answer that:
If you are implementing a class, you should always make sure that two equal objects have the same hashcode.
So does that mean that everytime a class A want to be implement interface I, and I want to be able to compare instances that implement I, I have to make sure the hashcode is calculated in the same way for all instances of I and only use I properties?
I do feel like I'm not intended to implement IEquatable<T> when T is an interface, but my alternatives are:
- Using regular inheritance with a base class - I rather avoid inheritance when possible, and this solution won't work if B needs to derive from some framework
Cclass because of single inheritance - Implement equality checks between
AandBwith a method on eitherAorB- will create code duplication - Have an equality check method between
Iinstances defined inI- sounds like the best option
Are there any options that I'm missing?
回答1:
Consider making the a IEqualityComparer<> class to compare the common values.
I have renamed the interface to ICommon for readability
public interface ICommon
{
int SomeInt { get; }
bool SomeBool { get; }
float SomeFloat { get; }
}
public class CommonComparer : IEqualityComparer<ICommon>
{
public bool Equals(ICommon x, ICommon y)
{
return x.SomeInt.Equals(y.SomeInt)
&& x.SomeBool.Equals(y.SomeBool)
&& x.SomeFloat.Equals(y.SomeFloat);
}
public int GetHashCode(ICommon obj)
{
unchecked
{
int hc = -1817952719;
hc = (-1521134295)*hc + obj.SomeInt.GetHashCode();
hc = (-1521134295)*hc + obj.SomeBool.GetHashCode();
hc = (-1521134295)*hc + obj.SomeFloat.GetHashCode();
return hc;
}
}
}
and the program can distinguish between the equal items on two lists.
class Program
{
static void Main(string[] args)
{
var listA = new List<A>
{
new A(1001, true, 1.001f, "A1"),
new A(1002, true, 1.002f, "A2"),
new A(1003, false, 1.003f, "A1"),
new A(1004, false, 1.004f, "A4")
};
var listB = new List<B>
{
new B(1001, true, 1.001f, "B1", 2.5),
new B(1002, false, 1.002f, "B2", 2.8),
new B(1003, true, 1.003f, "B3", 2.9),
new B(1004, false, 1.004f, "B4", 2.9)
};
var common = Enumerable.Intersect(listA, listB, new CommonComparer()).OfType<ICommon>();
Console.WriteLine($"{"SomeInt",-8} {"Bool",-6} {"SomeFloat",-10}");
foreach (var item in common)
{
Console.WriteLine($"{item.SomeInt,-8} {item.SomeBool,-6} {item.SomeFloat,-10}");
}
//SomeInt Bool SomeFloat
//1001 True 1.001
//1004 False 1.004
}
}
and the rest of the code definitions
public class A : ICommon, IEquatable<A>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
private readonly string _someARelatedStuff;
// Rest of class...
public A(ICommon other, string someARelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someARelatedStuff)
{ }
public A(int someInt, bool someBool, float someFloat, string someARelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someARelatedStuff = someARelatedStuff;
}
public override string ToString() => _someARelatedStuff;
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is A other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(A other)
{
return comparer.Equals(this, other)
&& _someARelatedStuff.Equals(other._someARelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someARelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}
public class B : ICommon, IEquatable<B>
{
static readonly CommonComparer comparer = new CommonComparer();
public int SomeInt { get; }
public bool SomeBool { get; }
public float SomeFloat { get; }
readonly string _someBRelatedStuff;
readonly double _someOtherBRelatedStuff;
// Rest of class...
public B(ICommon other, string someBRelatedStuff, double someOtherBRelatedStuff)
: this(other.SomeInt, other.SomeBool, other.SomeFloat, someBRelatedStuff, someOtherBRelatedStuff)
{ }
public B(int someInt, bool someBool, float someFloat, string someBRelatedStuff, double someOtherBRelatedStuff)
{
this.SomeInt = someInt;
this.SomeBool = someBool;
this.SomeFloat = someFloat;
this._someBRelatedStuff = someBRelatedStuff;
this._someOtherBRelatedStuff = someOtherBRelatedStuff;
}
public override string ToString() => $"{_someBRelatedStuff}, {_someOtherBRelatedStuff.ToString("g4")}";
#region IEquatable Members
public override bool Equals(object obj)
{
if (obj is B other)
{
return Equals(other);
}
return false;
}
public virtual bool Equals(B other)
{
return comparer.Equals(this, other)
&& _someBRelatedStuff.Equals(other._someBRelatedStuff)
&& _someOtherBRelatedStuff.Equals(other._someOtherBRelatedStuff);
}
public override int GetHashCode()
{
unchecked
{
int hc = comparer.GetHashCode(this);
hc = (-1521134295)*hc + _someBRelatedStuff.GetHashCode();
hc = (-1521134295)*hc + _someOtherBRelatedStuff.GetHashCode();
return hc;
}
}
#endregion
}
来源:https://stackoverflow.com/questions/58167650/overriding-iequatablet-when-t-is-an-interface-and-hashcodes-are-different-betw