Ошибка объединения команд "if", которые проверяют нуль и совпадения шаблонов

Следующее работает, как ожидалось:

dynamic foo = GetFoo();

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

но если я скомбинирую так:

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

то я получаю предупреждение компилятора

Use of unassigned local variable 'i'

Может ли кто-нибудь объяснить, почему это происходит?

Ответ 1

По-видимому, это не ошибка компилятора.

Как сообщалось ранее, как ошибка здесь.

Однако он был закрыт как не ошибка. Причина в том, что эта часть спецификации языка С# (примечание: я цитирую здесь от пользователя gafter на GitHub - это НЕ оригинальное содержимое от меня самого):

Если операнд условного логического оператора имеет динамику типа времени компиляции, то выражение динамически связано (динамическое связывание). В этом случае тип времени компиляции выражения является динамическим, и описанное ниже разрешение будет иметь место во время выполнения с использованием типа времени выполнения тех операндов, которые имеют динамику типа времени компиляции.

В частности, операция && не является логической операцией короткого замыкания во времени компиляции, потому что ее правый операнд имеет тип dynamic.

Тонкие вещи, и, как сказал DavidG, еще одна причина избежать dynamic где это возможно! (И я должен признаться, я все еще не полностью убежден, что это не ошибка, но что только я не понимаю все, что я думаю...)

Ответ 2

Хорошо, я некоторое время думал об этой проблеме, и похоже, что поведение компилятора довольно чертовски правильное, и его не так сложно воспроизвести даже без dynamic значений.

Тем не менее потребуется какой-то странный код.

Для начала перегрузим оператор && для нашего типа Foo. Невозможно напрямую перегрузить логические операторы короткого замыкания, поэтому мы будем перегружать true, false и & отдельно.

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 in if block, теперь мы хотим && от него привязаться к нашей перегрузке. По этой причине мы будем перегружать != Operator и == а также должны быть всегда парными.

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

Пока foo != null оценивает Foo и foo 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 i, i не буду инициализироваться во время выполнения, но мы будем внутри if block.

Первоначальная проблема довольно эквивалентна из-за dynamic значений. foo != null является dynamic, поскольку foo является dynamic, поэтому означает, что && должно быть привязано во время выполнения, и у нас нет гарантий, что i буду инициализирован.

Похоже, что Матфей цитировал то же самое из проблемы github, но лично я не мог понять это с самого начала.