Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why there is no Nullable<T>.Equals(T value) method? [closed]

Tags:

c#

.net

nullable

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, let's change a type of a variable d to decimal?:

decimal? d = 2;

This time the result will be True, True, False, True. The explanation of this situation is quite easy. Equals method is implemented as follows for 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 has a value and other parameter is not null then Decimal.Equals(object value) will be called. Decimal.Equals(object value) method works in this way, that if value parameter is not decimal then the result will be always false.

It seems to me that the current implementation is not intuitive and I wonder why Nullable<T> doesn't provide developers with generic version of Equals method e.g.:

public bool Equals(T other)
{
    if (!this.HasValue)
        return false;

    return this.value.Equals(other);
}

Was it done on purpose or is it an omission?

Comment 1:

A brief comment to be clear. I suggested that Nullable<T> should have two Equals methods i.e.: public override bool Equals(object other) and public bool Equals(T other)

like image 249
Michał Komorowski Avatar asked Sep 11 '13 07:09

Michał Komorowski


People also ask

Can a value type be null?

As you know, a value type cannot be assigned a null value. For example, int i = null will give you a compile time error. C# 2.0 introduced nullable types that allow you to assign null to value type variables. You can declare nullable types using Nullable<t> where T is a type.

How do you handle possible null return?

Warning: Possible NULL Reference Return This error is because the reference type was nullable by default, but because enabling nullable reference types, the type is non-nullable now. In order to fix this warning, the return type should be marked as nullable.

Can integer value be null in C#?

For example, in nullable of integer type you can store values from -2147483648 to 2147483647, or null value.


2 Answers

Instead of writing (decimal)2 you can write 2m (will use that in the following).

When you use the == operator, no boxing occurs. The C# compiler will choose the pre-defined overload (i.e. the overload defined in the C# Language Specification; this is not necessarily a real .NET method) of operator == which matches best.

There are overloads:

operator ==(int x, int y);
operator ==(decimal x, decimal y);

There are no "mixed" overloads like operator ==(decimal x, int y);. Because an implicit conversion exists from int to decimal, your literal 2 is converted to 2m implicitly when you use ==.

With Equals, in some situations boxing occurs. You don't need nullables for that. To give examples, all of 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 is not equal to "two" of type Decimal.

Only when method overloading leads to a conversion between int and decimal will the result be true. For example:

(2m).Equals(2);  // true

So while an extra overload of Equals could be added to Nullable<>, the behavior you describe is not really related to Nullable<>.

like image 179
Jeppe Stig Nielsen Avatar answered Nov 16 '22 04:11

Jeppe Stig Nielsen


Although I like these questions, they can only really be answered by those on the design team responsible for the type. An obvious workaround is to access the Value that is T and use the Equals of that.

My best guess is that it would probably force all T to be IEquatable<T>, in order to generically access Equals<T> on a given type. This would work for the core value types, but other .NET structs wouldn't necessarily implement that interface and enum types don't.

I suppose it could be done via type checking / casting, but this then comes a lot of legwork versus the caller simply doing: myNullable.GetValueOrDefault().Equals().

You can make an extension method to do this task, to get it to call the method you need to specify the generic argument explicitly (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've asked a question about why this call is needed.

Turns out the reason for having to force it down into the extension method is due to the compiler implementation only using extension methods if no suitable method exists, in this case Equals(object) is considered more suitable than Equals<T>(T) as the latter is an extension method. It's in the spec.

like image 1
Adam Houldsworth Avatar answered Nov 16 '22 02:11

Adam Houldsworth