Nullable Reference Types with Generic Return Type

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:

  1. T is allowed to be nullable
  2. When T is non-nullable, then this type can still return null

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



Leave a reply



Submit