Why is there no Nullable <T> .Equals (T value) method?
Let's start with a very simple piece of code:
decimal d = 2; Console.WriteLine("d == 2 = {0}", d == 2); Console.WriteLine("d == (decimal)2 = {0}", d == (decimal)2); Console.WriteLine("d.Equals(2) = {0}", d.Equals(2)); Console.WriteLine("d.Equals((decimal)2) = {0}", d.Equals((decimal)2)); The result is 4xtrue. Now, change the type of d to decimal ?:
decimal? d = 2; This time the result will be True, True, False, True. Explaining this situation is fairly easy. The Equals method is implemented as follows for the Nullable <T> Type:
public override bool Equals(object other) { if (!this.HasValue) { return (other == null); } if (other == null) { return false; } return this.value.Equals(other); } If this value matters and the other parameter is not zero, then Decimal.Equals (object value) will be called. Decimal.Equals (object value) works in such a way that if the value parameter is not decimal, then the result will always be false.
It seems to me that the current implementation is not intuitive, and I wonder why Nullable <T> does not provide developers with a common version of the Equals method, for example:
public bool Equals(T other) { if (!this.HasValue) return false; return this.value.Equals(other); } Was this done on purpose or is this an omission?
Comment 1:
A brief comment should be clear. I suggested that Nullable <T> should have two Equals methods ie: public override bool Equals (object other) and public bool Equals (T other)
Instead of writing (decimal)2 you can write 2m (will use this in the following).
When you use the == operator, no boxing occurs. The C # compiler will choose the predefined overload (i.e., the Overload defined in the C # Language Specification, this is not necessarily the real .NET method) operator == , which works best.
There are overloads:
operator ==(int x, int y); operator ==(decimal x, decimal y); There are no "mixed" overloads such as operator ==(decimal x, int y); . Since implicit conversion exists from int to decimal , your literal 2 implicitly converted to 2m when you use == .
With Equals , boxing happens in some situations. You do not need nullables to do this. To give examples, all these calls:
object.Equals(2, 2m); object.Equals(2m, 2); ((object)2).Equals(2m); ((object)2m).Equals(2); (2).Equals((object)2m); (2m).Equals((object)2); (2).Equals(2m); return false ! Two of type Int32 not equal to two of type decimal .
Only when method overloading converts between int and decimal will the result be true . For example:
(2m).Equals(2); // true Thus, although an additional overload of Equals can be added to Nullable<> , the behavior you describe is not really related to Nullable<> .
Despite the fact that I like these questions, only those who are responsible for design can answer them. The obvious workaround is to access Value , which is T , and use Equals for this.
My best guess is that it will probably force all T be IEquatable<T> in order to access Equals<T> for a given type. This will work for basic value types, but other .NET structures will not necessarily implement this interface, and enum types will not.
I suppose this can be done by type checking / casting, but in this case there are a lot of problems with the handlers, and the caller just does: myNullable.GetValueOrDefault().Equals() .
You can make an extension method to perform this task to force it to call the method necessary to explicitly determine the general argument (otherwise the compiler calls Equals(object) :
class Program { static void Main(string[] args) { double? d = null; Console.WriteLine(d.Equals<double>(0.0)); d = 0.0; Console.WriteLine(d.Equals<double>(0.0)); Console.Read(); } } public static class NullableExtensions { public static bool Equals<T>(this T? left, T right) where T : struct, IEquatable<T> { if (!left.HasValue) return false; return right.Equals(left.Value); } } I asked why this call is necessary.
It turns out that the reason for the forced use of this extension method is related to the implementation of the compiler using extension methods only, if the appropriate method does not exist, in this case Equals(object) is considered more suitable than Equals<T>(T) , since the latter is an extension method. This is in the specification.