Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the behaviour of the '==' operator for a generic type value and the 'default' keyword?

Tags:

c#

Part 1 of the question: In the following code why does value == default compile fine but the other alternatives do not?

bool MyEqual<T>(T value)
{
    T value2 = default;
    if (value == value2)     // Error: Operator '==' cannot be applied to operands of type 'T' and 'T'
        return true;
    if (value == default(T)) // Error: Operator '==' cannot be applied to operands of type 'T' and 'T'
        return true;
    if (value == default)    // No error
        return true;
    return false;
}

Part 2 of the question: In the following code, why do the first three prints show false and the other three show true?

bool MyEqual<T>(T value)
{
    if (value == default)
        return true;
    return false;
}

Console.WriteLine($"{MyEqual<int>(0)}");                  // False
Console.WriteLine($"{MyEqual<int>(default)}");            // False
Console.WriteLine($"{MyEqual<int>(default(int))}");       // False

Console.WriteLine($"{MyEqual<string>(null)}");            // True
Console.WriteLine($"{MyEqual<string>(default)}");         // True
Console.WriteLine($"{MyEqual<string>(default(string))}"); // True

To sum it up: What is the behaviour of the expression value == default?

EDIT: Please do not mark it as a duplicate of this other question, because that one addresses a different case, value == default(T) and not value == default: What does default(object); do in C#? Also, my question is regarding the odd behaviour when using the '==' operator as I explain above.

like image 287
Jorge Galvão Avatar asked Jun 14 '19 16:06

Jorge Galvão


People also ask

Which operator is used to declare generic type in a class?

We use <T> to create a generic class, interface, and method. The T is replaced with the actual type when we use it.

What keyword allows you to put constraints on a generic type?

The where clause in a generic definition specifies constraints on the types that are used as arguments for type parameters in a generic type, method, delegate, or local function.

Which character is used for generic type?

The question mark ( ? ) wildcard character can be used to represent an unknown type using generic code. Wildcards can be used with parameters, fields, local variables, and return types.

What is the default keyword in C#?

You can use the default keyword in the following contexts: To specify the default case in the switch statement. As the default operator or literal to produce the default value of a type. As the default type constraint on a generic method override or explicit interface implementation.


1 Answers

In the context of == where the operand type is a generic parameter, value == default seems to emit equivalent IL to value == null, which always evaluates to false for a non-nullable value type operand.

Given:

static bool IsDefault<T>(T value) => value == default;
static bool IsNull<T>(T value) => value == null;

We get the IL:

.method private hidebysig static 
    bool IsDefault<T> (
        !!T 'value'
    ) cil managed 
{
    // Method begins at RVA 0x2050
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq
    IL_0009: ret
} // end of method C::IsDefault

.method private hidebysig static 
    bool IsNull<T> (
        !!T 'value'
    ) cil managed 
{
    // Method begins at RVA 0x205b
    // Code size 10 (0xa)
    .maxstack 8

    IL_0000: ldarg.0
    IL_0001: box !!T
    IL_0006: ldnull
    IL_0007: ceq
    IL_0009: ret
} // end of method C::IsNull

You could be forgiven for finding this surprising. It means, for example, that when T is bound to a non-nullable value type like int, the expression value == default evaluates to false for a value of 0. This contrasts with the inlined expression 0 == default, which evaluates to true.

Console.WriteLine(IsDefault<int>(0));     // False
Console.WriteLine(IsNull<int>(0));        // False
Console.WriteLine(IsDefault<int?>(null)); // True
Console.WriteLine(IsNull<int?>(null));    // True
Console.WriteLine(IsDefault<int?>(0));    // False
Console.WriteLine(IsNull<int?>(0));       // False

So, clearly, for a value of an unconstrained generic parameter type, the expressions value == default and value == default(T) are not equivalent. If legal, the latter would (presumably) evaluate to true if the value were null, false, or a "zeroed-out" value type (e.g., a value type where all constituent values are also defaults).

As to why value == default(T) does not compile, the answer is simple: the compiler does not know how to evaluate == for a type that is not known at compile time. If you were to add the constraint where T : class, then the compiler could at least perform a reference comparison. But as long as T could be a primitive type, custom value type, or reference type, the compiler doesn't know how to emit the comparison. The correct implementation for a given instantiation of T might be a built-in primitive comparison, an op_Equality overload, or a reference comparison. More importantly, it may not be a supported operator at all. It's that last possibility that really poses a problem.

While the C#/.NET engineers could have come up with a way to defer figuring out the correct comparison until runtime, that would also mean trading a compile-time error for a runtime exception in cases where the operator is simply not applicable, and I don't find that trade very appealing

like image 138
Mike Strobel Avatar answered Oct 21 '22 20:10

Mike Strobel