Home > Mobile >  Nullability and generics in .NET 6
Nullability and generics in .NET 6

Time:07-09

Let's say we have some generic method with 2 parameters:

private void TestConstraints<TKey>(TKey key, TKey? nullableKey)
    where TKey : notnull { }

That's perfectly fine from .NET6 perspective, where not nullable reference types were added. But using attempt is failing with reason CS8714 type can't be used as parameter:

private void TestMethod()
{
    var id = 5;
    int? nullableId = null;
    TestConstraints(id, nullableId);
}

Warning CS8714 The type 'int?' cannot be used as type parameter 'TKey' in the generic type or method 'TestClass.TestConstraints(TKey, TKey?)'. Nullability of type argument 'int?' doesn't match 'notnull' constraint.

Specifying type explicitly as int does not help.

Could someone clarify if I could operate this way (defining input parameters as TKey?) or not, and where in documentation it is stated?

Compiler version:

Microsoft (R) Build Engine version 17.2.0 41abc5629

CodePudding user response:

For unconstrained (neither to struct nor to class) generic type parameter T - T? is handled differently for value types and reference types. From the docs:

  • If the type argument for T is a reference type, T? references the corresponding nullable reference type. For example, if T is a string, then T? is a string?.
  • If the type argument for T is a value type, T? references the same value type, T. For example, if T is an int, the T? is also an int.
  • If the type argument for T is a nullable reference type, T? references that same nullable reference type. For example, if T is a string?, then T? is also a string?.
  • If the type argument for T is a nullable value type, T? references that same nullable value type. For example, if T is a int?, then T? is also a int?.

This happens partially due to the differences in how nullable reference and nullable value types are handled, cause nullable value types are actually separate types - Nullable<T>.

For TestConstraints let's imagine that T is int then according to the rules the signature becomes TestConstraints<int>(int key, int nullableKey) which obviously will not compile for the TestConstraints<int>(id, nullableId) call due to the type mismatch (TestConstraints<int>(id, nullableId.Value) will compile but throw at the runtime).

For int? (Nullable<int>) the signature becomes TestConstraints<int?>(int? key, int? nullableKey) which will compile (due to the implicit conversion T -> Nullable<T>) but obviously will fail the generic constraint with the warning.

The workaround can be to introduce two overloads, one for struct, one for class and let the compiler figure it out:

private void TestConstraints<TKey>(TKey key, TKey? nullableKey)
        where TKey : struct
    { }
    
private void TestConstraints<TKey>(TKey key, TKey? nullableKey)
        where TKey : class
    { }
  • Related