What is Unknown Nullability in C# 8? - c#

In C# 8.0 we can have nullable reference types. The docs state that there are 4 types of nullability. The first 3 are quite clear but I fail to understand the point of "unknown". The docs say it is used with generics but when I try to call a method on an unconstrained variable of T type in generics it just warns as if the type is nullable. I fail to see the difference between unknown and nullable. Why does unknown exist? How does it manifest itself?

Take the following generic method:
public static T Get<T>(T value)
{
return value;
}
If we call it like Get<string>(s), the return is non-nullable, and if we do Get<string?>(s), it's nullable.
However if you are calling it with a generic argument like Get<T>(x) and T isn't resolved, for example it is a generic argument to your generic class like below...
class MyClass<T>
{
void Method(T x)
{
var result = Get<T>(x);
// is result nullable or non-nullable? It depends on T
}
}
Here the compiler does not know if eventually it will be called with a nullable or non-nullable type.
There is a new type constraint we can use to signal that T cannot be null:
public static T Get<T>(T value) where T: notnull
{
return value;
}
However, where T is unconstrained and still open, the nullability is unknown.
If these unknowns were treated as nullable then you could write the following code:
class MyClass<T>
{
void Method(T x)
{
var result = Get<T>(x);
// reassign result to null, cause we we could if unknown was treated as nullable
result = null;
}
}
In the case where T was not nullable, we should have got a warning. So with unknown nullability types, we want warnings when dereferencing, but also warnings for assigning potentially null.

Related

Casting object? to a generic type that may or may not be nullable

I'm trying to convert a project to use nullable reference types, but I'm running into an issue. In my project, I have a place where I get an object? that needs to be cast to a generic type T before adding it to a collection. The type T could be anything; a nullable reference type, a non-nullable reference type, or a value type. This isn't known at compile time.
So, let's say I have the following code (toy example):
static T Convert<T>(object? value)
{
return (T)value;
}
This causes the compiler to complain that value may be null, and that the return of the function may be null. That's fair enough, since if T is non-nullable and value is null, this wouldn't be allowed. I thought maybe this would work:
static T Convert<T>(object? value)
{
if (value == null)
return default;
else
return (T)value;
}
But this has the same problem: if T is a non-nullable reference type, default is still null, which still violates the constraint.
Making the function return T? is not a solution, because in the case of value types, I don't want to use Nullable<T>.
I thought about throwing an exception if value is null, but I want to allow null if T is nullable. So I'd only want to throw that if T is non-nullable, and that kind of generic specialization doesn't seem possible in C#.
The context here is that I'm using a TypeConverter, and unfortunately the result of conversion is allowed to return null.
Is there a good way to handle this situation?
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.

Annotation for nullable reference with generics

Given the following generic Foo1 function:
struct Key<T> {}
static readonly Key<double> MyKey = new Key<double>();
T? Foo1<T>(Key<T> key)
{
return default;
}
A naive reader would assume that:
var foo1 = Foo1(MyKey);
foo1 is of type double?, it turns out that the compiler is picking double for the return type. I need to explictly add a constraint to get a nullable return value:
T? Foo2<T>(Key<T> key) where T : struct // really return a nullable
{
return default;
}
Could someone explain why the annotation for nullable reference ? is not being picked up in my first Foo1 function ?
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.

Proper nullable annotation for async generic method that may return default(T)

