Unexpected results after optimizing switch case in Visual Studio with C#8.0

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.

Tags:

C#

C# 8.0