Reputation: 325
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.
Upvotes: 3
Views: 694
Reputation: 460018
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 withint?
instead ofint
.
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.
Upvotes: 4