【问题标题】:How to determine if two generic type values are equal?如何确定两个泛型类型值是否相等?
【发布时间】:2011-01-09 13:41:21
【问题描述】:

更新* 很抱歉......我的示例代码包含一个错误,导致很多我不明白的答案。 而不是

Console.WriteLine("3. this.Equals   " + (go1.Equals(go2)));

我想写

Console.WriteLine("3. this.Equals   " + (go1.Equals(sb2)));

我试图弄清楚如何才能成功确定两个泛型类型值是否彼此相等。基于 Mark Byers 对 this question 的回答,我想我可以使用 value.Equals(),其中 value 是泛型类型。 我的实际问题是在 LinkedList 实现中,但可以通过这个更简单的示例来说明问题。

class GenericOjbect<T> {
    public T Value { get; private set; }
    public GenericOjbect(T value) {
        Value = value;
    }
    public bool Equals(T value) {
        return (Value.Equals(value));
    }
}

现在我定义一个包含new StringBuilder("StackOverflow")GenericObject&lt;StringBuilder&gt; 实例。如果我在这个 GenericObject 实例上调用Equals(new StringBuilder("StackOverflow"),我希望得到true,但我得到false

显示此的示例程序:

using System;
using System.Text;

class Program
{
    static void Main()
    {
        var sb1 = new StringBuilder("StackOverflow");
        var sb2 = new StringBuilder("StackOverflow");

        Console.WriteLine("StringBuilder compare");
        Console.WriteLine("1. ==            " + (sb1 == sb2));
        Console.WriteLine("2. Object.Equals " + (Object.Equals(sb1, sb2)));
        Console.WriteLine("3. this.Equals   " + (sb1.Equals(sb2)));

        var go1 = new GenericOjbect<StringBuilder>(sb1);
        var go2 = new GenericOjbect<StringBuilder>(sb2);

        Console.WriteLine("\nGenericObject compare");
        Console.WriteLine("1. ==            " + (go1 == go2));
        Console.WriteLine("2. Object.Equals " + (Object.Equals(go1, sb2)));
        Console.WriteLine("3. this.Equals   " + (go1.Equals(sb2)));
        Console.WriteLine("4. Value.Equals  " + (go1.Value.Equals(sb2.Value)));
    }
}

对于比较两个StringBuilder对象的三种方法,只有StringBuilder.Equals实例方法(第三行)返回true。这是我所期望的。但是在比较 GenericObject 对象时,它的 Equals() 方法(第三行)返回 false。有趣的是,第四个比较方法确实返回true。我认为第三和第四个比较实际上是在做同样的事情。

我会期待true。因为在 GenericObject 类的 Equals() 方法中,valueValue 都是 T 类型,在这种情况下是 StringBuilder。根据 Mark Byers 在 this question 中的回答,我预计 Value.Equals() 方法将使用 StringBuilder 的 Equals() 方法。正如我所展示的,StringBuilder 的 Equal() 方法确实返回 true

我试过了

public bool Equals(T value) {
    return EqualityComparer<T>.Default.Equals(Value, value);
}

但这也返回 false。

所以,这里有两个问题:

  1. 为什么代码没有返回true
  2. 如何实现Equals 方法,使其确实返回true

【问题讨论】:

    标签: c# generics comparison


    【解决方案1】:

    正如Marc Gravell's answer 中所建议的,问题在于StringBuilder Equals(object) 的实现与Equals(StringBuilder) 中的实现不同。

    然后,您可以忽略该问题,因为您的代码将与任何其他一致实现的类一起使用,或者您可以使用动态来解决问题(再次按照 Mark Gravell 的建议)。

    但是,鉴于你没有使用 C# 4(所以没有动态),你可以这样尝试:

    public bool Equals(T value)
    {
       // uses Reflection to check if a Type-specific `Equals` exists...
       var specificEquals = typeof(T).GetMethod("Equals", new Type[] { typeof(T) });
       if (specificEquals != null &&
           specificEquals.ReturnType == typeof(bool))
       {
           return (bool)specificEquals.Invoke(this.Value, new object[]{value});
       }
       return this.Value.Equals(value);
    }
    

    【讨论】:

    • 谢谢!这确实有效。如果我理解正确,对于另一种类型,我原来的 Equals 方法应该可以工作。我尝试过使用 new Exception("StackOverflow"),但我的原始代码再次没有返回 true。您能否建议 .NET Framework 中的另一个引用类型类,我可以简单地检查我的 Equals 实现?
    • 顺便说一句,为什么一个类必须实现对 Equals(Object) 的覆盖?一个类是否必须同时定义 Equals(type of class itself)Equals(Object) 才能被视为“连贯实现”?
    • 如果您的类被强制转换为对象或在泛型方法/类中使用(如您的情况),则调用默认的Equals(object)。所以如果你实现你的自定义Equals(type of class itself) 为什么你不应该覆盖默认值呢?此外,它是一行代码:return this.Equals(obj as TypeOftheClass);。这更符合 IMO。
    • 在我尝试之前我无法知道 .NET 类 Equals 的实现,但无论如何,您可以轻松编写自己的类来实现 2 个 Equals 方法并对其进行测试:)
    • 我想知道.NET System.Collections.Generic.LinkedList 如何执行它的 Find() 方法。因为那个版本在添加(sb1)之后就可以找到(sb2)了。
    【解决方案2】:

    您的代码看起来不错。这里的问题是 StringBuilder 有一组令人困惑的相互矛盾的 Equals。特别是,Equals(StringBuilder) 不同意 Equals(object),即使对象 StringBuilder。

    所有EqualityComparer&lt;T&gt; 需要 是一个理智的 Equals(object) 实现。接口 (IEquatable&lt;T&gt;) 是可选的。不幸的是,StringBuilder 没有这个(至少与您的第三个测试使用的 Equals(StringBuilder) 相比)。

    但总的来说,建议是:使用EqualityComparer&lt;T&gt;;这支持:

    • 具有标准“解除”规则的 nullable-of-T
    • IEquatable-of-T
    • object.Equals

    【讨论】:

    • +1:不知道 StringBuilder Equals 集。这一点,再加上他正在实施 Equals(T v) 而不是 Equals(GenericObject&lt;T&gt; v) 的事实是问题所在。
    • 如果我理解正确,您是说我的 Equals 方法中的 return (value.Equals(Value)) 调用了 StringBuilder 的 Equals(object) 方法?如果是这样,为什么?是不是因为像 T 这样的泛型类型实际上被认为是一个对象?
    • @comecme 完全正确;泛型对 all T 使用相同的方法 - 所以除非你有泛型类型约束,否则它只会发现对象上的方法。注意dynamic 可能有用;它对每个单独的 T 使用在运行时可用的最合适的方法。
    • 不幸的是 StringBuilder 是这里的例子;它增加了混乱:)
    • @comecme:你有第三个问题:你拼错了你的班级名称:GenericOjbect 而不是GenericObject ;)
    【解决方案3】:

    带有通用对象的第 3 行没有调用您的自定义编写方法。相反,它调用基础Object.Equals(object)。要调用您的自定义方法,您需要传入 T 而不是 GenericObject&lt;T&gt;。比如:go1.Equals(go2.Value)

    【讨论】:

    • 第 3 行调用 Object.Equals(object) 是对的。但是,使用go1.Equals(go2.Value) 并没有任何区别。另外,我想在这里写的是 GenericObject 上的 Equals 方法,它能够比较两个 GenericObject 实例。
    • @comecme: stringbuilder 有矛盾的 equals 方法。阅读 Marc Gravell 的回答;)
    • 如果我将我的 Equals 方法重命名为 MyEquals 它不起作用。第三行现在确实调用了我的 MyEquals 方法。但它的return (value.Equals(Value) 仍然返回false
    • 好的,我现在明白了。我不是故意使用go1.Equals(go2),而是go1.Equals(sb2)。所以现在我传入T,然后Equals 被调用。但是,它仍然返回false。我已经更新了我的问题。
    【解决方案4】:

    正如 Eric Lippert 在回答 question 时所说的那样 - 在编译时执行重载解决方案。

    如果您查看StringBuilder 的实现,您会注意到它重载了Equals,并且没有覆盖它。这基本上是问题的根源,为什么 StringBuilder.Equals 不能按您的示例中的预期工作。

    以以下 2 个类为例。 Overloader 类似于示例中的 StringBuilder,因为它重载了 EqualsOverrider 非常相似,只是它覆盖了Equals

    public class Overloader
    {
      public string Str {get;private set;}
      public Overloader (string str) {Str = str;}
    
      public bool Equals( Overloader str )
      {
        return this.Str.Equals( str );
      }
    }
    
    public class Overrider
    {
      public string Str {get;private set;}
      public Overrider (string str) {Str = str;}
    
      public override bool Equals( object obj )
      {
        if ( obj is Overrider )
        {
          return this.Str.Equals( (obj as Overrider).Str );
        }
        return base.Equals( obj );
      }
    }
    

    在我的示例中,我稍微修改了您的 GenericObject&lt;T&gt; 类:

    class GenericOjbect<T>
    {
      public T Value {get;private set;}
      public GenericOjbect( T val ) {Value = val;}
    
      public bool Equals( T val )
      {
        return Value.Equals( val );
      }
    
      public override bool Equals( object obj )
      {
        if ( obj is T )
        {
          return this.Equals( ( T )obj );
        }
        if (obj != null && obj is GenericOjbect<T> )
        {
          return this.Equals( ( obj as GenericOjbect<T> ).Value );
        }
        return base.Equals( obj );
      }
    }
    

    在这个示例程序中,您将看到Overloader(或Stringbuilder)将返回false。但是,Overrider 返回 true。

    class Program
    {
      static void Main( string[] args )
      {
        var goOverloader1 = new GenericOjbect<Overloader>( new Overloader( "StackOverflow" ) );
        var goOverloader2 = new GenericOjbect<Overloader>( new Overloader( "StackOverflow" ) );
    
        var goOverrider1 = new GenericOjbect<Overrider>( new Overrider( "StackOverflow" ) );
        var goOverrider2 = new GenericOjbect<Overrider>( new Overrider( "StackOverflow" ) );
    
        Console.WriteLine( "Overrider  : {0}", goOverloader1.Equals( goOverloader2 ) ); //False
        Console.WriteLine( "Overloader : {0}", goOverrider1.Equals( goOverrider2 ) ); //True
      }
    }
    

    再次引用 Eric Lippert - 在编译时执行重载解析。这意味着编译器基本上会像这样查看您的GenericObject&lt;T&gt;.Equals( T val )

    public bool Equals( T val )
    {
      return Value.Equals( (Object) val );
    }
    

    回答您的问题如何确定两个泛型类型值是否相等?。你可以做两件事。

    1. 如果您拥有所有将被包裹在GenericObject&lt;T&gt; 中的对象,请确保它们都至少覆盖Equals
    2. 您可以在 GenericObject&lt;T&gt;.Equals(T val) 中执行一些反射魔法来手动执行后期绑定。

    【讨论】:

    • 太糟糕了,我只能将一个答案标记为已接受的答案。您确实很好地解释了这个问题,但是 digEmAl 给了我一个代码示例,实际上 will 在 StringBuilders 上返回 true。如果我必须选择,而且我必须这样做,我更倾向于 digEmAl 的 anwser。
    • @comecme - 不用担心。我花了一段时间才明白发生了什么,但我没有看到 digEmAll 为您提供了工作代码。
    【解决方案5】:

    首先比较 typeof() 的输出,因此您要确保比较相同类型的对象,然后在 X 类上编写一个 Equals 方法,该方法采用 X 类的另一个实例,并比较所有属性......一旦你找到不同的东西,返回 false,否则继续直到返回 true。

    干杯:)

    【讨论】:

      【解决方案6】:

      您可以实现IEquatable&lt;T&gt;,也可以实现一个实现IEqualityComparer&lt;T&gt; 的比较器类。

      确保您检查相等性的value 是不可变的,并且仅在类初始化时设置。

      另一个考虑是实现IComparer&lt;T&gt;,当您实现这个时,您不必担心哈希码,因此也可以为可变类型/字段实现。

      一旦你在课堂上properly实现IEquatable&lt;T&gt;,你的问题就会得到解决。

      更新: 调用return EqualityComparer&lt;T&gt;.Default.Equals(Value, value); 基本上会返回相同的结果,因为没有实现IEqualityComparer&lt;T&gt;...

      【讨论】:

      • 我不明白你的意思。 IEquatable 接口只包含一个 Equals 方法。我遇到的问题是 如何 确定相等性。那么实现 IEquatable 对我有什么帮助呢?我仍然会将IEquatable&lt;T&gt;.Equals 实现为return (value.Equals(Value)); 并得到相同(不正确)的结果。
      • 为什么value 必须是不可变的?
      • 问题将是如果您使用使用 GetHashCode 的字典、哈希表等,当您实现 IEquatable 时,您还应该覆盖 GetHashCode,如果 GetHashCode 基于可变字段,它将导致意想不到的结果。
      • 我强调了properly这个词,转到链接阅读更多内容。
      • 虽然这个解释真的很好,但与问题无关。
      【解决方案7】:

      详细说明Gideon's answer(请点赞他的,不是我的):你定义的方法有签名

      bool GenericOjbect<T>::Equals( T )
      

      当您的代码正在调用时

      bool GenericOjbect<T>::Equals( GenericOjbect<T> )
      

      Object继承(而不是覆盖)。

      【讨论】:

      • 终于看到你在说什么了。我的测试程序类使用了go1.Equals(go2),我的意思是go1.Equals(sb2)。所以我确实想要Equals(T value)。我的课表明了我的意图,我的测试程序引起了很多混乱。我已经更新了我的问题。
      猜你喜欢
      • 1970-01-01
      • 2011-06-11
      • 2011-08-29
      • 2011-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-04
      相关资源
      最近更新 更多