I am converting a codebase to C#8 with nullable reference types. I came across the a method similar to the one in this question but async.
public async Task<T> GetAsync<T>()
{
// sometimes returns default(T); => warning CS8603 Possible null reference return
}
T may be any type, including nullable reference types or nullable value types.
To be clear, I understand WHY this method triggers a warning. What I'd like to know is what annotations can be used to resolve it.
I know I can use #nullable disable or default(T)!, but I was hoping for something that's less of a "hammer".
I know I can't use [return: MaybNull] because that would apply to the Task itself, not the T.
Is there any other attribute/annotation I can apply to make the compiler happy, or is default(T)! my only option?
Form my experience, you can use Task<T?> GetAsync<T>() where T: class to resolve your problem.
In C# 9 we can solve just by adding ?
public async Task<T?> GetAsync<T>()
{
return default;
}
But you need to distinguish nullable value types and nullable ref types in the calling code. To get a nullable ref type as a return value you can call the method with either <T> or <T?>:
SomeClass? c = await GetAsync<SomeClass>(); // return type is SomeClass?
SomeClass? c2 = await GetAsync<SomeClass?>(); // return type is SomeClass?
To get a nullable value type you need to call it with <T?>:
int? i = await GetAsync<int?>(); // return type is int?
int i2 = await GetAsync<int>(); // return type is int
P.S. I wonder how Microsoft explains us why they can't allow unconstrained T? and then just does this in the next C# version :)
Another option is to use a code from the answer for C# 8.
Answer for C# 8
We can't have async Task<T?> GetAsync<T>() since SomeClass? and SomeStruct? are very different. Also default! is not the best option since we can get nullable reference on non-nullable reference type by calling GetAsync<SomeClass>() in the calling code.
Better option is to have two different methods that use the same private method:
public class Storage
{
...
public Task<T?> GetClassAsync<T>() where T : class
{
return GetAsync<T?>();
}
public Task<T?> GetStructAsync<T>() where T : struct
{
return GetAsync<T?>();
}
private async Task<T> GetAsync<T>()
{
if (condition)
return default!;
string json = await GetJsonAsync();
T result = JsonSerializer.Deserialize<T>(json);
return result;
}
}
And usage:
// return type is SomeClass?
SomeClass? classResult = await storage.GetClassAsync<SomeClass>();
// return type is int?
int? structResult = await storage.GetStructAsync<int>();
// warning: Nullability of type argument doesn't match 'class' constraint
SomeClass? classResult = await storage.GetClassAsync<SomeClass?>();
// error: The type 'int?' must be a non-nullable value type
int? structResult2 = await storage.GetStructAsync<int?>();
From searching around and doing more research, it appears that the preferred way to suppress the warning in this context is to use the ! operator on default(T).

C#'s can't make `notnull` type nullable

