无法在开关块中使用只读字段的原因是什么?
What's the reason behind not being able to use readonly fields in switch blocks?
我很清楚 C# 不允许 switch
块中的 readonly
字段,这是 this question 地址。
我想了解为什么是这样。它只是一个任意的语言规范怪癖,还是背后有技术原因,如果是,技术原因是什么?
让我澄清一下,我了解 const
和 readonly
之间的区别,并且我知道 C# switch
需要 const
值,或者已知的值编译时间。
对我来说,在功能上,使用一堆 if..else if
语句与使用 switch
语句具有相同的结果,因为无论我用 switch
语句能做什么,我都可以用 if
还有,例如:
const int MyConstantValue = 10;
int myCompareValue = 3;
if(myCompareValue == MyConstantValue)
{
//...
}
else
{
//...
}
switch(myCompareValue)
{
case MyConstantValue:
//...
break;
default:
//...
break;
}
这两个构造具有相同的结果:执行 else
或 default
块,但 if
可以在没有编译时常量或已知值的情况下执行。为什么 if
可以做到 switch
做不到的事情?
正如 this 文档所述,只读字段是运行时常量。 (与常量字段相反)。
同时,switch 语句需要 compile-time 中的初始化值。
因此,编译器拒绝只读字段。
编辑: 不是 C# 编译器方面的专家,但它似乎在 IL 中创建了一个分支 table,而不是 if-statements (在某些情况下)。有关详细信息,请查看 this blog post.
引用相关部分:
...first time the function is called a Dictionary
of strings (key) and int (value) is created and all the cases are
stored in this dictionary as the key and an integer as a value is
stored against it. Then for the switch statement the string is taken
and is queried in the dictionary and if it is present the number value
for the string is returned. Using this number the compiler creates an
efficient jump table and it jumps to the target Console.Writeline
string.
Now the answer :). The strings are pre-stored in the dictionary. If
the strings in the case statement were not constants, changes in them
won’t reflect in the dictionary and hence you’d land up comparing
against stale value. To avoid this inconsistency the non-constant
values are not supported at all.
Obviously for dynamic values the dictionary cannot be used and hence
there is no optimization possible for switch-case so one should anyway
use if-then-else.
这是因为只读变量不是常量。它们可以在声明中或在 class 构造函数中赋值。因此它的值在设计时是未知的。
想想编译器对 switch
块做了什么:它本质上是根据相关表达式的目标值构建一系列条件分支,这些目标值在编译时已知 。
现在假设你有这个:
private readonly int N = System.DateTime.Ticks;
如果您尝试使用 N 作为 switch
块的目标值,则该值在编译时 未知 。那么编译器应该做什么呢?虽然它 可以 当然可以构建一个 more-complex 系列的条件分支,但这种模式的 "utility" 真的可以证明这一点吗?
就我个人而言,还有其他一些事情我更希望编译器作者花时间在...
Switch case 标签只能是编译时常量(至少在 C# 7 发布之前是这样,其中 switch
也可用于模式匹配)。 readonly
字段不是编译时常量,它是在执行代码时初始化的字段。
readonly
字段和常规字段之间的唯一区别是前者只能在构造函数中或通过字段初始化器初始化,而后者可以在任何地方初始化/重新分配。
要查看只读字段或常量之间的区别,只需考虑以下代码:
bool const True = true;
if (!true)
throw new Exception(); //Compiler warning: unreachable code detected
return;
这里,编译器知道编译时True
是什么,因此可以推断throw语句的可达性,这显然是不可达的; if (false)
将始终忽略以下块/语句。
现在考虑以下问题:
readonly static bool True = true;
if (!true)
throw new Exception();
return;
这会编译得很好。在初始化包含该字段的 class 之前,编译器无法知道 True
实际上是 true
。这意味着代码必须 运行 并且只有 运行 时间才能推断出 True
到底是什么。
至于为什么switch
case labels需要保持不变,我认为主要是出于优化原因(更高效的代码)和覆盖率分析;编译器可以推断出是否处理了所有可能的情况,这在许多情况下非常有用,而对于非常量标签则不可能。
如果您需要基于非常量表达式进行分支,那么只需使用 if
else if
else
.
原因是 C# 开关是仿照 C/C++ 开关建模的,它们具有相同的约束。
这个限制有两个原因:
- 性能:switch 语句可以编译成非常高效的"jump table",如果在编译时不知道情况,这是不可能的。
- 正确性:switch 语句在编译时具有可证明的唯一情况,但没有在编译时无法证明的常量情况。
我很清楚 C# 不允许 switch
块中的 readonly
字段,这是 this question 地址。
我想了解为什么是这样。它只是一个任意的语言规范怪癖,还是背后有技术原因,如果是,技术原因是什么?
让我澄清一下,我了解 const
和 readonly
之间的区别,并且我知道 C# switch
需要 const
值,或者已知的值编译时间。
对我来说,在功能上,使用一堆 if..else if
语句与使用 switch
语句具有相同的结果,因为无论我用 switch
语句能做什么,我都可以用 if
还有,例如:
const int MyConstantValue = 10;
int myCompareValue = 3;
if(myCompareValue == MyConstantValue)
{
//...
}
else
{
//...
}
switch(myCompareValue)
{
case MyConstantValue:
//...
break;
default:
//...
break;
}
这两个构造具有相同的结果:执行 else
或 default
块,但 if
可以在没有编译时常量或已知值的情况下执行。为什么 if
可以做到 switch
做不到的事情?
正如 this 文档所述,只读字段是运行时常量。 (与常量字段相反)。
同时,switch 语句需要 compile-time 中的初始化值。
因此,编译器拒绝只读字段。
编辑: 不是 C# 编译器方面的专家,但它似乎在 IL 中创建了一个分支 table,而不是 if-statements (在某些情况下)。有关详细信息,请查看 this blog post.
引用相关部分:
...first time the function is called a Dictionary of strings (key) and int (value) is created and all the cases are stored in this dictionary as the key and an integer as a value is stored against it. Then for the switch statement the string is taken and is queried in the dictionary and if it is present the number value for the string is returned. Using this number the compiler creates an efficient jump table and it jumps to the target Console.Writeline string.
Now the answer :). The strings are pre-stored in the dictionary. If the strings in the case statement were not constants, changes in them won’t reflect in the dictionary and hence you’d land up comparing against stale value. To avoid this inconsistency the non-constant values are not supported at all.
Obviously for dynamic values the dictionary cannot be used and hence there is no optimization possible for switch-case so one should anyway use if-then-else.
这是因为只读变量不是常量。它们可以在声明中或在 class 构造函数中赋值。因此它的值在设计时是未知的。
想想编译器对 switch
块做了什么:它本质上是根据相关表达式的目标值构建一系列条件分支,这些目标值在编译时已知 。
现在假设你有这个:
private readonly int N = System.DateTime.Ticks;
如果您尝试使用 N 作为 switch
块的目标值,则该值在编译时 未知 。那么编译器应该做什么呢?虽然它 可以 当然可以构建一个 more-complex 系列的条件分支,但这种模式的 "utility" 真的可以证明这一点吗?
就我个人而言,还有其他一些事情我更希望编译器作者花时间在...
Switch case 标签只能是编译时常量(至少在 C# 7 发布之前是这样,其中 switch
也可用于模式匹配)。 readonly
字段不是编译时常量,它是在执行代码时初始化的字段。
readonly
字段和常规字段之间的唯一区别是前者只能在构造函数中或通过字段初始化器初始化,而后者可以在任何地方初始化/重新分配。
要查看只读字段或常量之间的区别,只需考虑以下代码:
bool const True = true;
if (!true)
throw new Exception(); //Compiler warning: unreachable code detected
return;
这里,编译器知道编译时True
是什么,因此可以推断throw语句的可达性,这显然是不可达的; if (false)
将始终忽略以下块/语句。
现在考虑以下问题:
readonly static bool True = true;
if (!true)
throw new Exception();
return;
这会编译得很好。在初始化包含该字段的 class 之前,编译器无法知道 True
实际上是 true
。这意味着代码必须 运行 并且只有 运行 时间才能推断出 True
到底是什么。
至于为什么switch
case labels需要保持不变,我认为主要是出于优化原因(更高效的代码)和覆盖率分析;编译器可以推断出是否处理了所有可能的情况,这在许多情况下非常有用,而对于非常量标签则不可能。
如果您需要基于非常量表达式进行分支,那么只需使用 if
else if
else
.
原因是 C# 开关是仿照 C/C++ 开关建模的,它们具有相同的约束。
这个限制有两个原因:
- 性能:switch 语句可以编译成非常高效的"jump table",如果在编译时不知道情况,这是不可能的。
- 正确性:switch 语句在编译时具有可证明的唯一情况,但没有在编译时无法证明的常量情况。