使用 C#8.0 优化 Visual Studio 中的 switch case 后出现意外结果
Unexpected results after optimizing switch case in Visual Studio with C#8.0
今天在编码时,visual studio 通知我可以优化我的 switch case。但是我拥有的代码与 visual studio 从我的 switch case 生成的代码不会产生相同的结果。
我使用的枚举:
public enum State
{
ExampleA,
ExampleB,
ExampleC
};
在下面的代码 运行s 之后,值等于 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优化switch case时,这个值变成了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
行,则会写入正确的值。
我的问题是:这是一个错误吗?或者我在这里遗漏了什么?
这会起作用:
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
所以编译器 infers float
(介于 float
、uint
和 short
) 作为 switch 表达式的输出类型(并将 uint
和 short
转换为它),然后将其结果转换为 double
,这会导致 ExampleB
情况下的精度损失。
这突出了语句和表达式之间的区别。你之前的开关是一个开关语句,这是得到运行.
的赋值
value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);
此处您正在将 uint
(右侧)转换为 double
(左侧)。实际上,您在 switch 语句的每个分支中都进行了不同的转换,这很好,因为它们是单独的赋值语句。
将其与优化后所做的进行比较:switch 语句变成了 switch expression。 expressions 有一个 type。这个表达式的类型是什么?
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()
}
开关的每个分支 returns 不同的类型 - 分别为 float
、uint
和 short
。 因此 C# 需要找到一种所有这三种类型都可以隐式转换的类型。并找到 float
。 C# 不能仅仅“找出” 运行 时的开关 returns 并计算出“动态”执行的转换。
每个分支返回的东西都要先转换成一个float
。因此,整个表达式的类型是float
。最后,您将 float
分配给 value
,这是一个 double
.
所以整体转换为uint
-> float
-> double
,造成精度损失
可能和其他人一样,我也想到了这个问题,因为我真的对 switch 表达式的这些语义感到惊讶。这是一个类似于我最初的例子:
{
object left = 1;
object value;
switch (left)
{
case int previousInt: // <-- this case gets used
value = previousInt + 1;
break;
case double previousDouble:
value = previousDouble + 2;
break;
default:
throw new InvalidOperationException();
}
// Output:
// 2
// System.Int32 (expected)
Console.WriteLine(value);
Console.WriteLine(value.GetType());
}
现在,我的 IDE (JetBrains Rider) 建议我可以将其转换为 switch 表达式。最终结果是这样的:
{
object left = 1;
object value = left switch
{
int previousInt => previousInt + 1, // <-- this "case" gets used
double previousDouble => previousDouble + 2,
_ => throw new InvalidOperationException()
};
// Output:
// 2
// System.Double (NOT what I expected)
Console.WriteLine(value);
Console.WriteLine(value.GetType());
}
此问题的根本原因与此问题中发布的相同。表达式只有一种类型,double
和 int
都可以隐式转换为 double
(但 不能 转换为 int
! ) => 这是 C# 编译器选择的表达式的目标类型。
底线:使用 switch 表达式时要非常小心,尤其是在我的用例中使用 object
等松散类型的变量时。在这种情况下写出变量的类型可能更好(也更安全),使 reader 更明显(嘿,那是 you 只是一些days/weeks/months从现在开始!)代码如何工作。
今天在编码时,visual studio 通知我可以优化我的 switch case。但是我拥有的代码与 visual studio 从我的 switch case 生成的代码不会产生相同的结果。
我使用的枚举:
public enum State
{
ExampleA,
ExampleB,
ExampleC
};
在下面的代码 运行s 之后,值等于 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优化switch case时,这个值变成了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
行,则会写入正确的值。
我的问题是:这是一个错误吗?或者我在这里遗漏了什么?
这会起作用:
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
所以编译器 infers float
(介于 float
、uint
和 short
) 作为 switch 表达式的输出类型(并将 uint
和 short
转换为它),然后将其结果转换为 double
,这会导致 ExampleB
情况下的精度损失。
这突出了语句和表达式之间的区别。你之前的开关是一个开关语句,这是得到运行.
的赋值value = BitConverter.ToUInt32(BitConverter.GetBytes((long)2147483647), 0);
此处您正在将 uint
(右侧)转换为 double
(左侧)。实际上,您在 switch 语句的每个分支中都进行了不同的转换,这很好,因为它们是单独的赋值语句。
将其与优化后所做的进行比较:switch 语句变成了 switch expression。 expressions 有一个 type。这个表达式的类型是什么?
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()
}
开关的每个分支 returns 不同的类型 - 分别为 float
、uint
和 short
。 因此 C# 需要找到一种所有这三种类型都可以隐式转换的类型。并找到 float
。 C# 不能仅仅“找出” 运行 时的开关 returns 并计算出“动态”执行的转换。
每个分支返回的东西都要先转换成一个float
。因此,整个表达式的类型是float
。最后,您将 float
分配给 value
,这是一个 double
.
所以整体转换为uint
-> float
-> double
,造成精度损失
可能和其他人一样,我也想到了这个问题,因为我真的对 switch 表达式的这些语义感到惊讶。这是一个类似于我最初的例子:
{
object left = 1;
object value;
switch (left)
{
case int previousInt: // <-- this case gets used
value = previousInt + 1;
break;
case double previousDouble:
value = previousDouble + 2;
break;
default:
throw new InvalidOperationException();
}
// Output:
// 2
// System.Int32 (expected)
Console.WriteLine(value);
Console.WriteLine(value.GetType());
}
现在,我的 IDE (JetBrains Rider) 建议我可以将其转换为 switch 表达式。最终结果是这样的:
{
object left = 1;
object value = left switch
{
int previousInt => previousInt + 1, // <-- this "case" gets used
double previousDouble => previousDouble + 2,
_ => throw new InvalidOperationException()
};
// Output:
// 2
// System.Double (NOT what I expected)
Console.WriteLine(value);
Console.WriteLine(value.GetType());
}
此问题的根本原因与此问题中发布的相同。表达式只有一种类型,double
和 int
都可以隐式转换为 double
(但 不能 转换为 int
! ) => 这是 C# 编译器选择的表达式的目标类型。
底线:使用 switch 表达式时要非常小心,尤其是在我的用例中使用 object
等松散类型的变量时。在这种情况下写出变量的类型可能更好(也更安全),使 reader 更明显(嘿,那是 you 只是一些days/weeks/months从现在开始!)代码如何工作。