Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Замечание
Эта статья является спецификацией компонентов. Спецификация служит проектным документом для функции. Этот документ включает предлагаемые изменения спецификации, а также информацию, необходимую во время проектирования и разработки функции. Эти статьи публикуются до тех пор, пока предложенные изменения спецификации не будут завершены и включены в текущую спецификацию 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();
}
Мотивация
В этом продвигаемом вопросе можно найти множество мотивирующих примеров использования. К основным мотивациям относятся:
- Паритет между свойствами и
Set()
методами. - Присоединение обработчиков событий в коде пользовательского интерфейса.
Подробный дизайн
- Правая сторона присваивания вычисляется только в том случае, если получатель условного доступа не является 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
- Переназначение ссылки для условного доступа не допускается. Основная причина этого в том, что единственный способ условно получить доступ к переменной ссылочного типа – это ссылочное поле, и ссылочные структуры запрещены для использования в типах значений, допускающих 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
является System.Nullable<T>
, а b
является свойством с методом readonly-сеттера.
Спецификация
Грамматика условного присваивания 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
вычисляется только один раз.
Внедрение
Грамматика в стандарте в настоящее время не соответствует строго синтаксису, используемому в реализации. Мы ожидаем, что так и останется после внедрения этой функции. Дизайн синтаксиса в реализации на самом деле не изменится — изменится только способ его использования. Рассмотрим пример.
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;
Недостатки
Выбор оставить назначение в условном доступе вводит некоторую дополнительную работу для интегрированной среды разработки, в которой множество путей кода должны работать обратным способом от назначения к идентификации того, что назначается.
Альтернативы
Мы могли бы вместо этого сделать ?.
синтаксически дочерним элементом =
. Это приводит к тому, что при обработке выражений =
необходимо учитывать условный характер правой стороны в присутствии ?.
слева. Это также приводит к тому, что структура синтаксиса не так строго соответствует семантике.
Неразрешенные вопросы
Совещания по проектированию
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-04-27.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-08-31.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2022/LDM-2022-10-26.md#null-conditional-assignment
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-10-28.md#increment-and-decrement-operators-in-null-conditional-access
C# feature specifications