How to handle notnull classes and valuetypes in the same method?

Viewed 68

Note that this whole question assumes a nullable context as is the default in NET 6.

I want to write a method Read that takes in a nonnullable type (i.e. a struct or class) and outputs the nullable version of that struct or class. Something like this (obviously it wouldn't just return default but no need to complicate this):

public static T? Read<T>()
{
    return default; //should return null
}

This example method should return null, and indeed it does for classes, nullable classes, and nullable valuetypes but not valuetypes:

Console.WriteLine(null == Read<string>()); //true
Console.WriteLine(null == Read<string?>()); //true
Console.WriteLine(null == Read<int>()); //false
Console.WriteLine(null == Read<int?>()); //true

I want a method that returns true for all of these cases. I've tried a whole host of constraints on T. I feel like the notnull constraint should work but it doesn't.

I could just make two methods, one constrained to struct (i.e. value types) and one constrained to class (i.e. nonnullable reference types). The following works:

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

public static T? ReadRef<T>()  where T: class
{
    return null; //does return null
}

But, as you can see, these two methods would be identical sans the constraint and different names (which is needed because they have the same signature). So it seems silly that I would have to make two different methods for this.

For context: I know that the jankiness of this situation is in part caused by nullable value types being represented by Nullable<T> under the hood while nullable classes are just kept track of by the compiler. I just want to know if there is a way to avoid this.

1 Answers

AFAIK this is impossible, due to the following:

  1. Overload resolution for calls without explicitly provided arguments (for calls with explicitly provided parameters of the generic type you could use approach similar to specified in answers to this question - see it yourself) - see the nice blog post by Jon Skeet.

  2. When generic type parameter T is not constrained neither to class nor to struct compiler will treat T? differently for value and reference types and for value types T? will be equal to T (see this answer).

Related