Reputation: 1886
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?
Upvotes: 8
Views: 29361
Reputation: 24606
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.
Upvotes: 11
Reputation: 12811
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];
}
}
Upvotes: 8
Reputation: 726479
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.
Upvotes: 5
Reputation: 19486
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.
Upvotes: 2