Почему этот код недоступен?

Я нашел случай, когда у меня есть код, который, как я считаю, недоступен и не обнаружен. Предупреждение не выдаётся ни компилятором, ни Visual Studio.

Рассмотрим этот код:

enum Foo { A, B, C }
class Bar { public Foo type; }

static class Program
{
    private static void Main()
    {
        var bar = new Bar { type = Foo.A };

        if (bar.type == Foo.B)
        {
            Console.WriteLine("lol");
        }
    }
}

Очевидно, что программа не будет печатать "lol", потому что условие в выражении if ложно. Я не понимаю, почему предупреждение не выдано для недостижимого кода. Моя единственная гипотеза заключается в том, что это потенциально возможно достичь, если у вас есть условие гонки в многопоточной программе. Это правильно?

Ответ 1

Статический анализ может сделать только так много, и он будет отмечать код как недостижимый, если он может доказать, что значение не может быть изменено. В вашем коде то, что происходит внутри Bar, выходит за рамки потока методов и не может быть статически аргументировано. Что, если конструктор Bar запускает поток, который устанавливает значение type обратно в B? Компилятор не может знать об этом, потому что, опять же, внутренние элементы Bar не привязаны к методу.

Если ваш код проверял значение локальной переменной, тогда компилятор мог знать, не существует ли способа изменить его. Но это не так.

Ответ 2

Спецификация С# говорит,

Первая встроенная инструкция оператора if достижима, если оператор if доступен, а логическое выражение не имеет постоянного значения false.

и, относительно постоянных выражений,

Постоянное выражение должно быть нулевым литералом или значением с одним из следующих типов: sbyte, byte, short, ushort, int, uint, long, ulong, char, float, double, decimal, bool, object, string или any тип перечисления.

В постоянных выражениях допускаются только следующие конструкции:

  • Литералы (включая null литерал).
  • Ссылки на константные члены классов и типов структур.
  • Ссылки на члены типов перечисления.
  • Ссылки на константные параметры или локальные переменные
  • Подстрочные выражения, которые сами по себе являются постоянными выражениями.
  • Выражения Cast, если целевой тип является одним из перечисленных выше типов. проверенные и непроверенные выражения
  • Выражения по умолчанию
  • Предопределенные +, ! , и ~ унарные операторы.
  • Предопределенные +, , *, /, %, <<, >>, &, |, ^, &&, || , == !=, <, >, <=, and >= двоичные операторы, если каждый операнд имеет тип, указанный выше.
  • Оператор ?: условный.

Выражения доступа членов не входят в этот список, поэтому логическое выражение не является постоянным. Таким образом, тело блока if доступно.

Ответ 3

Потому что во время компиляции такая гарантия невозможна. Рассмотрим этот альтернативный класс Bar

class Bar
{
   Random random = new Random();
   Array Foos = Enum.GetValues(typeof(Foo));

    private Foo _type;
    public Foo type
    {
        get { return _type; }
        set
        {
            _type = (Foo)Foos.GetValue(random.Next(3));
        }
    }
}

Обратите внимание, что "достижимый" определяется на уровне функции. Не разрешается выходить за пределы проверяемой функции, даже если это безопасно.

Ответ 4

Предупреждение, которое вы ожидали, не реализовано, потому что оно не является полезным предупреждением.

В реальных приложениях компилятор очень часто сталкивается с кодом, который он может полностью доказать, недостижим, может быть, даже с чем-то вроде лысых

static class Program
{
    private static void Main()
    {
        if (false)
        {
            Console.WriteLine("lol");
        }
    }
}

У меня нет компилятора С# на этом компьютере, но я готов поспорить, что у вас там нет предупреждения. Это связано с тем, что, когда вы помещаете if (false) {... } вокруг блока кода, вы сделали это специально, возможно, временно отключить какой-либо эксперимент. Надевать вас на это было бы не полезно.

Более распространено то, что это не буквальное значение false, это константа времени компиляции, которую система сборки установит в true или false в зависимости от конфигурации; вы хотите, чтобы компилятор удалял недостижимый код в одной сборке, а не другой, и вы не хотите жалобы в любом случае.

Еще более распространено, чем это для ранних оптимизаций, таких как inline и постоянное распространение, чтобы обнаружить, что условное значение всегда ложно; предположим, что у вас есть что-то вроде

static class Program
{
    private static void Fizz(int i)
    {
        if (i % 3 == 0) {
            Console.WriteLine("fizz");
        } else {
            Console.WriteLine(i);
        }
    }

    private static void Main()
    {
        Fizz(4);
    }
}

Вам явно не хотелось бы рассказывать, что одна сторона условного внутри Fizz() была недоступна только потому, что она только вызывалась с аргументом 4 в этой программе.