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, 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, ifT
is astring
, thenT?
is astring?
.- If the type argument for
T
is a value type,T?
references the same value type,T
. For example, ifT
is anint
, theT?
is also anint
.- If the type argument for
T
is a nullable reference type,T?
references that same nullable reference type. For example, ifT
is astring?
, thenT?
is also astring?
.- If the type argument for
T
is a nullable value type,T?
references that same nullable value type. For example, ifT
is aint?
, thenT?
is also aint?
.
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
{ }