简体   繁体   中英

Nullable types in generic class

I need to create a class similar to this:

class GenericClass<T>
{
    public T[] Arr {get; }
    public GenericClass(int n)
    {
        Arr = new T[n];
        for (int i = 0; i < n; i++)
        {
            Arr[i] = null;
        }
    }
}

But there is a compiler error:

CS0403 Cannot convert null to type parameter 'T' because it could be a non-nullable value type. Consider using 'default(T)' instead.

I don't want to use default(T) , because it could be the same as normal value but I need to distinguish. I don't know the type so I can't use minimal value. How can I use null?

You are actually shooting yourself in the foot here. Arrays in .NET are automatically set to the default value of the type, so as long as a type's default is null (all objects and nullables), then simply creating the array is enough to set all items to null. Without that excess code, you may never have encountered an issue at all, depending on how you're trying to use this class.

If you're trying to allow cases like GenericClass<int> and have it expose an array of int? s, then do this. The where T : struct enforces that the supplied type is a value type, thereby allowing you to use T? . You can't use object types for the generic parameter in this case.

class GenericClass<T> where T : struct
{
    public T?[] Arr { get; }
    public GenericClass(int n)
    {
        Arr = new T?[n];
    }
}

If you're trying to create a class that supports both object instances and nullables, try this. The problem here is you can't make a type constraint -- if you use where T : struct , you can't use object types; and if you use where T : class , you can't use nullables. Therefore, this type allows usages like GenericClass<MyObject> (works like you want), GenericClass<int?> (works like you want) and GenericClass<int> (doesn't work like you want, and can't possibly work like you want anyway). Unfortunately, there's no way to define this generic to disallow the latter case. You can make that check at runtime if you like, though.

class GenericClass<T>
{
    public T[] Arr { get; }
    public GenericClass(int n)
    {
        Arr = new T[n];
    }
}

The thing with generics is that they have to consider the possibility of T being literally anything, including a type that cannot be null (value types including primitives and structs). In order to restrict the generic parameter to types that can be null , you need to add the class restraint:

class GenericClass<T> where T : class
{
    public T[] Arr { get; private set; }
    public GenericClass(int n)
    {
        Arr = new T[n];
        for (int i = 0; i < n; i++)
        {
            Arr[i] = null;
        }
    }
}

Alternatively, you may not want to be dealing with null at all. Instead, you can replace

Arr[i] = null;

with

Arr[i] = default(T);

and it will work fine. default(T) will return null for any nullable type and the default value for any non-nullable type. (0 for int , false for bool , etc.)

EDIT: As another alternative, you can represent the object internally with a Nullable wrapper type. C# allows a shorthand for this syntax with the ? operator:

class GenericClass<T>
{
    public T?[] Arr { get; private set; }
    public GenericClass(int n)
    {
        Arr = new T?[n];
    }
}

Incidentally, with this approach, there's no need to iterate over every index of the array and set it to null , as C# will handle that for you.

If you know that T must be a nullable type, say, Nullable<Xyz> , you can force your users to pass Xyz instead, and make Nullable<Xyz> (aka Xyz? ) internally:

class GenericClass<T> where T : struct {
    public T?[] Arr {get; }
    public GenericClass(int n) {
        Arr = new T?[n]; // This will create an array of nulls, no need for a loop
    }
}

The where T : struct constraint added to the declaration will prevent GenericClass<T> instantiations with arguments that are not value types. For example, an attempt to make GenericType<string> will result in compile-time error.

What you want to do is instantiate your class with a nullable type if it's a value type:

var obj = new GenericClass<int?>();

Then you can use default(T) instead of null without issue. You'll just end up with an int?[] array though you don't need to use it explicitly because each element in the array will automatically get the default value when the array is instantiated.

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