问题
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?
回答1:
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.
回答2:
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.
来源:https://stackoverflow.com/questions/63226902/unexpected-results-after-optimizing-switch-case-in-visual-studio-with-c8-0