组合使用null-checks和Pattern Matches的'if'语句时出错

问题描述 投票:12回答:2

以下按预期工作:

dynamic foo = GetFoo();

if (foo != null)
{
    if (foo is Foo i)
    {
        Console.WriteLine(i.Bar);
    }
}

但如果我像这样结合if语句:

if (foo != null && foo is Foo i)
{
    Console.WriteLine(i.Bar);
}

然后我收到编译器警告

Use of unassigned local variable 'i'

谁能解释为什么会这样?

c# pattern-matching
2个回答
8
投票

事实上,这似乎不是编译器错误。

它曾被报道为一个bug here

但是,它已被关闭,因为它不是一个bug。原因是因为C#语言规范的这一部分(注意:我在这里引用GitHub上的用户gafter - 这不是我自己的原创内容):

如果条件逻辑运算符的操作数具有动态编译时类型,则表达式是动态绑定的(动态绑定)。在这种情况下,表达式的编译时类型是动态的,下面描述的解析将在运行时使用具有编译时类型动态的那些操作数的运行时类型进行。

具体来说,&&操作不是编译时布尔短路操作,因为它的右操作数是动态类型。

微妙的东西,正如DavidG上面说的那样,另一个原因就是尽可能避免使用dynamic! (而且我必须承认,我仍然不完全相信这不是一个错误,但那只是我不理解我猜的一切......)


1
投票

好吧,我一直在考虑这个问题,看起来编译器行为非常正确,即使没有dynamic值,重现它也不是那么难。

但是,仍然需要一些奇怪的代码。

首先,我们将为&&类型重载Foo运算符。直接过载短路逻辑运算符是不可能的,因此我们将分别重载truefalse&

public static bool operator true(Foo x) => true;
public static bool operator false(Foo x) => true;
public static Foo operator &(Foo foo, Foo val) => new Foo();

最初我们在foo != null && foo is Foo i区块中有一个表达式if,现在我们希望它的&&绑定到我们的超载。出于这个原因,我们将重载!=运算符和==以及它们应该始终配对。

public static Foo operator !=(Foo val, Foo val2) => new Foo();  
public static Foo operator ==(Foo val, Foo val2) => new Foo();

目前foo != null评估为Foofoo is Foo评估为bool,但我们的&&超载有签名(Foo, Foo) - 仍然是不匹配,将为bool的隐式转换增加一个超载。

public static implicit operator Foo(bool val) => new Foo();

这是我们到目前为止的Foo类型的代码

class Foo
{
    public static bool operator true(Foo x) => true;
    public static bool operator false(Foo x) => true;
    public static Foo operator &(Foo foo, Foo val) => new Foo();

    public static implicit operator Foo(bool val) => new Foo();

    public static Foo operator !=(Foo val, Foo val2) => new Foo();
    public static Foo operator ==(Foo val, Foo val2) => new Foo();
}

瞧!我们对这件作品有同样的错误。

 static void Main(string[] args)
 {
     Foo foo = GetFoo();

     if (foo != null && foo is Foo i)
     {
         // Use of unassigned local variable i
         // Local variable 'i' might not be initialized before accessing 
         Console.WriteLine(i);
     }
 }

 static Foo GetFoo() => new Foo();

事实上,如果我们使用foo is string i而不是foo is Foo ii将不会在运行时初始化,但我们将在if块内。

由于涉及dynamic值,最初的问题相当等同。 foo != nulldynamic,至于foodynamic,所以它意味着&&应该在运行时绑定,我们无法保证i将被初始化。

看起来Matthew从github问题中引用了同样的东西,但我个人从一开始就无法理解它。

© www.soinside.com 2019 - 2024. All rights reserved.