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.
We use <T> to create a generic class, interface, and method. The T is replaced with the actual type when we use it.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With