Today while coding, visual studio notified me that my switch case could be optimized. But the code that I had vs the code that visual studio generated from my switch case does not result in the same outcome.
The Enum I Used:
public enum State
{
ExampleA,
ExampleB,
ExampleC
};
After the following code runs the value is equal to 2147483647.
State stateExample = State.ExampleB;
double value;
switch (stateExample)
{
case State.ExampleA:
value = BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0);
break;
case State.ExampleB:
value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);
break;
case State.ExampleC:
value = BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0);
break;
default:
value = 0;
break;
}
But when visual studio optimized the switch case, the value becomes 2147483648.
State stateExample = State.ExampleB;
double value = stateExample switch
{
State.ExampleA => BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
_ => throw new InvalidOperationException()
};
This is just the code with information that reproduced the erroneous output and not actual code that is run in production. What I found weird was that if I comment out the line State.ExampleA
in the last code block the correct value is written.
My Question is: Is this a bug? Or am I missing something here?
The biggest problem with switch statements, in general, is that they can be a code smell. Switch overuse might be a sign that you're failing to properly employ polymorphism in your code.
Important points to C Switch CaseOnly Integral values are allowed inside the case label, and the expression inside the switch must evaluate to a single integral constant. Variables are not allowed inside the case label, but integer/character variables are allowed for switch expression. Break and default are optional.
In a switch statement, the “case value” can be of “char” and “int” type. Following are some of the rules while using the switch statement: 1. There can be one or N numbers of cases.
The default statement is executed if no case constant-expression value is equal to the value of expression . If there's no default statement, and no case match is found, none of the statements in the switch body get executed.
This highlights the difference between a statement and an expression. The switch you had before, was a switch statement and this was the assignment that got run.
value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);
Here you are converting an uint
(right hand side) to a double
(left hand side). You were actually doing a different conversion in each branch of your switch statement, and this was fine, because well, they are separate assignment statements.
Compare that to what you are doing after the optimisation: the switch statement became a switch expression. And expressions have a single type. What is type of this expression?
stateExample switch
{
State.ExampleA => BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
_ => throw new InvalidOperationException()
}
Each branch of the switch returns a different type - float
, uint
and short
respectively. So C# needs to find a type to which all three of these can be implicitly converted. And float
is found. C# can't just "figure out" what the switch returns at runtime and work out the conversion to perform "dynamically".
The things returned in each branch has to be first converted to a float
. Therefore, the type of the whole expression is float
. Finally, you assign the float
to value
, which is a double
.
So the overall conversion is uint
-> float
-> double
, causing a loss of precision.
This will work:
double value1 = stateExample switch
{
State.ExampleA => (double)BitConverter.ToSingle(BitConverter.GetBytes((long)2147483646), 0), //Commenting this line results in correct value
State.ExampleB => BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0),
State.ExampleC => BitConverter.ToInt16(BitConverter.GetBytes((long)2147483648), 0),
_ => throw new InvalidOperationException()
};
BitConverter.ToSingle
returns float
so compiler infers float
(between float
, uint
and short
) as output type for the switch expression (and casting uint
and short
to it) and then casts it's result to double
which results in precision loss for ExampleB
case.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With