Я могу сделать разумное предположение относительно того, что здесь происходит, но все это немного сложно :) Это включает в себя нулевое состояние и нулевое отслеживание, описанные в проекте спецификации . По сути, в тот момент, когда мы хотим вернуться, компилятор предупредит, если состояние выражения «возможно, нулевое» вместо «не нулевое».
Этот ответ в несколько повествовательной форме, а не просто «вот выводы» ... Я надеюсь, что он более полезен.
Я собираюсь немного упростить пример, избавившись от полей, и рассмотрим метод с одной из этих двух подписей:
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тел операторов, в спецификации нет ничего, что могло бы решить эту проблему. Инструменты статического анализа вполне могут это сделать, но пытаться включить это в спецификацию языка было бы плохой идеей, ИМО - для инструментов статического анализа хорошо иметь все виды эвристики, которые могут развиваться со временем, но не так сильно для спецификации языка.