简体   繁体   English

如何确定两个泛型类型值是否相等?

[英]How to determine if two generic type values are equal?

Update* I am so sorry... my sample code contained an error which resulted in a lot of answers I didn't understand. 更新*我很抱歉......我的示例代码包含一个错误,导致了许多我不理解的答案。 In stead of 代替

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

I meant to write 我打算写

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

I'm trying to figure out how I can successfully determine if two generic type values are equal to each other. 我试图找出如何成功确定两个泛型类型值是否相等。 Based on Mark Byers' answer on this question I would think I can just use value.Equals() where value is a generic type. 根据Mark Byers对这个问题的回答,我认为我可以使用value.Equals() ,其中value是泛型类型。 My actual problem is in a LinkedList implementation, but the problem can be shown with this simpler example. 我的实际问题是在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));
    }
}

Now I define an instance of GenericObject<StringBuilder> containing new StringBuilder("StackOverflow") . 现在我定义一个包含new StringBuilder("StackOverflow")GenericObject<StringBuilder>实例。 I would expect to get true if I call Equals(new StringBuilder("StackOverflow") on this GenericObject instance, but I get false . 如果我在这个GenericObject实例上调用Equals(new StringBuilder("StackOverflow") ,我希望得到true ,但我得到了false

A sample program showing this: 一个示例程序显示:

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

For the three methods of comparing two StringBuilder objects, only the StringBuilder.Equals instance method (the third line) returns true . 对于比较两个StringBuilder对象的三种方法,只有StringBuilder.Equals实例方法(第三行)返回true This is what I expected. 这是我的预期。 But when comparing the GenericObject objects, its Equals() method (the third line) returns false . 但是在比较GenericObject对象时,它的Equals()方法(第三行)返回false Interestingly enough, the fourth compare method does return true . 有趣的是,第四种比较方法确实返回了true I'd think the third and fourth comparison are actually doing the same thing. 我认为第三和第四次比较实际上是在做同样的事情。

I would have expected true . 我原以为是true Because in the Equals() method of the GenericObject class, both value and Value are of type T which in this case is a StringBuilder . 因为在GenericObject类的Equals()方法中, valueValue都是T类型,在本例中是StringBuilder Based on Mark Byers' answer in this question , I would've expected the Value.Equals() method to be using the StringBuilder's Equals() method. 基于Mark Byers在这个问题中的答案,我希望Value.Equals()方法使用StringBuilder的Equals()方法。 And as I've shown, the StringBuilder's Equal() method does return true . 正如我所示,StringBuilder的Equal()方法确实返回true

I've even tried 我甚至试过了

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

but that also returns false. 但这也会返回false。

So, two questions here: 那么,这里有两个问题:

  1. Why doesn't the code return true ? 为什么代码不能返回true
  2. How could I implement the Equals method so it does return true ? 我怎么能实现Equals方法,所以它确实返回true

As suggested in Marc Gravell's answer , the problem is with StringBuilder Equals(object) implementation that is different to the one in Equals(StringBuilder) . 作为建议马克Gravell的答案 ,问题是用StringBuilder Equals(object)的实现,是一个不同Equals(StringBuilder)

Then, you can ignore the problem because your code will work with any other coherently-implemented classes, or you can use dynamic to fix the problem (again as suggested by Mark Gravell). 然后,您可以忽略该问题,因为您的代码将与任何其他一致实现的类一起使用,或者您可以使用动态来修复问题(再次由Mark Gravell建议)。

But, given that you are not using C# 4 (so no dynamic), you can try in this way: 但是,鉴于您没有使用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);
}

Your code looks fine. 你的代码看起来很好。 The problem here is that StringBuilder has a confusing set of Equals that are contradictory. 这里的问题是StringBuilder有一组令人困惑的Equals是矛盾的。 In particular, Equals(StringBuilder) disagrees with Equals(object), even when the object is a StringBuilder. 特别是,即使对象 StringBuilder,Equals(StringBuilder)也不同意Equals(object)。

All that EqualityComparer<T> needs is a sane Equals(object) implementation. EqualityComparer<T> 需要的只是一个理智的Equals(对象)实现。 The interface ( IEquatable<T> ) is optional. 接口( IEquatable<T> )是可选的。 Unfortunately StringBuilder doesn't have this (at least, by comparison to Equals(StringBuilder), which your third test is using). 不幸的是,StringBuilder没有这个(至少,与你的第三个测试正在使用的Equals(StringBuilder)相比)。

But in general, the advice is: use EqualityComparer<T> ; 但总的来说,建议是:使用EqualityComparer<T> ; this supports: 这支持:

  • nullable-of-T with standard "lifted" rules 可以通过标准的“解除”规则获得可空的
  • IEquatable-of-T IEquatable-的-T
  • object.Equals 的Object.Equals

Line 3 with the generic object is not calling your custom written method. 第3行与通用对象不调用自定义写入方法。 Instead, it is calling the base Object.Equals(object) . 相反,它调用基础Object.Equals(object) To call your custom method, you need to pass in a T not a GenericObject<T> . 要调用自定义方法,需要传入T而不是GenericObject<T> Something like: go1.Equals(go2.Value) 类似的东西: go1.Equals(go2.Value)

As Eric Lippert says in answer to this question - Overload resolution is performed at compile time. 正如Eric Lippert在回答这个问题时所说的那样 - 在编译时执行重载解析。

If you take a look at StringBuilder 's implementation you will notice it overloads Equals and does not override it. 如果你看一下StringBuilder的实现,你会注意到它重载Equals并且不会覆盖它。 This is basically the root of the problem as to why StringBuilder.Equals does not work as you expected in your example. 这基本上是问题的根源,为什么StringBuilder.Equals不能像您在示例中所期望的那样工作。

Take the following 2 classes as example. 以下面的两个班级为例。 Overloader is analogous to StringBuilder in the example as it overloads Equals . Overloader类似于示例中的StringBuilder ,因为它重载了Equals Overrider is very similar except it it overrides Equals instead. Overrider非常相似,只是它覆盖了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 );
  }
}

