Description
openedon Jan 18, 2018
Currently the value is null
pattern does not compile if value
is an unconstrained generic type:
public static void M<T>(T value)
{
if (value is null) { } //error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type.
}
This proposal is about removing this restriction.
Motivation
The main benefit would be, that this brings value is null
more in line with value == null
. Both expressions are almost interchangeable with two exceptions:
value == null
calls a user-defined equality operator if present, whileis null
does not.value == null
is permitted ifvalue
is unconstrained generic, whileis null
is not allowed.
The second difference can be removed. This would also bring is null
more in line with VBs Is Nothing
which behaves as == null
.
This language change would benefit code fix authors that deal with this kind of expressions and have to special case unconstrained generics (Recent examples are dotnet/roslyn#24237 and dotnet/roslyn#24173). It would also benefit users as it removes an artificial difference between otherwise (almost) identical constructs.
Proposed behavior for value is null
value is null
would have to behave as value == null
(see also C# spec links below).
Impact / Breaking changes
It is proposed to remove an existing compile time error. So this isn't breaking anything. Existing diagnostic analyzers are affected because CS0403 would no longer be reported (which in turn might cause unit tests to fail).
Some more background
The following snippets show the differences between is null
, ==null
and VBs Is Nothing
in respect to generic type parameter constraints.
public class C {
//unconstrained
public bool UnconstrainedIs<T>(T v) =>
v is null; //error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
public bool UnconstrainedNull<T>(T v) =>
v == null; // Fine
//class
public bool RefconstraintIs<T>(T v) where T:class =>
v is null; //Fine
public bool RefconstraintNull<T>(T v) where T:class =>
v == null; //Fine
//struct
public bool ValueconstraintIs<T>(T v) where T:struct =>
v is null; //error CS0403: Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.
public bool ValueconstraintNull<T>(T v) where T:struct =>
v == null; //error CS0019: Operator '==' cannot be applied to operands of type 'T' and '<null>'
public bool ValueconstraintRefEquals<T>(T v) where T:struct =>
ReferenceEquals(v, null); // Fine
}
Public Class C
Public Sub UnconstrainedIs(Of T)(v As T)
Dim flag As Boolean = v Is Nothing
End Sub
Public Sub RefconstraintIs(Of T As Class)(v As T)
Dim flag As Boolean = v Is Nothing
End Sub
Public Sub ValueconstraintIs(Of T As Structure)(v As T)
Dim flag As Boolean = v Is Nothing ' error BC30020: 'Is' operator does not accept operands of type 'T'. Operands must be reference or nullable types.
End Sub
Public Sub ValueconstraintReferenceEquals(Of T As Structure)(v As T)
Dim flag As Boolean = ReferenceEquals(v, Nothing)
End Sub
End Class
There is only one case where the three comparisons substantial differ and this proposal is about removing this difference.
C# Spec references
The Reference type equality operators section from the spec about == null
One operand is a value of type T where T is a type_parameter and the other operand is the literal null. Furthermore T does not have the value type constraint.
explicit allows T to be unconstrained. The section The is operator does not mention the is null
case. The spec needs to adopt this part from the Reference type equality operators section:
The x == null construct is permitted even though T could represent a value type, and the result is simply defined to be false when T is a value type.
Two more quotes about the difference between user defined comparison:
For an operation of the form x == y or x != y, if any applicable operator == or operator != exists, the operator overload resolution (Binary operator overload resolution) rules will select that operator instead of the predefined reference type equality operator.
Note that user defined conversions, are not considered by theis
operator.
Update: Oops! I got that quote wrong. It is about conversions and not comparison.