【问题标题】:C# 7 Compiler Error - Pattern MatchingC# 7 编译器错误 - 模式匹配
【发布时间】:2017-11-11 19:59:12
【问题描述】:

出于某种原因,M1() 会导致编译器错误,而执行相同操作的M2() 不会导致错误。知道为什么吗?

使用false == 应该与使用非运算符! 相同。

使用未赋值的局部变量“i”

class Program {
    static void Main(string[] args) {
        int x = 8;

        M1(x);
        M2(x);
    }  // Main()

    public static void M1(Object obj) {
        if (false == (obj is int i))  // Causes ERROR on WriteLine
            return;

        System.Console.WriteLine(i); // Use of unassigned local variable 'i'
    }

    public static void M2(Object obj) {
        if (!(obj is int i))  // OKAY
            return;

        System.Console.WriteLine(i);
    }
} // class Program

【问题讨论】:

  • 似乎 i 超出了 M1 的范围 - 边缘情况查找?
  • @PatrickArtner 显然不是。如果i 超出范围,则错误不会显示“未分配的局部变量”。您只能为范围内的变量获取该错误。
  • @Aominè 我明白了。对骑士一无所知。我怀疑需要仔细阅读实际规范才能获得正确答案... Docs (docs.microsoft.com/en-us/dotnet/csharp/pattern-matching) 建议 if (a is Foo b) 检查“表达式肯定分配匹配变量” - 很有可能规范隐含地禁止所有其他比较真/假。
  • 顺便说一句,是时候放弃那些声称== false! 相同的帖子了^_^
  • @M.kazemAkhgary,声称 == false! 在 C# 7 之前已经是错误的,因为可空布尔值的行为不同。对于bool? b = null;if (b == false) 编译得很好,但if(!b) 抱怨无法从bool? 转换为bool

标签: c# c#-7.0


【解决方案1】:

这里的问题在于编译器处理“当真时绝对赋值”的方式。 ! 将其反转; == false 没有。所以对于代码:

if (!(obj is int i))
        return;

System.Console.WriteLine(i);

编译器可以推断如果obj is int i 为假,则! 会反转,因此如果不是int,则会出现return。因此可以允许i 安全地“泄漏”到后续代码中。

但是,相同的规则不适用于== false。虽然在语义上与人类阅读代码相同,但编译器将 !== false 视为非常不同的事物。所以对于:

if (false == (obj is int i))

编译器犹豫并认为它无法知道i的赋值状态,因此出现错误。

有关此问题的讨论,请参阅Incorrect "Use of unassigned local variable" for (x is T y) == false

故事的寓意:避免与false 比较,在使用C# 模式时使用!

编辑

需要注意的是== false在这里并不是特例。对== 的任何使用都会消除编译器确定“当为真时确定分配”的能力。例如下面的代码编译:

object x = 1;
if (!(x is bool y))
    return 0;

var z = y;

但是添加一个== true 并且它不再这样做了:

object x = 1;
if (!(x is bool y == true))
    return 0;

var z = y; // error: use of an unassigned variable

EDIT2

顺便说一句,对于那些使用if (expression == false) 的人来说,因为他们发现if (!expression) 难以阅读,您可能有兴趣知道the syntax, if !(expression) is being considered for C# 8

【讨论】:

  • ! 仍然是一个运算符,它具有像 == 一样的实现。为什么编译器不能优化== false?请注意,== false 部分是不变的。它可以被优化,但不幸的是编译器在这样做之前会发出错误。
  • @M.kazemAkhgary,很可能它可以这样改进。如果您对此感到满意,请raise an issue on the C# language repo 提出要求。
  • @DavidArno 那里已经存在问题:github.com/dotnet/csharplang/issues/801
  • @Evk,嗯,我看到我对这个问题发表了评论!我完全忘记了它
  • 鉴于问题不在模式匹配中,您可以提供不使用模式匹配但 C# 编译器返回相同错误的情况。例如:int x; bool t = true; if((t && int.TryParse("1", out x)) == true) { Console.WriteLine(x); }
【解决方案2】:

很好的发现,这就是我的想法。请注意,这绝对是可以解决的问题,但我正在努力寻找原因。请注意,这不是准确的答案。只是说!

不仅是false == 导致了这个问题,而且== true 导致i 在任何一个分支中都无法使用,所以我继续写了这个。

var x = obj is int i;
if(x) Console.WriteLine(i);

你得到同样的错误。如您所见,如果x 为真,那么i 应该被初始化,对吧?除非您开始使用 x 值!这里x 不是常量,因此我们不能保证x 在执行if 语句之前始终为真。

编译器如何在编译时计算常量值和表达式。我不确定这里发生了什么,但我认为是

if((obj is int i) == false)

这里== 运算符在模式匹配结果和评估 if 语句之间设置了一个间隙,因此出现了错误。我不知道为什么 ! 操作员工作正常,也许他们优化了那部分但忘记优化这个? ;)

我认为这与编译器的语义分析阶段有关,它的工作是验证代码的含义并确定其执行方式,如果有问题,是否存在未定义行为的可能性或完全没有意义的事情,它失败,你得到编译时错误。阅读更多关于Phases of Compiler Design

【讨论】: