After adding <Nullable>enable</Nullable>
or #nullable enable
, I ran into the following problem with my Generic methods:
This does not work:
public T? GetDefault<T>()
{
return default;
}
This works with warning:
public T GetDefault<T>()
{
return default;
}
This works individually, but not together.
public T? GetDefault<T>() where T : class
{
return default;
}
public T? GetDefault<T>() where T : struct
{
return default;
}
Logically, the first method should work.
What is the correct way (in any framework) out of this situation without creating several methods and suppressing warnings?
[MaybeNull] attribute only works with .Net Core 3.0+.
Also, I asked this questions here
T?
can only be used when the type parameter is known to be of a reference type or of a value type. Otherwise, we don't know whether to see it as a System.Nullable<T>
or as a nullable reference type T
.
Instead you can express this scenario in C# 8 by using the [MaybeNull]
attribute.
#nullable enable
using System.Diagnostics.CodeAnalysis;
public class C
{
[return: MaybeNull]
public T GetDefault<T>()
{
return default!; // ! just removes warning
}
}
This attribute is only included in .NET Core 3.0+, but it is possible to declare and use the attribute internal to your project (although this is not officially supported, there's no reason to assume the behavior will break down the line). To do so, you can just add a namespace+class declaration to your code similar to the following:
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that an output may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
internal sealed class MaybeNullAttribute : Attribute { }
}
Explanation of the problem
The problem in your first code sample occurs because compiler differently handles nullable value types and nullable reference types:
T?
is represented by type Nullable<T>
.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 ifT
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
.
It seems the best solution to this problem will only be in C# 9 as T??
Links:
1. https://github.com/dotnet/csharplang/issues/3471#issuecomment-631722668
2. https://github.com/dotnet/csharplang/issues/3297
At the moment, a working solution was provided by Rikki Gibson. It implies additional code, but it works as it should.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With