johv
johv

Reputation: 4594

Why can't a constant field be of non-built-in struct type in C#?

class T
{
    enum E { }
    struct S { }
    interface I { }
    delegate void D();
    class C { }

    const E e = new E();
    //const S s = default(S); // ERROR
    const I i = default(I);
    const D d = default(D);
    const C c = default(C);

    const int x = 10;
    const string y = "s";

    private void MyMethod(
        E e = new E(),
        S s = default(S), // NO ERROR
        I i = default(I),
        D d = default(D),
        C c = default(C),
        int x = 10,
        string y = "s")
    { }
}

All of the above are possible except for the constant field of struct type.

I can see why the non-string reference types would default to the only literal expression they can represent - null - but if I'm not mistaken the default value of a struct is an instance of the struct with all its fields set to their default value, basically memset(0). Also, for the default arguments of the method MyMethod the compiler has no complaints. So, why is it that a const struct field is not possible?

Is there a reason why or is it just the way C# was written? Oh and by the way, what's the idea of allowing enum types to have a default constructor?

(Question originally found here, where it didn't get a real answer.)

Upvotes: 5

Views: 549

Answers (2)

supercat
supercat

Reputation: 81105

The designers of C# seem to take the viewpoint that the language should forbid constructs which would have semantics that were well-defined but didn't seem to be useful, especially if such constructs would represent things the designers didn't think people should be doing.

For example, C# forbids the use of sealed types as generic constraints. If class Foo isn't sealed, then a declaration class Bar<T> where T:Foo will declare a generic class family Bar<> for which the T would have to be Foo or something derived from it. Semantically, there's no reason that construct couldn't have the exact same meaning even if Foo were a sealed type; to be sure, unless Foo were to change, there wouldn't be any way T could be anything other than Foo, and the designers of C# would probably say that the code should simply use Foo rather than T [though if Foo were ever unsealed, the meaning of the generic code would no longer match the code that specified Foo].

Likewise, I suspect that while there wouldn't have been any particular problem with C# allowing a value type to declare a constant equal to the the default value of that type, the normal purpose of constants is to provide a single patch point for things that might need to change. Any code which declared const MyPoint = Default(Point); to declare a default location of (0,0) may have been up a creek if it became necessary to have a different default value.

On the other hand, the fact that C# doesn't allow the declaration of non-default-valued constants of non-primitive value types other than Decimal doesn't mean that C# wouldn't be cable of passing through the values of constants declared in other assemblies if the language designers had chosen to allow that. If C# had allowed value-type constants declared in other assemblies to be used as value-type constants in C#, then if it became necessary to use some value other than the default for a Point constant, it would be possible to add to the assembly a reference to a CIL module which defined a Point constant containing the sequence of byte which would represent the necessary coordinates, and then have the C# module use the constant from the CIL assembly.

I disagree with the idea that static readonly should be considered a 100% adequate substitute for const, since values of constants may be used in contexts where values of static readonly variables cannot. Still, the fact that C# is unwilling to consider value-type constants from other modules to be legitimate constant expressions unless the types in question are ones it knows about would greatly limit the usefulness of being able to define constants of arbitrary value types.

Addendum

Discussions elsewhere have brought up a more general problem with value-type constants: they're stored as a sequence of bytes, which means that their meaning will be very dependent upon implementation details of the type in question; if later versions of a type store things differently, there would be no mechanism to prevent the byte sequence from being totally misinterpreted. As a simple example, if one version of a type Point had int fields X and Y in that order, but later version changed the sequence to Y, X, then a Point constant which was compiled with X=1 Y=2 would be interpreted as having Y=1 X=2.

Upvotes: 2

Marc Gravell
Marc Gravell

Reputation: 1062590

Fundamentally, true constant fields need to be representable directly in IL as metadata, which means: limiting to a set of known types that work directly in IL. You could say that you can express a "constant" via a constructor call and substitute that constructor call whenever the constant is used, etc, but:

  1. that would be simulating something very close to static readonly, which already exists and can be used
  2. it would not be provably constant - since your custom type could do anything internally

Basically, just use static readonly in your case:

static readonly S s = default(S);

In the case of enums: all value types both have a default constructor (in C# terms) and don't have a default constructor (in IL terms). Schrödinger's constructor! All the semi-existent default constructor means is "initialize this to zero", and it works for any value type (it is the initobj IL instruction). Actually, in C# you can also do this in two other ways for enums: default(TheEnum), and 0 - since the literal 0 works for any enum.

Upvotes: 5

Related Questions