Reputation: 33
I stumbled across an interesting issue where I do not have any explanation for, hoping someone could explain.
When dealing with nullable objects (pseudo) code like the following works (Note: working snipped below):
object? obj = GetNullableObject();
if(obj is null) {
return;
}
FunctionRequiringNonNullableObj(obj); // No compile error, object is known to be not null
However, doing the same, with for example long? does not:
long? literal = GetNullableLong();
if(literal is null) {
return;
}
FunctionRequiringNonNullableLong(literal); // Compiler error -> Cannot convert long? to long
Somehow the compiler seems to ignore the fact that the literal cannot be null anymore. I assume I am missing something related to performance optimization magic (or related).
Implemented both versions (object? and long?) to reproduce the issue. My expectation is that in both cases the compiler knows that the variable is not null anymore.
using System;
public class Program
{
public static void Main()
{
TestWithObject();
TestWithLiteral();
}
private static void TestWithObject() {
object? obj = ReturnOptionalObject();
if(obj is null) {
Console.WriteLine("Object is null. Returning.");
return;
}
FunctionUsingObject(obj);
}
private static void TestWithLiteral() {
long? literal = ReturnsOptionalLiteral();
if(literal is null) {
Console.WriteLine("Literal is null. Returning.");
return;
}
// Does not work, compiler error
// FunctionUsingLiteral(literal);
}
private static object? ReturnOptionalObject() {
return new object();
}
private static long? ReturnsOptionalLiteral () {
return 123L;
}
private static void FunctionUsingObject(object obj) {
Console.WriteLine("Can access object.");
}
private static void FunctionUsingLiteral(long literal) {
Console.WriteLine("Can access literal.");
}
}
Upvotes: 1
Views: 89
Reputation: 141565
Nullable reference types and nullable value types are completely different things from the compiler/runtime point of view. Nullable value types are actually represented as a Nullable<T>
struct so you need to access Value
property:
long? literal = GetNullableLong();
if(literal is null) {
return;
}
FunctionRequiringNonNullableLong(literal.Value);
On the other hand nullable reference types do not have a specific type to represent them (only some metadata) and used only for nullable static analysis (and by default produce warnings, not errors).
Both will behave correctly in terms of nullable analysis flow, for example if you try to access literal.Value
before the null check it will produce the warning as attempt to do something similar with obj
.
Also note:
Console.WriteLine(typeof(int?) == typeof(int)); // False
Console.WriteLine(typeof(int?) == typeof(Nullable<int>)); // True
// error CS8639: The typeof operator cannot be used on a nullable reference type
// Console.WriteLine(typeof(string?) == typeof(string));
Read more:
Upvotes: 2