Неожиданные результаты после оптимизации case switch в Visual Studio с C # 8.0 - PullRequest
19 голосов
/ 03 августа 2020

Сегодня, когда я кодировал, Visual Studio сообщила мне, что мой корпус коммутатора можно оптимизировать. Но код, который у меня был, по сравнению с кодом, который визуальная студия сгенерировала из моего переключателя, не дает такого же результата.

Используемое мной перечисление:

public enum State
{
    ExampleA,
    ExampleB,
    ExampleC
};

После выполнения следующего кода значение равно 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;
}

Но когда Visual Studio оптимизировала случай переключения, значение становится 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()
};

Это просто код с информацией, воспроизводящей ошибочную вывод, а не фактический код, который выполняется в производственной среде. Что мне показалось странным, так это то, что если я закомментирую строку State.ExampleA в последнем блоке кода, будет записано правильное значение.

Мой вопрос: это ошибка? Или мне что-то здесь не хватает?

Ответы [ 2 ]

25 голосов
/ 03 августа 2020

Это подчеркивает разницу между оператором и выражением . Переключатель, который у вас был раньше, был оператором switch , и это было запущенное присвоение.

value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);

Здесь вы конвертируете uint (правая часть) в double (левая сторона). Фактически вы выполняли разные преобразования в каждой ветке вашего оператора switch, и это было нормально, потому что это отдельные операторы присваивания.

Сравните это с тем, что вы делаете после оптимизации: оператор switch стал переключатель выражение . И выражения имеют единственный тип . Каков тип этого выражения?

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()
}

Каждая ветвь переключателя возвращает другой тип - float, uint и short соответственно. Итак, C# необходимо найти тип, в который все три из них могут быть неявно преобразованы . И float найдено. C# не может просто «выяснить», что коммутатор возвращает во время выполнения, и выработать преобразование для выполнения «динамически».

Вещи, возвращаемые в каждой ветке, должны быть сначала преобразованы в float . Следовательно, тип выражения целое - float. Наконец, вы назначаете float value, что является double.

Таким образом, общее преобразование составляет uint -> float -> double, что приводит к потере точности .

8 голосов
/ 03 августа 2020

Это будет работать:

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 возвращает float, поэтому компилятор выводит float (между float, uint и short) как тип вывода для выражения switch (и приведение к нему uint и short), а затем приводит его результат к double, что приводит к потере точности для случая ExampleB.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...