覆盖IEquatable< T>当T是接口并且哈希码在派生类型之间不同时 [英] Overriding IEquatable<T> when T is an interface and hashcodes are different between derived types

查看:53
本文介绍了覆盖IEquatable< T>当T是接口并且哈希码在派生类型之间不同时的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有分别实现接口 I A B 类.

 公共接口I{int SomeInt {get;}布尔SomeBool {}浮动SomeFloat {get;}}公开课A:我{public int SomeInt {get;}公共布尔SomeBool {get;}公众持票人SomeFloat {get;}私有只读字符串_someARelatedStuff;//课程的其余部分...}B类:我{public int SomeInt {get;}公共布尔SomeBool {get;}公众持票人SomeFloat {get;}私有字符串readonly _someBRelatedStuff;私人双只读_someOtherBRelatedStuff;//课程的其余部分...} 

有时候我想测试 A B 之间的相等性(通常是在比较 A 列表和 B 时)基于它们的 I 属性( SomeInt SomeBool SomeFloat )的相等性,因此我实现了两者都使用 IEquatable< I> ,然后根据它们共享的 I 属性值比较它们.

问题在于我已经在A和B上都实现了 GetHashCode()的实现,该实现会产生不同的哈希值,因为我正在考虑其他成员.

B 不依赖 A ,因此我使用接口 I 进行比较,并且具有带有吸气剂的属性列表.

我在StackOverflow答案中阅读了 :

如果要实现一个类,则应始终确保两个相等的对象具有相同的哈希码.

这是否意味着每当一个类 A 想要实现接口 I ,并且我希望能够比较实现 I 的实例时,code>,我必须确保对 I 的所有实例以相同的方式计算哈希码,并且仅使用 I 属性?

当T是接口时,我确实不打算实现 IEquatable< T> ,但是我的选择是:

  1. 对基类使用常规继承-我宁愿在可能的情况下避免继承,并且如果B由于单一继承而需要从某个框架 C 类派生,则此解决方案将不起作用
  2. 使用 A B 上的方法在 A B 之间执行相等性检查-将创建代码重复
  3. I 中定义的 I 实例之间具有相等性检查方法-听起来像是最好的选择

我缺少任何选择吗?

解决方案

请考虑制作一个 IEqualityComparer<> 类以比较公共值.

我已将接口重命名为 ICommon 以提高可读性

 公共接口ICommon{int SomeInt {get;}布尔SomeBool {}浮动SomeFloat {get;}}公共类CommonComparer:IEqualityComparer< ICommon>{公共布尔等于(ICommon x,ICommon y){返回x.SomeInt.Equals(y.SomeInt)&&x.SomeBool.Equals(y.SomeBool)&&x.SomeFloat.Equals(y.SomeFloat);}public int GetHashCode(ICommon obj){未检查{int hc = -1817952719;hc =(-1521134295)* hc + obj.SomeInt.GetHashCode();hc =(-1521134295)* hc + obj.SomeBool.GetHashCode();hc =(-1521134295)* hc + obj.SomeFloat.GetHashCode();返回hc;}}} 

该程序可以区分两个列表中的相等项.

  class程序{静态void Main(string [] args){var listA = new List< A>{新的A(1001,true,1.001f,"A1"),新A(1002,true,1.002f,"A2"),新的A(1003,false,1.003f,"A1"),新的A(1004,false,1.004f,"A4")};var listB = new List< B>{新的B(1001,true,1.001f,"B1",2.5),新的B(1002,false,1.002f,"B2",2.8),新的B(1003,true,1.003f,"B3",2.9),新的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项目){Console.WriteLine($"{item.SomeInt,-8} {item.SomeBool,-6} {item.SomeFloat,-10}"));}//SomeInt Bool SomeFloat//1001是1.001//1004错误1.004}} 

和其余的代码定义

 公共类A:ICommon,IEquatable< A>{静态只读CommonComparer比较器=新的CommonComparer();public int SomeInt {get;}公共布尔SomeBool {get;}公众持票人SomeFloat {get;}私有只读字符串_someARelatedStuff;//课程的其余部分...公共A(ICommon其他,字符串someARelatedStuff):this(other.SomeInt,other.SomeBool,other.SomeFloat,someARelatedStuff){}公共A(int someInt,bool someBool,float someFloat,字符串someARelatedStuff){this.SomeInt = someInt;this.SomeBool = someBool;this.SomeFloat = someFloat;this._someARelatedStuff = someARelatedStuff;}公共替代字符串ToString()=>_someARelatedStuff;#region IEquatable成员公共重写bool等于(object obj){如果(obj是另一个){返回等于(其他);}返回false;}公共虚拟布尔等于(另一个){返回comparer.Equals(this,other)&&_someARelatedStuff.Equals(other._someARelatedStuff);}公共重写int GetHashCode(){未检查{int hc = comparer.GetHashCode(this);hc =(-1521134295)* hc + _someARelatedStuff.GetHashCode();返回hc;}}#endregion}公共类别B:ICommon,IEquatable B{静态只读CommonComparer比较器=新的CommonComparer();public int SomeInt {get;}公共布尔SomeBool {get;}公众持票人SomeFloat {get;}只读字符串_someBRelatedStuff;只读double _someOtherBRelatedStuff;//课程的其余部分...公共B(​​ICommon other,字符串someBRelatedStuff,double someOtherBRelatedStuff):this(other.SomeInt,other.SomeBool,other.SomeFloat,someBRelatedStuff,someOtherBRelatedStuff){}公共B(​​int someInt,bool someBool,float someFloat,字符串someBRelatedStuff,double someOtherBRelatedStuff){this.SomeInt = someInt;this.SomeBool = someBool;this.SomeFloat = someFloat;this._someBRelatedStuff = someBRelatedStuff;this._someOtherBRelatedStuff = someOtherBRelatedStuff;}公共替代字符串ToString()=>$"{_ someBRelatedStuff},{_someOtherBRelatedStuff.ToString(" g4)}" ;;#region IEquatable成员公共重写bool等于(object obj){如果(obj是B其他){返回等于(其他);}返回false;}公共虚拟布尔等于(B其他){返回comparer.Equals(this,other)&&_someBRelatedStuff.Equals(other._someBRelatedStuff)&&_someOtherBRelatedStuff.Equals(other._someOtherBRelatedStuff);}公共重写int GetHashCode(){未检查{int hc = comparer.GetHashCode(this);hc =(-1521134295)* hc + _someBRelatedStuff.GetHashCode();hc =(-1521134295)* hc + _someOtherBRelatedStuff.GetHashCode();返回hc;}}#endregion} 

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:

  1. 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 C class because of single inheritance
  2. Implement equality checks between A and B with a method on either A or B - will create code duplication
  3. Have an equality check method between I instances defined in I - sounds like the best option

Are there any options that I'm missing?

解决方案

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
}

这篇关于覆盖IEquatable&lt; T&gt;当T是接口并且哈希码在派生类型之间不同时的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