I have slightly modified your GenericObject<T> class in my example: 我在我的示例中略微修改了GenericObject<T>类:

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

In this sample program you will see that Overloader (or Stringbuilder for that matter) will return false. 在此示例程序中,您将看到Overloader (或Stringbuilder )将返回false。 However, Overrider returns true. 但是, 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
  }
}

Referencing Eric Lippert again - Overload resolution is performed at compile time. 再次引用Eric Lippert - 在编译时执行重载分辨率。 Meaning that the compiler basically looks at your GenericObject<T>.Equals( T val ) like this: 这意味着编译器基本上会像这样查看GenericObject<T>.Equals( T val )

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

To anwser your question How to determine if two generic type values are equal? 回答你的问题如何确定两个泛型类型值是否相等? . There's two things you possibly could do. 你可以做两件事。

  1. If you own all the objects that will be wrapped in GenericObject<T> ensure they all at least override Equals . 如果您拥有将包装在GenericObject<T> 所有对象,请确保它们至少重写Equals
  2. You could perform some reflection magic in your GenericObject<T>.Equals(T val) to manually perform late binding. 您可以在GenericObject<T>.Equals(T val)执行一些反射魔法来手动执行后期绑定。

You can either implement IEquatable<T> , or implement a comparer class that implements IEqualityComparer<T> . 您可以实现IEquatable<T> ,也可以实现实现IEqualityComparer<T>的比较器类。

Make sure that value you check for equality is immutable and is set only at initialization of the class. 确保检查相等性的value是不可变的,并且仅在类的初始化时设置。

Another consideration would be to implement IComparer<T> , when you implement this one, you don't have to worry about the hash-code, and thus, can be implemented for mutable types/fields as well. 另一个考虑因素是实现IComparer<T> ,当你实现这个时,你不必担心哈希码,因此,也可以为可变类型/字段实现。

Once you'll properly implement IEquatable<T> in your class, your questions will be solved. 一旦你在课堂上正确实现IEquatable<T> ,你的问题就会得到解决。

Update: Calling return EqualityComparer<T>.Default.Equals(Value, value); 更新:调用return EqualityComparer<T>.Default.Equals(Value, value); would basically return same result since there is no IEqualityComparer<T> implemented... 基本上会返回相同的结果,因为没有实现IEqualityComparer<T> ...

Compare the output of typeof() first, so you make sure you are comparing the same type of objects, then write an Equals method on X class which takes another instance of X class, and compare all properties... once you find something different, return false, else keep going till you return true. 首先比较typeof()的输出,所以你要确保你比较相同类型的对象,然后在X类上编写一个Equals方法,它接受另一个X类实例,并比较所有属性......一旦找到不同的东西,返回false,否则继续,直到你返回true。

Cheers :) 干杯:)

To elaborate on Gideon's answer (please upvote his, not mine): the method you defined has signature 详细说明基甸的答案 (请提出他的,而不是我的):你定义的方法有签名

bool GenericOjbect<T>::Equals( T )

While your code is calling 你的代码正在调用

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

which is inherited (not overridden) from Object . Object继承(未覆盖)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM