5
votes

According to C# specification in 10.4 Constants:

The type specified in a constant declaration must be sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, string, an enum-type, or a reference-type. Each constant-expression must yield a value of the target type or of a type that can be converted to the target type by an implicit conversion (§6.1).

Why then I can't do following:

public class GenericClass<T>
    where T : class
{
    public const T val = null;
}

That should be possible, because:

  • where T : class means, that The type argument must be a reference type; this applies also to any class, interface, delegate, or array type (from MSDN)
  • it satisfies another words from specification: the only possible value for constants of reference-types other than string is null.

Any possible explanation?

2
T itself what the spec calls a generic type parameter, not a reference type. - SLaks
that makes no sense. Why don't you just change the const to static? - Federico Berasategui
Also, why on earth would you want to do that? - SLaks
@SLaks but doesn't the where constrain T to be a class reference type? - David R Tribble

2 Answers

0
votes

Possible Explanation

Consider how the CLR initializes static members of generic classes or when it invokes static constructors on generic types. Normally, static initialization occurs when the program is first loaded; however, generic classes initialize their static members the first time an instance of that class is created.

Bear in mind that a generic class is not a single type; every single T that gets passed in the type declaration is creating a new type.

Now then consider a const expression, which has the requirement to be evaluated at compile-time. Although T is constrained as a class, and therefore it can receive the value null, the variable val does not exist in memory until the class has been created at runtime.

For example, consider if the const T val were valid. Then elsewhere in the code we could use:

GenericClass<string>.val
GenericClass<object>.val

Edit

Although both expressions would have the value null, the former is of type string and the latter is of type object. In order for the compiler to perform substitution, it needs to know the type definitions of the constants in question.

Constraints may be enforced at compile-time, but open generics are not converted into closed generics until runtime. Therefore, GenericClass<object>.val cannot be stored in the compiler's local memory to perform the substitution because the compiler does not instantiate the closed form of the generic class, and thus does not know what type to instantiate the constant expression to.

0
votes

Eric Lippert admitted it is a bug, and it should be allowed:

It looks to me like you’ve found a bug; either the bug is in the specification, which should explicitly call out that type parameters are not valid types, or the bug is in the compiler, which should allow it.