I'm trying to create a type similar to Rust's Result or Haskell's Either and I've got this far:
public struct Result<TResult, TError>
where TResult : notnull
where TError : notnull
{
private readonly OneOf<TResult, TError> Value;
public Result(TResult result) => Value = result;
public Result(TError error) => Value = error;
public static implicit operator Result<TResult, TError>(TResult result)
=> new Result<TResult, TError>(result);
public static implicit operator Result<TResult, TError>(TError error)
=> new Result<TResult, TError>(error);
public void Deconstruct(out TResult? result, out TError? error)
{
result = (Value.IsT0) ? Value.AsT0 : (TResult?)null;
error = (Value.IsT1) ? Value.AsT1 : (TError?)null;
}
}
Given that both types parameters are restricted to be notnull, why is it complaining (anywhere where there's a type parameter with the nullable ? sign after it) that:
A nullable type parameter must be known to be a value type or non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
?
I'm using C# 8 on .NET Core 3 with nullable reference types enabled.
Basically you're asking for something that can't be represented in IL. Nullable value types and nullable reference types are very different beasts, and while they look similar in source code, the IL is very different. The nullable version of a value type T is a different type (Nullable<T>) whereas the nullable version of a reference type T is the same type, with attributes telling the compiler what to expect.
Consider this simpler example:
public class Foo<T> where T : notnull
{
public T? GetNullValue() =>
}
That's invalid for the same reason.
If we constraint T to be a struct, then the IL generated for the GetNullValue method would have a return type of Nullable<T>.
If we constraint T to be a non-nullable reference type, then the IL generated for the GetNullValue method would have a return type of T, but with an attribute for the nullability aspect.
The compiler can't generate IL for a method which has a return type of both T and Nullable<T> at the same time.
This is basically all the result of nullable reference types not being a CLR concept at all - it's just compiler magic to help you express intentions in code and get the compiler to perform some checking at compile-time.
The error message isn't as clear as it might be though. T is known to be "a value type or non-nullable reference type". A more precise (but significantly wordier) error message would be:
A nullable type parameter must be known to be a value type, or known to be a non-nullable reference type. Consider adding a 'class', 'struct', or type constraint.
At that point the error would reasonably apply to our code - the type parameter is not "known to be a value type" and it's not "known to be a non-nullable reference type". It's known to be one of the two, but the compiler needs to know which.
The reason for the warning is explained in the section The issue with T? of Try out Nullable Reference Types. Long story short, if you use T? you have to specify whether the type is a class or struct. You may end up creating two types for each case.
The deeper problem is that using one type to implement Result and hold both Success and Error values brings back the same problems Result was supposed to fix, and a few more.
The same type has to carry a dead value around, either the type or the error, or bring back nulls
Pattern matching on the type isn't possible. You'd have to use some fancy positional pattern matching expressions to get this to work.
To avoid nulls you'll have to use something like Option/Maybe, similar to F#'s Options. You'd still carry a None around though, either for the value or error.
Result (and Either) in F#
The starting point should be F#'s Result type and discriminated unions. After all, this already works on .NET.
A Result type in F# is :
type Result<'T,'TError> =
| Ok of ResultValue:'T
| Error of ErrorValue:'TError
The types themselves only carry what they need.
DUs in F# allow exhaustive pattern matching without requiring nulls :
match res2 with
| Ok req -> printfn "My request was valid! Name: %s Email %s" req.Name req.Email
| Error e -> printfn "Error: %s" e
Emulating this in C# 8
Unfortunately, C# 8 doesn't have DUs yet, they're scheduled for C# 9. In C# 8 we can emulate this, but we lose exhaustive matching :
#nullable enable
public interface IResult<TResult,TError>{}​
​struct Success<TResult,TError> : IResult<TResult,TError>
{
public TResult Value {get;}
public Success(TResult value)=>Value=value;
public void Deconstruct(out TResult value)=>value=Value;
}
​struct Error<TResult,TError> : IResult<TResult,TError>
{
public TError ErrorValue {get;}
public Error(TError error)=>ErrorValue=error;
public void Deconstruct(out TError error)=>error=ErrorValue;
}
And use it :
IResult<double,string> Sqrt(IResult<double,string> input)
{
return input switch {
Error<double,string> e => e,
Success<double,string> (var v) when v<0 => new Error<double,string>("Negative"),
Success<double,string> (var v) => new Success<double,string>(Math.Sqrt(v)),
_ => throw new ArgumentException()
};
}
Without exhaustive pattern matching, we have to add that default clause to avoid compiler warnings.
I'm still looking for a way to get exhaustive matching without introducing dead values, even if they are just an Option.
Option/Maybe
Creating an Option class by the way that uses exhaustive matching is simpler :
readonly struct Option<T>
{
public readonly T Value {get;}
public readonly bool IsSome {get;}
public readonly bool IsNone =>!IsSome;
public Option(T value)=>(Value,IsSome)=(value,true);
public void Deconstruct(out T value,out bool isSome)=>(value,isSome)=(Value,IsSome);
}
//Convenience methods, similar to F#'s Option module
static class Option
{
public static Option<T> Some<T>(T value)=>new Option<T>(value);
public static Option<T> None<T>()=>default;
}
Which can be used with :
string cateGory = someValue switch { Option<Category> (_ ,false) =>"No Category",
Option<Category> (var v,true) => v.Name
};

C# 8.0 nullable and generics [duplicate]

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?
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
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;
}
In C# 9 you are able to express nullability of unconstrained generics more naturally:
public T? Get<T>(string key)
{
var wrapper = cacheService.Get(key);
return wrapper.HasValue ? Deserialize<T>(wrapper) : default;
}
Note there's no ! operator on the default expression. The only change from your original example is the addition of ? to the T return type.
In addition to Drew's answer about C# 9
Having T? Get<T>(string key) we still need to distinguish nullable ref types and nullable value types in the calling code:
SomeClass? c = Get<SomeClass?>("key"); // return type is SomeClass?
SomeClass? c2 = Get<SomeClass>("key"); // return type is SomeClass?
int? i = Get<int?>("key"); // return type is int?
int i2 = Get<int>("key"); // return type is int

Categories

Resources