Type-inferring a constant in C#

Viewed 5270

In C#, the following type-inference works:

var s = "abcd";

But why can't the type be inferred when the variable is a constant?

The following throws a compile-time exception:

const var s = "abcd"; // <= Compile time error: 
                      //    Implicitly-typed local variables cannot be constant
11 Answers

I'm actually hoping Lippert pops by and and takes a look at the question

If there's something you want brought to my attention, you can leave my name in the text -- not a comment -- and I'll find it eventually. Or, better, you can "tweet" to @ericlippert. Note that this does not constitute a service level agreement; I do this in my spare time.

why can't the type be inferred when the variable is a constant?

"constant" and "variable" are opposites. const var gives me the shudders to type. A constant is a value that never changes and has no storage location; a variable is a storage location whose contents change. They're completely different, so don't attempt to combine them. The var syntax was chosen to call out "this is a variable", and we're sticking with it.

var can stand in for a specific type declaration, but combining it with const severely muddies the picture of what the compiler does with the value. Therefore const var is disallowed to prevent this confusion and you have to explicitly type your constants.

I would be perfectly fine with inferred constants that do not use var:

const Pi = 3.14159;

seems fine to me. However, I know of no plans to add this to C#.

This is just a guess, but I think that the reason might have to do with the fact that const values are put in metadata (which has subtle consequences all it's own) when compiled. I wonder if maybe the compiler has some issues figuring out how to transform a var to metadata.

In Richter's CLR VIA C# (page 177),

Defining a constant causes creation of metadata. When code refers to a constant symbol, compilers look up that symbol in the metadata of the assembly that defines that constant, extract the constant's value, and embed the value in the emitted IL code.

He goes on to note that this means that you can't get the reference to memory of a constant for this reason. To make this a bit more explicit, in psuedo C# if assembly A defines a const:

//Assembly A, Class Widget defines this:
public static const System.Decimal Pi = 3.14

then you have a consumer of A:

//somewhere in the Program.exe assembly
decimal myCircleCurcum = 2 * Widget.pi

the resultant compiled IL of program.exe would do something like this pseudocode:

// pseudo-IL just to illustrate what would happen to the const
myCircleCurcum = 2*3.14

note that the consuming assembly has no idea that the decimal 3.14 had any relationship to Assembly A at all--it is to program.exe a literal value. This, to me, is a reasonable way for the C# compiler to act--after all, Assembly A declared explicitly that pi is a constant (meaning that the value is once and for all pi=3.14). But, I'd venture to guess, that 99% of C# developers do not understand the ramifications of this & might change pi to be 3.1415 on a whim.

Constants have a really poor cross-assembly version story (again, this comes from Richter) because a consumer of assembly A with a constant in it will not see a change if assembly A's constant changes (i.e. it was recompiled). This can cause really hard to figure out bugs by consumer of assembly A. . . so much so that I ban my team from using constants. Their slight perf gain is not worth the subtle bugs they can cause.

You can really only ever use a constant if you know that the value will never, ever change -- and even with something set as a const such as pi, you can't say for sure that you won't want your percision to change in the future.

if assembly A defines:

decimal const pi = 3.14

then you build it and then other assemblies consume it, if you then change assembly A:

decimal const pi = 3.1415

and rebuild assembly A, the consumer of assembly A will still have the old value 3.14! why? because the original 3.14 was defined as a constant which means that the consumers of assembly A have been told that the value won't change--so they can bake that value of pi into their own metadata (if you rebuild consumer of assembly A it will then get the new value of pi in it's metadata). Again, I don't see this as a problem with the way CSC handles constants--it's just that developers probably don't expect that a constant can't be changed safely under some circumstances, where it can be changed safely in others. Safe: no consumers will ever have reference by .dll only (i.e. they will always build from source EVERY TIME), unsafe: consumers don't have a clue about when source code of your assembly with the const defined it it changes. It probably should be made much more clear in .NET documentation that constant means you can't change the value in the sourcecode

For that reason, I'd strongly suggest not using constants and instead just making the widget readonly. How many values can you really say for certain are truly going to be const for ever and always?

The only real reason to use const over readonly in my mind is if something might have performance implications... but if you are running into that, I'd wonder if C# is really the correct language for your problem. In short, to me, it is alomst never a good idea to use constants. There are very few times where the tiny perf improvement is worth the potential problems.

The short answer is because the language designers (Microsoft) say so.

From MSDN:

Compiler Error CS0822

Error Message: Implicitly typed locals cannot be const

Implicitly typed local variables are only necessary for storing anonymous types. In all other cases they are just a convenience. If the value of the variable never changes, just give it an explicit type. Attempting to use the readonly modifier with an implicitly typed local will generate CS0106.

To correct this error

If you require the variable to be constant or readonly, give it an explicit type.

In this case it is obvious that you know the reference type will be constant, and of a fairly primitive type (consts can only be value types, or strings, etc..), so you should declare that type, rather than use implicit typing.

In other words, because the type is obviously constant and known, there's absolutely no reason to use var.

Implicitly typed local variables are only necessary for storing anonymous types. In all other cases they are just a convenience. If the value of the variable never changes, just give it an explicit type. Attempting to use the readonly modifier with an implicitly typed local will generate CS0106.

http://msdn.microsoft.com/en-us/library/bb310881.aspx

Compiler Error CS0822

To correct this error If you require the variable to be constant or readonly, give it an explicit type.

I realized that what I (we) actually wanted is the behavior of the const keyword as defined in JavaScript or C. That is, the ability to calculate it at runtime but disallow updating it in subsequent code. This can be useful to force discipline and be explicit when you only want a value to be calculated once.

In other words, what this question is really asking for is to be able to use the readonly keyword on locals (variables/method parameters). I.e., this syntax might be useful:

// This variable should never be overwritten!
readonly var target = 4;

It is not like there is no precedent for this in C#. using() and iteration (foreach) variables already behave this way:

class Program
{
    static void Main(string[] args)
    {
        foreach (var x in new[] { "asdf", })
        {
            System.Console.WriteLine(x);
            // error CS1656: Cannot assign to 'x' because it is a 'foreach iteration variable'
            x = "food";
        }
    }
}

Oh, look—I got type inference and readonly behavior! Yay! However, using the foreach keyword is way too clunky to actually do this in real code. It is not obvious at all that you’re trying to protect yourself from, uh, yourself or your coworkers adding code that mutates x later without thinking through the implications (with the help of a compiler error). That is why it would be great if it became a language feature.

Interesting. I don't know if it is just a limitation of the C# compiler or if it a fundemental limitaion of the language itself.

To explain what I mean, consider VB.

In VB 9 you also couldn't infer constants, but this was just a limitation of the compiler. In VB 10 they were able to add constant type inference without making any significant changes the to language.

IMO var main purpose is to allow anonymous types (type is unknown, with var you can declare a variable to store it). The more common usage now is to write less code ;). As they explain here if you know the type and the value (which won't change) just write the type.

Related