C# 常量和编译时计算-为什么要更改此行为
如果您将Eric Lippert的这篇文章转发到大约13分钟,他会描述对C#编译器所做的更改,该更改会导致以下代码无效(显然在.NET 2之前,包括.NET 2之前,此代码本应编译) 现在我清楚地了解到,上述代码的任何执行实际上都会计算为C# 常量和编译时计算-为什么要更改此行为,c#,compiler-construction,compiler-optimization,inference,C#,Compiler Construction,Compiler Optimization,Inference,如果您将Eric Lippert的这篇文章转发到大约13分钟,他会描述对C#编译器所做的更改,该更改会导致以下代码无效(显然在.NET 2之前,包括.NET 2之前,此代码本应编译) 现在我清楚地了解到,上述代码的任何执行实际上都会计算为 int y; int x = 10; y = 123; Console.Write(y); 但我不明白的是,为什么认为让下面的代码可编译是“可取的”?IE:允许这些推论继续下去有什么风险 该规范规定,仅在块内分配的内容的明确分配是不确定的。该规范没有提到编译
int y;
int x = 10;
y = 123;
Console.Write(y);
但我不明白的是,为什么认为让下面的代码可编译是“可取的”?IE:允许这些推论继续下去有什么风险 该规范规定,仅在
块内分配的内容的明确分配是不确定的。该规范没有提到编译器的魔力,可以删除不必要的if
块。特别是,当您更改if
条件时,它会产生一条非常令人困惑的错误消息,并突然得到一条关于未分配y
的错误消息“嗯?分配y时我没有改变!”
编译器可以自由地执行它想要的任何明显的代码删除,但是首先它需要遵循规则的规范
具体而言,第5.3.3.5节(MS 4.0规范):
5.3.3.5 If声明
对于形式为的if语句stmt:
if(
expr)
then stmtelse
else stmt
- v在expr的开头与stmt的开头具有相同的确定赋值状态
- 如果v在expr的末尾被明确分配,那么它在控制流传输上被明确分配到then stmt和else stmt或stmt的端点(如果没有else子句)
- 如果v在expr结尾处的状态为“true expression后明确赋值”,则它在控制流传输上明确赋值给then stmt,而在控制流传输上不明确赋值给else stmt或stmt的端点(如果没有else子句)
- 如果v在expr的结尾处具有状态“false expression后明确指定”,则它在控制流传输到else stmt时明确指定,而在控制流传输到then stmt时不明确指定。当且仅当它在stmt的端点处被明确指定时,才在stmt的端点处被明确指定
- 否则,在控制流传输中,v被认为没有明确分配到then stmt或else stmt,或者如果没有其他stmt,v被分配到stmt的端点
对于一个最初未分配的变量来说,它被认为是在某个特定的位置被明确分配的,对该变量的分配必须发生在通向该位置的每个可能的执行路径中
从技术上讲,执行路径存在于if
条件为false的地方;如果在else
中也分配了y
,则可以,但。。。如果
条件始终为真,则规范明确不要求指出。我仍然觉得这个问题有点困惑,但让我看看是否可以将问题重新表述为我可以回答的形式。首先,让我重申问题的背景:
在C#2.0中,此代码:
int x = 123;
int y;
if (x * 0 == 0)
y = 345;
Console.WriteLine(y);
被当作是你写的
int x = 123;
int y;
if (true)
y = 345;
Console.WriteLine(y);
这反过来又被视为:
int x = 123;
int y;
y = 345;
Console.WriteLine(y);
这是一个法律程序
但在C#3.0中,我们采取了突破性的改变来防止这种情况。编译器不再将该条件视为“始终为真”,尽管您和我都知道它始终为真。我们现在将其设置为非法程序,因为编译器认为它不知道“if”的主体始终被执行,因此不知道局部变量y始终在使用之前被赋值
为什么C#3.0行为是正确的
这是正确的,因为规范规定:
- 常量表达式只能包含常量
不是常量表达式,因为它包含一个非常量项,x*0==0
x
- 只有当条件是一个等于
的常量表达式时,true
的结果才知道总是可到达的if
y
归类为明确指定
为什么常量表达式只包含常量是可取的
我们希望C#语言能够被用户清楚地理解,并且能够被编译器编写者正确地实现。要求编译器对表达式的值进行所有可能的逻辑推断,这与这些目标背道而驰。确定一个给定表达式是否为常数应该很简单,如果是的话,它的值是多少。简单地说,常量求值代码应该知道如何执行算术,但不需要知道有关算术操作的事实。常数计算器知道如何乘以2*1,但它不需要知道“1是整数上的乘法恒等式”这一事实
现在,编译器编写者可能会认为他们在某些方面很聪明,从而生成更优化的代码。编译器编写者可以这样做,但不能改变代码是否合法。只有在给定合法代码时,才允许他们进行优化,使编译器的输出更好
这个bug是如何在C#2.0中出现的
所发生的事情是,编写编译器是为了过早地运行算术优化器。优化器应该是聪明的,它应该在程序被确定为合法后运行。它是在程序被确定为合法之前运行的,因此影响了结果
这是一个潜在的brea
int x = 123;
int y;
y = 345;
Console.WriteLine(y);
(int x)=>x * 0 == 0
(int x)=>true
()=>2 + 3