我在 C# 中有两种使用
switch
表达式的方法,一种使用整数,另一种使用字符串:
public int Int(int value)
{
int sum = value switch
{
0 => 0,
1 => 1,
2 => 2,
_ => throw new System.Exception()
};
return sum;
}
public int String(string value)
{
int sum = value switch
{
"0" => 0,
"1" => 1,
"2" => 2,
_ => throw new System.Exception()
};
return sum;
}
反编译后的代码(Release模式,C#12)为:
// Integer method
public int Int(int value)
{
switch (value)
{
case 0:
return 0;
case 1:
return 1;
case 2:
return 2;
default:
throw new Exception();
}
}
// String method
public int String(string value)
{
if (!(value == "0"))
{
if (!(value == "1"))
{
if (value == "2")
{
return 2;
}
throw new Exception();
}
return 1;
}
return 0;
}
我检查了IL甚至ASM,如果我正确理解代码的话,整数开关使用跳转表,而字符串开关依赖于字符比较。
我使用 BenchmarkDotNet 测试了性能,发现 String 方法更快。此外,当我手动为整数方法实现 if 样式比较时,它的性能明显更好。虽然这主要不是一个与性能相关的问题,但这些结果凸显了为什么理解编译器优化这些情况的差异对我来说很重要。
我的问题:
为什么 C# 编译器选择将字符串
switch
表达式优化为 if 链,而不对整数 switch
表达式应用类似的优化?
如果您能深入了解 switch 语句的这些不同编译器优化背后的基本原理,我们将不胜感激!
int
输入的方法使用跳转表,它会立即计算:基本上,编译器通过索引从数组中选择一个地址。
string
的方法只是一系列 if / else if
检查,一次一个。反编译后的代码混乱;人类会把它写成:
if (value == "0")
return 0;
else if (value == "1")
return 1;
else if (value == "2")
return 2;
else
throw new Exception ();
这比较慢,因为它必须评估第一个条件;如果失败,它将评估第二个条件,依此类推。此外,字符串比较本身很慢。
基本上,switch on strings 只是
if / else if
的语法糖。这是相对于 C++ 的生活质量的改进,在 C++ 中你根本无法打开字符串,并且必须手动编写 if / else if
链。
至于为什么编译器不能对字符串使用跳转表,问题在于我们一般无法将字符串简化为单个小数字来用作跳转表中的索引。您使用的整数 0、1 和 2 基本上需要一个跳转表,但字符串则不需要那么多。