Version Used: 7.3
Steps to Reproduce:
```c#
public void Foo
if (value == default)
}
**Expected Behavior**: Compile error similar to `value == default(T)`
```c#
public void Foo<T>(T value){
if (value == default(T)) // <--- compile error as expected
}
Actual Behavior: compiles into a null check for all types including value types and leads to situations where when T is int and value=0, value == default evaluates to false.
StackOverflow question with more details: https://stackoverflow.com/q/56602309/
@gafter This looks like a bug to me. Can you confirm?
@agocke
The closest thing we have to a specification is https://github.com/dotnet/csharplang/blob/master/proposals/csharp-7.1/target-typed-default.md which says
The _default_ literal can be the operand of equality operators, as long as the other operand has a type. So
default == xandx == defaultare valid expressions, butdefault == defaultis illegal.
This suggests (but does not say) that the default expression is converted to the type of the other operand. This is not what is implemented.
The LDM decided (https://github.com/dotnet/csharplang/blob/master/meetings/2017/LDM-2017-05-31.md#default-in-operators):
Default in operators
defaultas an operand to unary or binary operators would sometimes work, and sometimes not, depending on whether there happens to be a best operator across all available predefined or user-defined ones for all types:
c# var a = default + default; // error var b = default - default; // ok var c = default * default; // ok var d = default / default; // errorThis feels arbitrary. But worse, it is actually a recipe for future compat disasters. Imagine we added a
-operator to, say, arrays in the future. Now the second line above would break, because theintoverload of the pre-defined-operator would no longer happen to be best.Conclusion
Don't allow
defaultas an operand to a unary or binary operator. We need to protect the ability to add new operator overloads in the future.
This is not what is implemented.
What the compiler does (I suspect) is perform overload resolution on operator == and the applicable method it finds is operator ==(object, object). There is no overload for operator == that would cause us to convert the right-hand-side to T, so that's not what we do.
Searching through my emails, I see there was an undocumented decision to permit == and != with a default literal, taking the type from the other side. Here are some snippets:
The LDM notes (May 31st) were that
defaultwould be disallowed as operand on all unary and binary operators, including==and!=.
I’d taken notes on that day (and updated the speclet) capturing thatdefaultwould still be allowed in==and!=. That seems to be from offline exchange with Mads following LDM meeting.But the implemented behavior is that
defaultis disallowed on==and!=, except if the other operand implement user-defined==or!=operator. See demo.My interpretation is that there is a bug in the LDM notes (
==and!=should be allowed) and there is a bug in the implementation too (==and!=should be allowed even when no user-defined operator exists).
I would like to fix the implementation bug in C# 7.3 (gated by language version).
This was followed by a flood of LDM agreement, and then
The rule would be “
defaultgets the type from the other side”. Then I think the tuple case just works too.
(0, 0) == defaultis(0, 0) == default((int, int))which is true.
Fixed in 16.4p2 and documented as a breaking change.
Thanks @avin-kavish for reporting this