C# 常量和编译时计算-为什么要更改此行为

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:允许这些推论继续下去有什么风险 该规范规定,仅在块内分配的内容的明确分配是不确定的。该规范没有提到编译

如果您将Eric Lippert的这篇文章转发到大约13分钟,他会描述对C#编译器所做的更改,该更改会导致以下代码无效(显然在.NET 2之前,包括.NET 2之前,此代码本应编译)

现在我清楚地了解到,上述代码的任何执行实际上都会计算为

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 stmt
else
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