简体   繁体   中英

C#: default of generic T? is not null; behavior changes with generic constraint

I have a generic class that should operate on (non-nullable) reference and value types (parameters, returns ...) but internally needs fields that can be null.

using System;

public class Gen<T> // where T : struct
{
    public class Data
    {
        public T? t;
    }

    public static void Write(string s)
    {
        Data d = new Data();
        Console.WriteLine("Default of {0} is {1}", s, d.t == null ? "null" : "NOT null");
    }

    // ... other stuff that uses T and not T? like
    // public T DoSomething(T value) ...
}

static class Program
{
    static void Main(string[] args)
    {
        Gen<int>.Write("int?");
        Gen<string>.Write("string?");
    }
}

This code does not produce any errors or warnings when compiled (.NET 5) with nullable enabled. However the behavior is not as I have expected.

Default of int? is NOT null
Default of string? is null

While playing around searching for a solution, I discovered that when the where T : struct constraint is added (and Gen.Write() removed), the behavior changes to

Default of int? is null

It's odd that a constraint changes the behavior.

Does anybody know a elegant solution to write such a generic class? Using a custom Nullable class that supports reference types too or a separate bool flags for every T? filed is a bit tedious.

If you want to use Nullable<int> you shouldn't use int , so use:

Gen<int?>.Write("int?");

Then the output will be

Default of int? is null
Default of string? is null

The code in the question is an example. The real class does not have a Write method and never uses the string of the type. However as I indicated by 'other stuff' it uses T as well as T?. So it is not desired to instantiate it with int? instead of int .

First i want to explain why it's not odd that the struct constraint in the generic class changes the behavior. Because actually that constraint makes it compile if you are < C#8. Then T? means Nullable<T> , so if you use Gen<int>.Write("int?") the field t will be a Nullable<int> . But then Gen<string>.Write("string") won't compile at all since string is not a struct . So it has a completely different meaning with the constraint.

With C#8 enabled you can remove the struct constrained, then t remains an int and the string will be a nullable string . So the question mark has the meaning: in case of a reference type it's a nullable reference type, otherwise it's just what it is.

You can't have both, a generic type that can be a nullable reference type or a nullable value type without using the desired generic type, so use int? if it must be nullable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM