Search code examples
c#nullable-reference-types

C# Generic Nullable Reference Type Arguments


Let's say I have a struct as follows:

public readonly struct Quantity<T> {
    public readonly T value;

    public Quantity(T value) {
        this.value = value;
    }
}

If I then have a method with the following signature:

void Foo(Quantity<string?> quantity) { ... }

Why does the following produce a CS8620 warning, "Argument cannot be used for parameter due to differences in the nullability of reference types."?

Quantity<string> strQuantity = new Quantity<string>("Hello world");
Foo(strQuantity);

Or, rather, how do I avoid the warning? As I understand it, string and string? are the same types under the hood - and clearly in this case a Quantity<string> can be used anywhere a Quantity<string?> is required. So how do I indicate this to the compiler?

Some thoughts:

  • Creating a new object (as in new Quantity<string?>(strQuantity.value)) feels a bit ridiculous, given that the underlying data would be identical, and therefore it's just a waste of time. For the same reason the compiler won't allow an explicit/implicit cast to be written for the Quantity struct.
  • Is there some way around this if Quantity is a class rather than a struct?
  • Do I need to go all the way to making a IQuantity<out T>, or something? Would that even help?

Solution

  • The .NET JIT is pretty good at "unwrapping" structs of a single field. I wouldn't worry too much about creating extra instances of such structs.

    ImmutableArray<T> is a common type with a similar problem, which it solves using the CastUp method.

    For the nullable case specifically, you can just use ! to suppress the warning. You can consider wrapping the suppression in a method to make things more clear.

    public static ImmutableArray<T?> AsArrayOfNullable<T>(this ImmutableArray<T> input) where T : class => input!;
    

    There's also a csharplang discussion for possibly extending places where variant type parameters are permitted: https://github.com/dotnet/csharplang/issues/3950