I’ve written a simple extension method to help parse a string to a nullable enum type.
public static TEnum? ParseNullableEnum<TEnum>(this string? str)
where TEnum : Enum
{
if (str is null)
{
return null;
}
if (!Enum.TryParse(typeof(TEnum), str, ignoreCase: true, out var source))
{
throw new ArgumentOutOfRangeException(nameof(str), str, null);
}
return (TEnum)source;
}
As you can see, the return type is TEnum?, where TEnum is an Enum. And nullable is enabled, so null is a valid value. However, I’m getting an error on line return null;
CS0403: Cannot convert null to type parameter ‘TEnum’ because it could be a non-nullable value type. Consider using default(‘TEnum’) instead.
Is this a VS bug, or am I doing something wrong? I’m not converting to TEnum, the output type is TEnum?… right?
>Solution :
You want:
public static TEnum? ParseNullableEnum<TEnum>(this string? str)
where TEnum : struct, Enum
The problem you have is that the Enum constraint can match any specific enum type, or the Enum class itself. (Enum is a weird one: it’s the base type of all enum types, but is itself a reference type. It basically represents a boxed enum type, i.e. you can write Enum foo = condition ? ConsoleColor.Black : ConsoleKey.A;, to pick on two random different enum types).
In other words, someone could call:
ParseNullableEnum<Enum>("thing")
Adding the struct constraint forces someone to actually pass a specific enum type, rather than Enum.
The other part of the puzzle is that if a generic type parameter T is not constrained to class or struct, then T? means that T is "defaultable" rather than "nullable". This means that:
- If
Tis a reference type,T?allowsnullto be assigned - If
Tis a value type,T?does not meanNullable<T>, it just means the same asT.
This is due to a difference in how T? is implemented for reference and value types by the compiler. For a value type, T? is shorthand for the type Nullable<T>, but for reference types T? compiles to the same thing as T but has an effect on compiler warnings only. It’s not possible for the compiler to emit a method where the return type is T when T is a reference type, but Nullable<T> when T is a value type.
So, by not constraining T to a reference or value type, your return type of TEnum? did not mean Nullable<TEnum>, it just meant TEnum with different nullable warnings. By constraining TEnum to a value type, the compiler is allowed to emit a method which actually returns Nullable<TEnum>, and so is allowed to have the value null.