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.
My actual problem is in a LinkedList implementation, but the problem can be shown with this simpler example.
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")
. I would expect to get true
if I call Equals(new StringBuilder("StackOverflow")
on this GenericObject instance, but I get 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
. This is what I expected. But when comparing the GenericObject objects, its Equals() method (the third line) returns false
. Interestingly enough, the fourth compare method does return true
. I'd think the third and fourth comparison are actually doing the same thing.
I would have expected true
. Because in the Equals() method of the GenericObject class, both val开发者_运维知识库ue
and Value
are of type T
which in this case is a 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. And as I've shown, the StringBuilder's Equal() method does return true
.
I've even tried
public bool Equals(T value) {
return EqualityComparer<T>.Default.Equals(Value, value);
}
but that also returns false.
So, two questions here:
- Why doesn't the code return
true
? - How could I implement the
Equals
method so it does returntrue
?
As suggested in Marc Gravell's answer, the problem is with StringBuilder
Equals(object)
implementation that is different to the one in 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).
But, given that you are not using C# 4 (so no dynamic), you can try in this way:
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. In particular, Equals(StringBuilder) disagrees with Equals(object), even when the object is a StringBuilder.
All that EqualityComparer<T>
needs is a sane Equals(object) implementation. The interface (IEquatable<T>
) is optional. Unfortunately StringBuilder doesn't have this (at least, by comparison to Equals(StringBuilder), which your third test is using).
But in general, the advice is: use EqualityComparer<T>
; this supports:
- nullable-of-T with standard "lifted" rules
- IEquatable-of-T
- object.Equals
Line 3 with the generic object is not calling your custom written method. Instead, it is calling the base Object.Equals(object)
. To call your custom method, you need to pass in a T
not a GenericObject<T>
. Something like: go1.Equals(go2.Value)
As Eric Lippert says in answer to this question - Overload resolution is performed at compile time.
If you take a look at StringBuilder
's implementation you will notice it overloads Equals
and does not override it. This is basically the root of the problem as to why StringBuilder.Equals
does not work as you expected in your example.
Take the following 2 classes as example. Overloader
is analogous to StringBuilder
in the example as it overloads Equals
. Overrider
is very similar except it it overrides Equals
instead.
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:
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. However, Overrider
returns 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. Meaning that the compiler basically looks at your GenericObject<T>.Equals( T val )
like this:
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.
- If you own all the objects that will be wrapped in
GenericObject<T>
ensure they all at least overrideEquals
. - You could perform some reflection magic in your
GenericObject<T>.Equals(T val)
to manually perform late binding.
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.
Cheers :)
You can either implement IEquatable<T>
, or implement a comparer class that implements IEqualityComparer<T>
.
Make sure that value
you check for equality is immutable and is set only at initialization of the class.
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.
Once you'll properly implement IEquatable<T>
in your class, your questions will be solved.
Update:
Calling return EqualityComparer<T>.Default.Equals(Value, value);
would basically return same result since there is no IEqualityComparer<T>
implemented...
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
.
精彩评论