【问题标题】:Overriding IEquatable<T> when T is an interface and hashcodes are different between derived types当 T 是接口并且派生类型之间的哈希码不同时覆盖 IEquatable<T>
【发布时间】:2019-09-30 12:10:12
【问题描述】:

我有AB 类都实现了接口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...
}

有时我想根据I 属性的相等性(SomeInt、@987654333 @, SomeFloat),所以我在两者上都实现了IEquatable&lt;I&gt;,并根据它们共享的I 属性值比较它们。

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

B 不依赖于A,所以我使用接口I 来比较它们,它有一个带有getter 的属性列表。

我读到in a StackOverflow answer

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

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

当 T 是一个接口时,我确实觉得我不打算实现 IEquatable&lt;T&gt;,但我的替代方案是:

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

有没有我遗漏的选项?

【问题讨论】:

  • 这取决于意图以及在该上下文中两个对象之间的相等意味着什么。
  • 如果 A 和 B 不同(具有不同的属性),那么我不明白您为什么会尝试比较它们是否相等。 A 应该与其他 A 进行比较,B 应该与其他 B 进行比较。您的问题似乎是如何将苹果(A 类)与橙子(B 类)进行比较,它们都实现了接口(水果)。
  • 您可以为您的接口编写IEqualityComparer&lt;T&gt; 的实现,并使用它来比较实例。这类似于您问题中的选项(3)。 (这通常与接受 IEqualityComparer 的集合类一起使用,以覆盖集合元素类型的正常相等比较。)
  • @MatthewWatson 这听起来比我想象的更好。谢谢。
  • @RyanWilson @EhsanSajjad 正如我在问题中所说的那样 - 我想比较 A 的列表和 B 的列表,以确定它们的 I 派生属性是否相等。 IEquatable.

标签: c# .net interface gethashcode iequatable


【解决方案1】:

考虑创建一个IEqualityComparer&lt;&gt; 类来比较常用值。

为了便于阅读,我已将界面重命名为ICommon

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

并且程序可以区分两个列表中的相等项。

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

    }
}

以及其余的代码定义

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
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-03-10
    • 1970-01-01
    • 2016-11-05
    • 1970-01-01
    • 1970-01-01
    • 2018-09-21
    • 2010-10-22
    • 1970-01-01
    相关资源
    最近更新 更多