简体   繁体   中英

What's the reason behind not being able to use readonly fields in switch blocks?

I'm well aware that C# does not allow readonly fields in switch blocks, which is what this question addresses.

I'd like to understand why this is the case. Is it just an arbitrary language specification quirk, or is there a technical reason behind it, and if so, what is that technical reason?

Let me make it clear that I understand the difference between const and readonly , and I know that C# switch requires const values, or values known at compile time. To me, functionally, using a bunch of if..else if statements has the same outcome as using a switch statement, because whatever I can do with a switch statement I can achieve with an if as well, for example:

const int MyConstantValue = 10;

int myCompareValue = 3;
if(myCompareValue == MyConstantValue)
{
  //...
}
else
{
  //...
}

switch(myCompareValue)
{
  case MyConstantValue:
    //...
    break;
  default:
    //...
    break;
}

Both of these constructs have the same outcome: the else or default block is executed, but the if can do it without compile time constants or known values. Why can an if do that where a switch cannot?

The reason for this is that C# switches are modelled after C/C++ switches, which have the same constraint.

There are two reasons for this constraint:

  • Performance : A switch statement can be compiled into a very efficient "jump table", which wouldn't be possible if the cases were not known at compile time.
  • Correctness : A switch statement has provably unique cases at compile time, but without constant cases that would not be provable at compile time.

As this piece of documentation states, readonly fields are a runtime constant. (As opposed to const fields).

Meanwhile, switch statements expect an initialized value in compile-time.

Therefore, the compiler rejects the readonly fields.

Edit: Not an expert on the C# compiler, but it appears that it creates a branch table in the IL, as opposed to if-statements (in certain cases). For more information, check out this blog post .

To quote the relevant part:

...first time the function is called a Dictionary of strings (key) and int (value) is created and all the cases are stored in this dictionary as the key and an integer as a value is stored against it. Then for the switch statement the string is taken and is queried in the dictionary and if it is present the number value for the string is returned. Using this number the compiler creates an efficient jump table and it jumps to the target Console.Writeline string.

Now the answer :). The strings are pre-stored in the dictionary. If the strings in the case statement were not constants, changes in them won't reflect in the dictionary and hence you'd land up comparing against stale value. To avoid this inconsistency the non-constant values are not supported at all.

Obviously for dynamic values the dictionary cannot be used and hence there is no optimization possible for switch-case so one should anyway use if-then-else.

Think about what the compiler does with a switch block: it essentially builds a series of conditional branches, based on the target values of the relevant expression, which are known at compile time .

Now suppose you had this:

private readonly int N = System.DateTime.Ticks;

If you try to use N as the target value of a switch block, then the value is not known at compile time. So what should the compiler do? Although it could of course build a more-complex series of conditional branches, would that really be justified by the "utility" of this pattern?

Personally, there are other things I'd rather the compiler writers spent their time on...

Switch case labels can only be compile time constants (at least until C# 7 ships, where switch will also be useful for pattern matching). A readonly field is not a compile time constant, its a field that is initialized when code is executed.

The only difference between a readonly field and a regular field is that the former can only be initialized in a constructor or via a field initializer while the latter can be initialized / reassigned anywhere.

To see the diference, between a readonly field or a constant, simply consider the following code:

bool const True = true;

if (!true)
   throw new Exception(); //Compiler warning: unreachable code detected

return; 

Here, the compiler knows what True is at compile time and can therefore reason about the reachability of the throw statement, which is obviously unreachable; if (false) will always ignore the following block / statement.

Now consider the following:

readonly static bool True = true;

if (!true)
   throw new Exception();

return;

This will compile just fine. The compiler can not know that True is in fact true untill the class containing the field is initialized. This means code has to run and only the runtime can reason about what True really is.

As to why switch case lables need to be constant, I believe its mainly due to optimization reasons (more efficient code) and coverage analysis; the compiler can reason out if all possible cases are handled or not, something very useful in many scenarios and not possible with non constant labels.

If you need to branch based on non constant expressions then simply use if else if else .

Its because readonly variables are not constant. They can be assigned in the declaration or in the class constructor. Therefor its value is not known at design time.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM