Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Nullable reference types with generic return type

I'm playing around a bit with the new C# 8 nullable reference types feature, and while refactoring my code I came upon this (simplified) method:

public T Get<T>(string key) {     var wrapper = cacheService.Get(key);     return wrapper.HasValue ? Deserialize<T>(wrapper) : default; } 

Now, this gives a warning

Possible null reference return

which is logical, since default(T) will give null for all reference types. At first I thought I would change it to the following:

public T? Get<T>(string key)

But this cannot be done. It says I either have to add a generic constraint where T : class or where T : struct. But that is not an option, as it can be both (I can store an int or int? or an instance of FooBar or whatever in the cache). I also read about a supposed new generic constraint where class? but that did not seem to work.

The only simple solution I can think of is changing the return statement using a null forgiving operator:

return wrapper.HasValue ? Deserialize<T>(wrapper) : default!; 

But that feels wrong, since it can definitely be null, so I'm basically lying to the compiler here..

How can I fix this? Am I missing something utterly obvious here?

like image 806
Razzie Avatar asked Feb 08 '19 13:02

Razzie


People also ask

What is nullable reference type?

Nullable reference types are a compile time feature. That means it's possible for callers to ignore warnings, intentionally use null as an argument to a method expecting a non nullable reference. Library authors should include runtime checks against null argument values.

Can a generic type be null?

This means that you can put any object in a collection because all classes in the C# programming language extend from the object base class. Also, we cannot simply return null from a generic method like in normal method.

What is #nullable disable?

Nullable disable as the default: disable is the default if you don't add a Nullable element to your project file. Use this default when you're not actively adding new files to the codebase. The main activity is to update the library to use nullable reference types.

What are nullable types in C#?

C# provides a special data types, the nullable types, to which you can assign normal range of values as well as null values. For example, you can store any value from -2,147,483,648 to 2,147,483,647 or null in a Nullable<Int32> variable. Similarly, you can assign true, false, or null in a Nullable<bool> variable.


2 Answers

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

like image 174
Marcus Kaseder Avatar answered Sep 20 '22 14:09

Marcus Kaseder


I think default! is the best you can do at this point.

The reason why public T? Get<T>(string key) doesn't work is because nullable reference types are very different from nullable value types.

Nullable reference types is purely a compile time thing. The little question marks and exclamation marks are only used by the compiler to check for possible nulls. To the eyes of the runtime, string? and string are exactly the same.

Nullable value types on the other hand, is syntactic sugar for Nullable<T>. When the compiler compiles your method, it needs to decide the return type of your method. If T is a reference type, your method would have return type T. If T is a value type, your method would have a return type of Nullable<T>. But the compiler don't know how to handle it when T can be both. It certainly can't say "the return type is T if T is a reference type, and it is Nullable<T> if T is a reference type." because the CLR wouldn't understand that. A method is supposed to only have one return type.

In other words, by saying that you want to return T? is like saying you want to return T when T is a reference type, and return Nullable<T> when T is a value type. That doesn't sound like a valid return type for a method, does it?

As a really bad workaround, you could declare two methods with different names - one has T constrained to value types, and the other has T constrained to reference types:

public T? Get<T>(string key) where T : class {     var wrapper = cacheService.Get(key);     return wrapper.HasValue ? Deserialize<T>(wrapper) : null; }  public T? GetStruct<T>(string key) where T : struct {     var wrapper = cacheService.Get(key);     return wrapper.HasValue ? (T?)Deserialize<T>(wrapper) : null; } 
like image 27
Sweeper Avatar answered Sep 19 '22 14:09

Sweeper