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:
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.