Поделиться через


null-условное присваивание

Замечание

Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Этот документ включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию ECMA.

Может возникнуть некоторое несоответствие между спецификацией компонентов и завершенной реализацией. Эти различия отражены в соответствующих заметках с заседания по дизайну языка (LDM) .

Дополнительные сведения о процессе внедрения спецификаций функций в стандарт языка C# см. в статье о спецификациях .

Вопрос чемпиона: https://github.com/dotnet/csharplang/issues/8677

Сводка

Разрешает назначение условно в пределах a?.b или a?[b] выражения.

using System;

class C
{
    public object obj;
}

void M(C? c)
{
    c?.obj = new object();
}
using System;

class C
{
    public event Action E;
}

void M(C? c)
{
    c?.E += () => { Console.WriteLine("handled event E"); };
}
void M(object[]? arr)
{
    arr?[42] = new object();
}

Мотивация

Различные мотивирующие варианты использования можно найти в проблеме с чемпионом. К основным мотивациям относятся:

  1. Четность между свойствами и Set() методами.
  2. Присоединение обработчиков событий в коде пользовательского интерфейса.

Подробный дизайн

  • Правая сторона назначения вычисляется только в том случае, если получатель условного доступа не имеет значения NULL.
// M() is only executed if 'a' is non-null.
// note: the value of 'a.b' doesn't affect whether things are evaluated here.
a?.b = M();
  • Разрешены все формы составного назначения.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
  • Если используется результат выражения, тип выражения должен быть известен типом значения или ссылочным типом. Это согласуется с существующим поведением условного доступа.
class C<T>
{
    public T? field;
}

void M1<T>(C<T>? c, T t)
{
    (c?.field = t).ToString(); // error: 'T' cannot be made nullable.
    c?.field = t; // ok
}
  • Выражения условного доступа по-прежнему не являются значениями lvalue, и они по-прежнему не допускаются, например принимать ref их.
M(ref a?.b); // error
  • Невозможно назначить ссылку условному доступу. Основной причиной этого является то, что единственным способом условного доступа к переменной ref является поле ссылок, и ссылочные структуры запрещены использовать в типах значений, допускающих значение NULL. Если допустимый сценарий условного назначения ссылок появился в будущем, мы могли бы добавить поддержку в то время.
ref struct RS
{
    public ref int b;
}

void M(RS a, ref int x)
{
  a?.b = ref x; // error: Operator '?' can't be applied to operand of type 'RS'.
}
  • Например, невозможно назначить условный доступ с помощью деконструкции. Мы ожидаем, что это будет редко для людей, которые хотят сделать это, и не значительный недостаток, чтобы сделать это над несколькими отдельными выражениями назначения вместо этого.
(a?.b, c?.d) = (x, y); // error
a?.b++; // error
--a?.b; // error
  • Эта функция обычно не работает, если получатель условного доступа является типом значения. Это связано с тем, что это приведет к одному из следующих двух случаев:
void Case1(MyStruct a)
    => a?.b = c; // a?.b is not allowed when 'a' is of non-nullable value type

void Case2(MyStruct? a)
    => a?.b = c; // `a.Value` is not a variable, so there's no reasonable meaning to define for the assignment

readonly-setter-calls-on-non-variables.md предлагает расслабиться, в этом случае мы могли бы определить разумное поведение a?.b = cдля , когда a является и b является System.Nullable<T> свойством с методом чтения.

Specification

Грамматика условного назначения null определяется следующим образом:

null_conditional_assignment
    : null_conditional_member_access assignment_operator expression
    : null_conditional_element_access assignment_operator expression

Дополнительные сведения см. в разделе "11.7.7" и "11.7.11".

Если условное назначение null отображается в операторе expression-statement, его семантика выглядит следующим образом:

  • P?.A = B эквивалентен if (P is not null) P.A = B;, за исключением того, что P вычисляется только один раз.
  • P?[A] = B эквивалентен if (P is not null) P[A] = B, за исключением того, что P вычисляется только один раз.

В противном случае его семантика выглядит следующим образом:

  • P?.A = B эквивалентно (P is null) ? (T?)null : (P.A = B), где T является тип P.A = Bрезультата, за исключением того, что P вычисляется только один раз.
  • P?[A] = B эквивалентно (P is null) ? (T?)null : (P[A] = B), где T является тип P[A] = Bрезультата, за исключением того, что P вычисляется только один раз.

Implementation

Грамматика в стандарте в настоящее время не соответствует строго синтаксису, используемому в реализации. Мы ожидаем, что останется дело после реализации этой функции. Разработка синтаксиса в реализации на самом деле не изменится только тем способом, который он будет использоваться. Рассмотрим пример.

graph TD;
subgraph ConditionalAccessExpression
  whole[a?.b = c]
end
subgraph  
  subgraph WhenNotNull
    whole-->whenNotNull[".b = c"];
    whenNotNull-->.b;
    whenNotNull-->eq[=];
    whenNotNull-->c;
  end
  subgraph OperatorToken
    whole-->?;
  end
  subgraph Expression
    whole-->a;
  end
end

Сложные примеры

class C
{
    ref int M() => /*...*/;
}

void M1(C? c)
{
    c?.M() = 42; // equivalent to:
    if (c is not null)
        c.M() = 42;
}

int? M2(C? c)
{
    return c?.M() = 42; // equivalent to:
    return c is null ? (int?)null : c.M() = 42;
}
M(a?.b?.c = d); // equivalent to:
M(a is null
    ? null
    : (a.b is null
        ? null
        : (a.b.c = d)));
return a?.b = c?.d = e?.f; // equivalent to:
return a?.b = (c?.d = e?.f); // equivalent to:
return a is null
    ? null
    : (a.b = c is null
        ? null
        : (c.d = e is null
            ? null
            : e.f));
}
a?.b ??= c; // equivalent to:
if (a is not null)
{
    if (a.b is null)
    {
        a.b = c;
    }
}

return a?.b ??= c; // equivalent to:
return a is null
    ? null
    : a.b is null
        ? a.b = c
        : a.b;

Недостатки

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

Alternatives

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

Неразрешенные вопросы

Проектирование собраний