Explanation of the problem
The problem in your first code sample occurs because compiler differently handles nullable value types and nullable reference types:
- Nullable value type
T?
is represented by type Nullable<T>
.
- Nullable reference type
T?
is the same type T
but with a compiler-generated attribute annotating it.
Compiler cannot generate code to cover this both cases at the same time, therefore a compilation error occurs. And this error forces us to specify class
or struct
constraint. This behavior is also stated in the C# specification
:
For a type parameter T
, T?
is only allowed if T
is known to be a value
type or known to be a reference type.
A good explanation of this problem can be found in this article: Try out Nullable Reference Types. Scroll to the paragraph "The issue with T?
".
A workaround to fix the problem
The next workaround can be used if you don't want to create two methods with different names and suppress warnings:
// An overload that will be used by reference types.
public T? GetDefault<T>(T? t = default) where T : class
{
return default;
}
// An overload that will be used by value types.
public T? GetDefault<T>(T? t = default) where T : struct
{
return default;
}
Here we added an argument t
to the methods GetDefault
to make compiler being able to differ these two methods. Now we can use methods GetDefault
and compiler will define which overload to use. The drawback of this approach is that GetDefault
methods have unusable argument t
.