Я могу сделать разумное предположение относительно того, что здесь происходит, но все это немного сложно :) Это включает в себя нулевое состояние и нулевое отслеживание, описанные в проекте спецификации . По сути, в тот момент, когда мы хотим вернуться, компилятор предупредит, если состояние выражения «возможно, нулевое» вместо «не нулевое».
Этот ответ в несколько повествовательной форме, а не просто «вот выводы» ... Я надеюсь, что он более полезен.
Я собираюсь немного упростить пример, избавившись от полей, и рассмотрим метод с одной из этих двух подписей:
public static string M(string? text)
public static string M(string text)
В приведенных ниже реализациях я дал каждому методу различное число, чтобы я мог однозначно сослаться на конкретные примеры. Это также позволяет всем реализациям присутствовать в одной и той же программе.
В каждом из случаев, описанных ниже, мы будем делать разные вещи, но в конечном итоге попытаемся вернуться text
- так text
что важно нулевое состояние .
Безусловный возврат
Во-первых, давайте просто попробуем вернуть его напрямую:
public static string M1(string? text) => text; // Warning
public static string M2(string text) => text; // No warning
Пока все просто. Обнуляемое состояние параметра в начале метода имеет значение «возможно, нулевое», если оно имеет тип, string?
и «не нулевое», если оно имеет тип string
.
Простой условный возврат
Теперь давайте проверим нулевое значение внутри самого if
условия оператора. (Я бы использовал условный оператор, который, как я полагаю, будет иметь тот же эффект, но я хотел бы остаться честнее с вопросом.)
public static string M3(string? text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
public static string M4(string text)
{
if (text is null)
{
return "";
}
else
{
return text; // No warning
}
}
Отлично, так выглядит в if
операторе, где само условие проверяет на нулевое значение, состояние переменной в каждой ветви if
оператора может быть различным: в пределах else
блока состояние «не равно нулю» в обеих частях кода. Так, в частности, в M3 состояние меняется с «возможно, нулевое» на «не нулевое».
Условный возврат с локальной переменной
Теперь давайте попробуем перенести это условие в локальную переменную:
public static string M5(string? text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
public static string M6(string text)
{
bool isNull = text is null;
if (isNull)
{
return "";
}
else
{
return text; // Warning
}
}
И M5, и M6 выдают предупреждения. Таким образом, мы не только не получаем положительный эффект изменения состояния с «возможно, нулевого» на «не нулевой» в M5 (как мы это делали в M3) ... мы получаем противоположный эффект в M6, откуда происходит состояние « не ноль "до" может быть ноль ". Это действительно удивило меня.
Похоже, мы узнали, что:
- Логика «как была вычислена локальная переменная» не используется для распространения информации о состоянии. Подробнее об этом позже.
- Введение нулевого сравнения может предупредить компилятор, что то, что раньше считалось не нулевым, в конце концов может быть нулевым.
Безусловный возврат после игнорируемого сравнения
Давайте посмотрим на второй из этих пунктов, введя сравнение перед безусловным возвратом. (Таким образом, мы полностью игнорируем результат сравнения.):
public static string M7(string? text)
{
bool ignored = text is null;
return text; // Warning
}
public static string M8(string text)
{
bool ignored = text is null;
return text; // Warning
}
Обратите внимание, что M8 чувствует, что он должен быть эквивалентен M2 - оба имеют ненулевой параметр, который они возвращают безоговорочно - но введение сравнения с нулевым изменяет состояние с «не нулевое» на «возможно нулевое». Мы можем получить дополнительные доказательства этого, пытаясь разыменовать text
до условия:
public static string M9(string text)
{
int length1 = text.Length; // No warning
bool ignored = text is null;
int length2 = text.Length; // Warning
return text; // No warning
}
Обратите внимание, что у return
оператора нет предупреждения: состояние после выполнения text.Length
«не равно нулю» (потому что если мы успешно выполним это выражение, оно не может быть нулевым). Таким образом, text
параметр начинается с «not null» из-за его типа, становится «возможно null» из-за нулевого сравнения, а затем снова становится «not null» text2.Length
.
Какие сравнения влияют на состояние?
Так что это сравнение text is null
... какой эффект имеют подобные сравнения? Вот еще четыре метода, все из которых начинаются с ненулевого строкового параметра:
public static string M10(string text)
{
bool ignored = text == null;
return text; // Warning
}
public static string M11(string text)
{
bool ignored = text is object;
return text; // No warning
}
public static string M12(string text)
{
bool ignored = text is { };
return text; // No warning
}
public static string M13(string text)
{
bool ignored = text != null;
return text; // Warning
}
Так что , хотя x is object
в настоящее время является рекомендуемым альтернативой x != null
, они не имеют такой же эффект: только сравнение с нулем (с любым is
, ==
или !=
) изменяет состояние с «не нуль» до « может быть нулем».
Почему подъем условия оказывает влияние?
Возвращаясь к нашему первому пункту ранее, почему M5 и M6 не принимают во внимание условие, которое привело к локальной переменной? Это меня не удивляет так сильно, как кажется, удивляет других. Встраивание подобной логики в компилятор и спецификацию - это большая работа и относительно небольшая выгода. Вот еще один пример, не имеющий ничего общего с обнуляемостью, где вставка чего-либо имеет эффект:
public static int X1()
{
if (true)
{
return 1;
}
}
public static int X2()
{
bool alwaysTrue = true;
if (alwaysTrue)
{
return 1;
}
// Error: not all code paths return a value
}
Несмотря на то, что мы знаем, что alwaysTrue
это всегда будет правдой, это не удовлетворяет требованиям спецификации, которые делают код после if
оператора недоступным, что нам и нужно.
Вот еще один пример вокруг определенного присваивания:
public static void X3()
{
string x;
bool condition = DateTime.UtcNow.Year == 2020;
if (condition)
{
x = "It's 2020.";
}
if (!condition)
{
x = "It's not 2020.";
}
// Error: x is not definitely assigned
Console.WriteLine(x);
}
Несмотря на то, что мы знаем, что код будет входить именно в один из этих if
тел операторов, в спецификации нет ничего, что могло бы решить эту проблему. Инструменты статического анализа вполне могут это сделать, но пытаться включить это в спецификацию языка было бы плохой идеей, ИМО - для инструментов статического анализа хорошо иметь все виды эвристики, которые могут развиваться со временем, но не так сильно для спецификации языка.