Почему этот код недоступен?
Я нашел случай, когда у меня есть код, который, как я считаю, недоступен и не обнаружен. Предупреждение не выдаётся ни компилятором, ни 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 в этой программе.