Why Does C# Limit the Set of Types That Can Be Declared as Const

Why does C# limit the set of types that can be declared as const?

From the C# specification, chapter 10.4 - Constants:

(10.4 in the C# 3.0 specification, 10.3 in the online version for 2.0)

A constant is a class member that represents a constant value: a value that can be computed at compile time.

This basically says that you can only use expressions that consists solely of literals. Any calls to any methods, constructors (that cannot be represented as pure IL literals) cannot be used, as there is no way for the compiler to do that execution, and thus compute the results, at compile time. Also, since there is no way to tag a method as invariant (ie. there is a one-to-one mapping between input and output), the only way for the compiler to do this would be to either analyze the IL to see if it depends on things other than the input parameters, special-case handle some types (like IntPtr), or just disallow every call to any code.

IntPtr, as an example, though being a value type, is still a structure, and not one of the built-in literals. As such, any expression using an IntPtr will need to call code in the IntPtr structure, and this is what is not legal for a constant declaration.

The only legal constant value type example I can think of would be one that is initialized with zeroes by just declaring it, and that's hardly useful.

As for how the compiler treats/uses constants, it will use the computed value in place of the constant name in the code.

Thus, you have the following effect:

  • No reference to the original constant name, class it was declared in, or namespace, is compiled into the code in this location
  • If you decompile the code, it will have magic numbers in it, simply because the original "reference" to the constant is, as mentioned above, not present, only the value of the constant
  • The compiler can use this to optimize, or even remove, unnecessary code. For instance, if (SomeClass.Version == 1), when SomeClass.Version has the value of 1, will in fact remove the if-statement, and keep the block of code being executed. If the value of the constant is not 1, then the whole if-statement and its block will be removed.
  • Since the value of a constant is compiled into the code, and not a reference to the constant, using constants from other assemblies will not automagically update the compiled code in any way if the value of the constant should change (which it should not!)

In other words, with the following scenario:

  1. Assembly A, contains a constant named "Version", having a value of 1
  2. Assembly B, contains an expression that analyzes the version number of assembly A from that constant and compares it to 1, to make sure it can work with the assembly
  3. Someone modifies assembly A, increasing the value of the constant to 2, and rebuilds A (but not B)

In this case, assembly B, in its compiled form, will still compare the value of 1 to 1, because when B was compiled, the constant had the value 1.

In fact, if that is the only usage of anything from assembly A in assembly B, assembly B will be compiled without a dependency on assembly A. Executing the code containing that expression in assembly B will not load assembly A.

Constants should thus only be used for things that will never change. If it is a value that might or will change some time in the future, and you cannot guarantee that all other assemblies are rebuilt simultaneously, a readonly field is more appropriate than a constant.

So this is ok:

  • public const Int32 NumberOfDaysInAWeekInGregorianCalendar = 7;
  • public const Int32 NumberOfHoursInADayOnEarth = 24;

while this is not:

  • public const Int32 AgeOfProgrammer = 25;
  • public const String NameOfLastProgrammerThatModifiedAssembly = "Joe Programmer";

Edit May 27th 2016

OK, just got an upvote, so I re-read my answer here and this is actually slightly wrong.

Now, the intention of the C# language specification is everything I wrote above. You're not supposed to use something that cannot be represented with a literal as a const.

But can you? Well, yes....

Let's take a look at the decimal type.

public class Test
{
public const decimal Value = 10.123M;
}

Let's look at what this class looks like really when looked at with ildasm:

.field public static initonly valuetype [mscorlib]System.Decimal X
.custom instance void [mscorlib]System.Runtime.CompilerServices.DecimalConstantAttribute::.ctor(int8, uint8, uint32, uint32, uint32) = ( 01 00 01 00 00 00 00 00 00 00 00 00 64 00 00 00 00 00 )

Let me break it down for you:

.field public static initonly

corresponds to:

public static readonly

That's right, a const decimal is actually a readonly decimal.

The real deal here is that the compiler will use that DecimalConstantAttribute to work its magic.

Now, this is the only such magic I know of with the C# compiler but I thought it was worth mentioning.

Why can't structs be declared as const?

Because the value type constructor might do anything -- for example, switch logic based on the time of day. Constant value types makes sense intellectually, but it simply cannot work on custom value types in practice due to the flexibility of constructors to do whatever they please. (Remember that constants are evaluated at compile time, which means your constructor would have to be run at compile time.)

Why are we allowed to use const with reference types if we may only assign null to them?

From MSDN

when the compiler encounters a constant identifier in C# source code (for example, months), it substitutes the literal value directly into the intermediate language (IL) code that it produces. Because there is no variable address associated with a constant at run time, const fields cannot be passed by reference and cannot appear as an l-value in an expression.

Because reference types (other than null, and strings which are special) need to be constructed at run time, the above would not be possible for reference types.

For reference types, the closest you can get is static readonly:

class Foo
{
// This is not a good idea to expose a public non-pure field
public static readonly StringBuilder BarBuilder = new StringBuilder();
public Foo(){
}
}

Unlike const substitution (in the calling code), static readonly creates a single shared instance of the reference type which has subtle differences if assembly versions are changed.

Although the reference cannot (normally) be reassigned, it doesn't preclude calling non-pure methods on the StringBuilder (like Append etc). This is unlike consts, where value types and strings are immutable (and arguably should be "eternal").

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

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.

Why does the declaration of a DateTime const give a compiler error but not optional parameter?

Have a look at the msdn article on optional parameters.
When the data type is a value type (enum, struct) you may initiate an optional parameter with an empty constructor:

static void Foo(string s, DateTime opt = new DateTime())

Or with the default keyword. This isn't true for reference types.

Declaring a const double[] in C#?

From MSDN (http://msdn.microsoft.com/en-us/library/ms228606.aspx)

A constant-expression is an expression
that can be fully evaluated at
compile-time. Because the only way to
create a non-null value of a
reference-type [an array] is to apply the new
operator, and because the new operator
is not permitted in a
constant-expression, the only possible
value for constants of reference-types
other than string is null.



Related Topics



Leave a reply



Submit