5

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?

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263

2 Answers2

0

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.

nicholas
  • 2,969
  • 20
  • 39
  • You're misunderstanding both static generics and const. – SLaks Jul 24 '13 at 13:44
  • A generic type constructor (open generic class) creates a separate (closed generic) type for each parameterization. Each of these types has a separate initializer that runs independently of other parameterizations. This is why it's legal to write `static readonly T MyField = new T()`. – SLaks Jul 24 '13 at 13:46
  • `const` fields don't really exist in-memory; the compiler substitutes their values at compile-time when referenced (this is why they must be compiled-type constants). The compiler would have no trouble differentiating between fields of different closed generic types. – SLaks Jul 24 '13 at 13:47
  • @SLaks, in order for the compiler to perform substitution, doesn't it need to know the type of the expression being evaluated? At the point where the constant is defined, it is of type `T` which the compiler does not know. Although the compiler *also* is capable of enforcing constraints and `T` is constrained to a reference type, that does not mean that `T` itself is a reference type. – nicholas Jul 24 '13 at 14:40
  • The substitution occurs where the constant is referenced, at which `T` is known. (except, I guess, if you write `const T myConst = OtherClass.otherConst`) – SLaks Jul 24 '13 at 14:47
  • `open generics are not converted into closed generics until runtime` That's not really true. The compiler certainly does use closed generic types (eg, to validate overloads). – SLaks Jul 24 '13 at 14:48
  • I am going to guess the compiler does not evaluate the value of constants each time they are referenced. It likely only performs the evaluation once and stores the result (and type) in a local symbol table or cache. As a result, when the open generic class is initially compiled, it cannot evaluate the constant expression since it does not recognize type `T`. – nicholas Jul 24 '13 at 14:59
  • You're probably right, but it would populate that cache when each constant is first referenced; not when it's defined (eg, for constants defined in referenced assemblies). Thus, it would have no trouble keying that lookup by closed generic type. (again, unless the reference itself is using an unbound type parameter) – SLaks Jul 24 '13 at 15:00
  • Also, the rules in the spec are not supposed to be affected by compiler implementation details like caching. – SLaks Jul 24 '13 at 15:02
0

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.

MarcinJuraszek
  • 124,003
  • 15
  • 196
  • 263