非shortcircuting布尔运算符和C#7模式匹配

BMI*_*I24 10 c# c#-7.0

我目前正在编写一个针对.NET 4.7(C#7)的C#应用​​程序.我尝试使用"is"关键字声明变量的新方法后感到困惑: if (variable is MyClass classInstance) 这种方式有效,但在执行时:

if (true & variable is MyClass classInstance)
{
    var a = classInstance;
}
Run Code Online (Sandbox Code Playgroud)

Visual Studio(我使用的是2017)向我展示了错误Use of unassigned local variable 'classInstance'.使用&(&&)的短循环版本,它工作正常.我错过了关于&操作员的事情吗?(我知道使用shortcircuting版本更常用,但此时我只是好奇)

Joh*_* Wu 10

这个伤到了我的脑袋,但我想我已经明白了.

这种混淆是由两个怪癖引起的:is操作离开变量unclared(非null)的方式,以及编译器优化掉布尔运算但不是按位运算的方式.

Quirk 1.如果强制转换失败,则变量未分配(非空)

根据is语法文档:

如果exp为true且与if语句一起使用,则分配varname并且仅在if语句中具有本地范围.

如果您在行之间读取,这意味着如果转换is失败,则该变量被视为未分配.这可能是违反直觉的(有些人可能会认为是null相反的).这意味着内的任何码if是否存在依赖于所述可变块将不编译任何机会的总体if条款可以计算为true不存在的类型匹配.所以举个例子

这编译:

if (instance is MyClass y)
{
    var x = y;
}
Run Code Online (Sandbox Code Playgroud)

这编译:

if (true && instance is MyClass y)
{
    var x = y;
}
Run Code Online (Sandbox Code Playgroud)

但这不是:

void Test(bool f)
{
    if (f && instance is MyClass y)
    {
        var x = y;  //Error: Use of unassigned local variable
    }
}
Run Code Online (Sandbox Code Playgroud)

Quirk 2.布尔运算被优化掉,而二进制运算则没有

当编译器检测到预定义的布尔结果时,编译器将不会发出无法访问的代码,并因此跳过某些验证.例如:

这编译:

void Test(bool f)
{
    object neverAssigned;
    if (false && f)
    {
        var x = neverAssigned;  //OK (never executes)
    }
}
Run Code Online (Sandbox Code Playgroud)

但如果你使用&而不是&&,它不会编译:

void Test(bool f)
{
    object neverAssigned;
    if (false & f)
    {
        var x = neverAssigned;  //Error: Use of unassigned local variable
    }
}
Run Code Online (Sandbox Code Playgroud)

当编译器看到类似的东西时true &&,完全忽略它.从而

    if (true && instance is MyClass y)
Run Code Online (Sandbox Code Playgroud)

完全一样

    if (instance is MyClass y)
Run Code Online (Sandbox Code Playgroud)

但是这个

    if (true & instance is MyClass y)
Run Code Online (Sandbox Code Playgroud)

不一样.编译器仍然需要发出执行&操作的代码并在条件语句中使用其输出.或者即使它没有,当前的C#7编译器显然执行与它相同的验证.这可能看起来有点奇怪,但要记住,当你使用&而不是&&,有一个&必须执行的保证,虽然在这个例子中似乎不重要,但一般情况必须允许额外的复杂因素,例如运算符重载.

怪癖是如何结合的

在最后一个示例中,if子句的结果是在运行时确定的,而不是在编译时确定的.因此,编译器无法确定在执行块y内容之前最终会被分配if.因此,你得到

    if (true & instance is MyClass y)
    {
        var x = y; //Error: use of unassigned local variable
    }
Run Code Online (Sandbox Code Playgroud)

TLDR

在复合逻辑运算的情况下,if当且仅当转换成功时,c#才能确定整体条件将解析为true.如果不确定,它就不能允许访问变量,因为它可能是未分配的.当表达式可以在编译时简化为非复合操作时,例如通过删除,会产生异常true &&.

解决方法

我认为我们使用新is语法的方式是带有if子句的单个条件.true &&在开头添加是有效的,因为编译器只是删除它.但是,与新语法相结合的任何其他内容都会在代码块运行时对新变量是否处于未分配状态产生歧义,而编译器也不允许这样做.

解决方法当然是嵌套条件而不是组合它们:

不会工作:

void Test(bool f)
{
    if (f & instance is MyClass y)
    {
        var x = y;  //Error: Use of unassigned local variable
    }
}
Run Code Online (Sandbox Code Playgroud)

工作良好:

void Test(bool f)
{
    if (f)
    {
        if (instance is MyClass y)
        {
            var x = y;  //Works
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*iho 1

我是否遗漏了有关 & 运算符的信息?

不,我不这么认为。你的期望对我来说似乎是正确的。考虑这个替代示例,它编译时没有错误:

object variable = null;
MyClass classInstance;

if (true & ((classInstance = variable as MyClass) != null))
{
    var a = classInstance;
}

var b = classInstance;
Run Code Online (Sandbox Code Playgroud)

(对我来说,考虑体外if分配更有趣,因为那是短路会影响行为的地方。)

通过显式赋值,编译器会在和classInstance的赋值中将其识别为明确赋值。它应该能够使用新语法做同样的事情。ab

从逻辑上讲and,短路与否并不重要。您的第一个值是true,因此应始终需要评估后半部分以获得整个表达式。正如您所指出的,编译器确实以不同的方式对待&&&,这是意想不到的。

此代码的一个变体是:

static void M3()
{
    object variable = null;

    if (true | variable is MyClass classInstance)
    {
        var a = classInstance;
    }
}
Run Code Online (Sandbox Code Playgroud)

编译器在使用classInstance时正确识别为未明确分配||,但具有相同的明显错误行为|(即也表示classInstance未明确分配),即使使用非短路运算符,无论如何分配都必须发生。

同样,上面的方法可以在显式赋值的情况下正常工作,而不是使用新语法。

如果这只是关于新语法没有解决的明确赋值规则,那么我预计&&会像 一样被破坏&。但事实并非如此。编译器会正确处理该问题。事实上,在功能文档中(我不太愿意说“规范”,因为还没有 ECMA 批准的 C# 7 规范),它写道:

type_pattern 既测试表达式是否属于给定类型,又在测试成功时将其强制转换为该类型。这引入了由给定标识符命名的给定类型的局部变量。当模式匹配操作的结果为 true 时,该局部变量肯定会被赋值。 [强调我的]

由于短路会在没有模式匹配的情况下产生正确的行为,并且由于模式匹配会在没有短路的情况下产生正确的行为(并且在功能描述中明确指定了明确的赋值),因此我认为这直接是一个编译器错误。非短路布尔运算符与模式匹配表达式的计算方式之间可能存在一些被忽视的交互,导致明确的赋值在随机播放中丢失。

您应该考虑向当局报告。我认为现在Roslyn GitHub 问题跟踪是他们跟踪此类事情的地方。如果您在报告中解释如何发现这一点以及为什么该特定语法在您的场景中很重要(因为在您发布的代码中,运算&&符的工作原理相同......非短路&似乎没有带来任何优势,这可能会有所帮助到代码)。