Nullable reference types with generic return type
You were very close. Just write your method like this:
[return: MaybeNull]
public T Get<T>(string key)
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : default!;
}
You have to use the default!
to get rid of the warning. But you can tell the compiler with [return: MaybeNull]
that it should check for null even if it's a non-nullable type.
In that case, the dev may get a warning (depends on flow analytics) if he uses your method and does not check for null.
For further info, see Microsoft documentation: Specify post-conditions: MaybeNull and NotNull
C# detect nullable reference type from generic argument of method's return type using reflection
Use MethodInfo.ReturnParameter
for NullabilityInfoContext
:
Type type = typeof(IMyInterface);
var methodInfo = type.GetMethod(nameof(IMyInterface.GetMyModel));
NullabilityInfoContext context = new();
var nullabilityInfo = context.Create(methodInfo.ReturnParameter); // return type
var genericTypeArguments = nullabilityInfo.GenericTypeArguments;
var myModelNullabilityInfo = genericTypeArguments.First(); // nullability info for generic argument of Task
Console.WriteLine(myModelNullabilityInfo.ReadState); // Nullable
Console.WriteLine(myModelNullabilityInfo.WriteState); // Nullable
To analyze nullable attributes methodInfo.ReturnTypeCustomAttributes.GetCustomAttributes(true)
can be used.
A problem with Nullable types and Generics in C# 8
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 { }
}
How to identify a nullable reference type for generic type?
In C# 8 with nullable enabled, is there a way to identify a nullable
reference type for generic type?
In C# 8
there is NO way to check if a type parameter passed to a generic method is a nullable reference type or not.
The problem is that any nullable reference type T?
is represented by the same type T
(but with a compiler-generated attribute annotating it), as opposed to nullable value type T?
that is represented by the actual .NET type Nullable<T>
.
When compiler generates code that invokes a generic method F<T>
, where T
can be either nullable reference type or not, an information if T
is nullable refence type is lost. Lets consider the next sample method:
public void F<T>(T value) { }
For the next invocations
F<string>("123");
F<string?>("456");
compiler will generate the next IL
code (I simplified it a bit):
call F<string>("123")
call F<string>("456")
You can see that to the second method a type parameter string
is passed instead of string?
because the representation of the nullable reference type string?
during the execution is the same type string
.
Therefore during execution it is impossible to define if a type parameter passed to a generic method is a nullable reference type or not.
I think that for your case an optimal solution would be to pass a bool
value that will indicate if a reference type is nullable or not. Here is a sample, how it can be implemented:
public static Result<T> Create<T>(T value, bool isNullable = false)
{
Type t = typeof(T);
// If type "T" is a value type then we can check if it is nullable or not.
if (t.IsValueType)
{
if (Nullable.GetUnderlyingType(t) == null && value == null)
throw new ArgumentNullException(nameof(value));
}
// If type "T" is a reference type then we cannot check if it is nullable or not.
// In this case we rely on the value of the argument "isNullable".
else
{
if (!isNullable && value == null)
throw new ArgumentNullException(nameof(value));
}
...
}
How to allow nullable generic type in C# 8 as a return type for a method?
public async Task<T?> Retrieve<T>()
where T : class, ITableEntity
C# 8 gives a warning when returning a nullable generic with nullable constraint
Imagine you call:
string result = Foo<string>();
result
now contains null
. But it's a string
, which is not nullable.
The compiler is warning you that Foo<T>
may be called where T
is not nullable, and returning null
in this case would be unexpected.
Note that where T : class?
means that T
may be nullable, but it also might not be. Both string
and string?
are allowed. I don't believe there's any way to say "T
must be nullable".
If you're trying to say:
T
is allowed to be nullable- When
T
is non-nullable, then this type can still returnnull
Then you can write:
[return: MaybeNull]
public T Foo<T>()
where T : class?
{
return null!;
}
SharpLab
Note that MaybeNull
only applies to the method's contract and not its body, when is why we need to return null!
. However you can see in the SharpLab link above that the caller string result = Foo<string>();
gets the correct warning.
Annotation for nullable reference with generics
Let's start with some background:
Before C# 9.0 Foo1
was invalid. Even in C# 8.0 with enabled nullable references:
CS8627: A nullable type parameter must be known to be a value type or non-nullable reference type
Foo2
was valid even before C# 8.0 because T?
made sense only if T
was a struct, and in this case T?
had a different type from T
(Nullable<T>
). So far, it's quite simple.
Starting with C# 8.0 nullable references have been introduced, which caused some confusion. From now on T?
can either mean Nullable<T>
or just T
. This version didn't allow T?
without a constraint but it allowed also when you specified where T : class
.
Without using constraints you had to use attributes to indicate that T
can be null
as a return value:
// C# 8.0: Poor man's T?
[return: MaybeNull] T Foo1<T>(Key<T> key) => default;
And what if T
is a value type now? It clearly will not change its type to Nullable<T>
in the return value. To return a double?
your type argument must also be double?
, meaning, MyKey
must also be a Key<double?>
.
In C# 9.0 the restriction for T?
has been relaxed, now it does not need a constraint:
// C# 9.0: this is valid now
T? Foo1<T>(Key<T> key) => default;
But it essentially now means the same as the C# 8.0 version. Without the where T : struct
constraint T?
is the same type as T
so it is nothing but an indication that the result can be null
, which can appear in compiler warnings. To return nullable value types you must use double?
as a generic argument, which also mean that your Key
also must have a nullable type defined:
static readonly Key<double?> MyKey = new Key<double?>();
If a nullable key makes no sense in your case, then you cannot do anything but specifying where T : struct
constraint as in Foo2
so the old rule kicks in: T?
and T
have different types where T?
means Nullable<T>
.
Update: The main difference between Foo1
and Foo2
is maybe more obvious if you see their decompiled source:
[System.Runtime.CompilerServices.NullableContext(2)]
private static T Foo1<T>([System.Runtime.CompilerServices.Nullable(new byte[] {
0,
1
})] Key<T> key)
{
return default(T);
}
private static Nullable<T> Foo2<T>(Key<T> key) where T : struct
{
return null;
}
Note that the return type of Foo1
is simply T
with some annotation so the compiler can emit the proper warnings.
Casting object? to a generic type that may or may not be nullable
If you use C# 9.0 or higher, you can use return type of T?
without need to resort to Nullable<T>
. For generics in non-nullable context there is special set of rules, detailed here.
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.
You can check GetType()
of T
with simple console application. If T
is int
, return type will be System.Int32
, not Nullable<System.Int32>
.
#nullable enable
using System;
public class Program
{
public static void Main()
{
var result = Convert<int>(null);
Console.WriteLine(result); // Prints: 0
Console.WriteLine(result.GetType().FullName); // Prints: System.Int32
}
static T? Convert<T>(object? value)
{
if (value is null)
return default(T);
return (T)value;
}
}
C# Playground example here.
Nullability of reference types in return type 'T? ManagerT.Strategy.get' doesn't match implicitly implemented member 'T IManagerT.Strategy.get'
You can, but you are saying in your generics that you don't expect T
to be null, then you are having a property whose getter (that's T IManager<T>.Strategy.get
) might return a null T
: that's the warning you are getting. Using only managed code and not reading externally (and unless you force the use of null
explicitly), that should never happen (Strategy
could never return null
)
You can make it:
public class Manager<T> : IManager<T?> where T : IStrategy<Options>
// Notice the ? in IManager<T?>
And the warning should go away, but think if that's what you actually want (that's precisely what nullable reference type annotations are for)
I've made a fiddle: https://dotnetfiddle.net/ccar24
Related Topics
Implementing Audit Log/Change History with MVC & Entity Framework
Programmatically Add an Application to Windows Firewall
.Net - Windowstyle = Hidden VS. Createnowindow = True
Generating Xml File Using Xsd File
How to Check Whether an Object Has Certain Method/Property
Check If Property Has Attribute
Add Two Integers Using Only Bitwise Operators
How to Install a Certificate into the Local MAChine Store Programmatically Using C#
Generic Way to Check If Entity Exists in Entity Framework
C# Iterating Through an Enum? (Indexing a System.Array)
Convert File Path to a File Uri
.Contains() on a List of Custom Class Objects
Why Does Path.Combine Not Properly Concatenate Filenames That Start with Path.Directoryseparatorchar
How to Interrupt Console.Readline