Skip to content

Proposal: Permit 'value is null' if 'value' is unconstrained generic #1261

Closed

Description

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, while is null does not.
  • value == null is permitted if value is unconstrained generic, while is 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
}

sharplab.io

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

shaprlab.io

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 the is operator.

Update: Oops! I got that quote wrong. It is about conversions and not comparison.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions