Nullable Type Is Not a Nullable Type

Why T? is not a nullable type?

For the sake of backward compatibility, T? is implemented as a pure compile-time construct. For reference type, it's the same as T at runtime. For value type, it is translated to Nullable<T>. The compiler needs to know for sure that T is a value type at compile time to do the translation. Thus for unconstraint type T, the compiler can't assume T? is nullable.

To get the behavior in the OP, the only OOTB way seems to be to overload the method for reference and value types. Unfortunately changing the type constraint is not enough for overload resolution so the name or the parameters would need to be different as well.

public static T? Method<T>() where T : struct
{
return null;
}

public static T Method<T>(int placeholder = 0) where T : class
{
return null;
}

Depending on what you're trying to do, Chenga's solutions are likely easier to work with, but this will work if you absolutely need it to return null with a non-nullable type constraint. If you're concerned about default being a non-null value you could also implement an Optional type such as the one in the question linked and return that instead of T.

Non-Nullable becomes Nullable on return

This is indeed by design.

The meeting notes where this design was chosen are here: https://github.com/dotnet/csharplang/blob/main/meetings/2019/LDM-2019-12-18.md#var

To summarise: It was found that people were frequently having to explicitly type the variable rather than using var because after assignment from a non-null-returning method, the code went on to assign null to the variable.

To quote from the meeting notes:

At this point we've seen a large amount of code that requires people
spell out the type instead of using var, because code may assign null
later.

That is, code like the following was found to be quite frequent:

var someVariable = SomeMethodThatDoesntReturnNull();
...
someVariable = null; // or someVariable = someMethodThatCanReturnNull();

Note that (as pointed out by Jeroen Mostert) result2 in your question is NOT nullable because it's explicitly typed as non-nullable. I'm not sure why you think it is nullable, but it definitely isn't.

For example, look at this repro on DotNetFiddle and note the warning on line 11.

Nullable type is not a nullable type?

According to the MSDN :

Calling GetType on a Nullable type
causes a boxing operation to be
performed when the type is implicitly
converted to Object. Therefore GetType
always returns a Type object that
represents the underlying type, not
the Nullable type.

When you box a nullable object, only the underlying type is boxed.

Again, from MSDN :

Boxing a non-null nullable value type
boxes the value type itself, not the
System.Nullable that wraps the value
type.

List T is non nullable?

Recently, Microsoft enabled nullable reference types by default on new projects.

You have five ways to make this warning go away:

  1. You can respect nullable reference types, and change the type of your parameter:
public static void test(List<String>? list = null) { }

  1. You can ignore the null assignment using the ! operator. (I like to call this the "I know better" operator because I will typically use it when I know what I'm assigning is not null but the compiler thinks it could be.)
public static void test(List<String> list = null!) { }

  1. You can disable nullable reference types for just this piece of the code:
#nullable disable
public static void test(List<String> list = null) { }
#nullable restore

  1. You can disable nullable reference types for the whole file by putting #nullable disable at the beginning of it (with your namespace declaration)
  2. You can disable nullable reference types for the project. Right click on the project in Solution Explorer -> Properties -> Build -> General -> Nullable.

How to implicitly convert nullable type to non-nullable type

You can achieve your goal by providing one extension method for non-nullable reference types and another for unmanaged (e.g. int, bool, ...) types. Note that unmanaged types require a cast.

public static class NullExtensions
{
public static T ThrowIfNull<T>(
this T? argument,
string? message = default,
[CallerArgumentExpression("argument")] string? paramName = default
) where T : notnull
{
if (argument is null)
{
throw new ArgumentNullException(paramName, message);
}
else
{
return argument;
}
}

public static T ThrowIfNull<T>(
this T? argument,
string? message = default,
[CallerArgumentExpression("argument")] string? paramName = default
) where T : unmanaged
{
if (argument is null)
{
throw new ArgumentNullException(paramName, message);
}
else
{
return (T)argument;
}
}
}

Use like this:

int? foo = 42;
int bar = foo.ThrowIfNull();
Console.WriteLine(bar);

string? baz = "Hello";
string quus = baz.ThrowIfNull();
Console.WriteLine(quus);

// Comment out either this
baz = null;
quus = baz.ThrowIfNull();
// Or this
foo = null;
bar = foo.ThrowIfNull();

Can a nullable variable in C# be marked as non-null from some point on for the compiler?

It looks like you just want to allow the method to be called without specifying a parameter (and use a certain default in that case).

There is another possible solution to this particular scenario:. Just overload f() like so:

static void f()
{
f(new A { x = 3 });
}

static void f(A a)
{
Console.WriteLine(a.x);
}

Code can then call f() with or without a parameter, but the difference is that in the case where it calls f(A a) with a parameter, it cannot be null.


(In response to your comment below.)

If you want to handle being able to call with a possibly-null struct, you can overload like this instead:

static void f(A? a = default)
{
f(a ?? new A { x = 3 });
}

static void f(A a)
{
Console.WriteLine(a.x);
}

That means your implementation doesn't have to deal with a possibly-null value, but you can still omit the argument at the call site, or call it with a null value.

Note: You can simplify static void f(A? a = default) to:

static void f(A? a = default) => f(a ?? new A { x = 3 });

if you prefer the shorter syntax.

Not nullable types

DateTime and TimeSpan are not-nullable since they are structs rather than classes.

As for your second question, there is no standard way you can do this in C#. You can do this using PostSharp, which is an AOP framework, or with Spec#, which is a whole new language (an extension of C#) which allows for some of desired behavior.

In Dart, given the nullable type `T?`, how do I get the non-nullable one `T`

In general, you do not. There is no simple way to strip the ? of a type, or destructure types in other ways. (You also can't find the T of type you know is a List<T> at run--time)

If you have the type as a Type object, you can do nothing with it. Using Type object is almost never what you need.

If you have the type as a type parameter, then the type system don't actually know whether it's nullable. Example:

void foo<T>() { ... here T can be nullable or non-nullable ... }

Even if you test null is T to check that the type is actually nullable, the type system doesn't get any smarter, that's not one of the tests that it can derive type information from.

The only types you can improve on are variable types (or rather, the type of a single value currently stored in a variable). So, if you have T x = ...; and you do if (x != null) { ... x is not null here }, you can promote the variable to T&Object, but that's only an intermediate type to allow you to call members on the variable, it's not a real type that you can capture as a type variable or a variable type. It won't help you.

All in all, it can't be done. When you have the nullable type, it's too late, you need to capture it before adding the ?.

What problem are you actually trying to solve?



Related Topics



Leave a reply



Submit