次の方法で共有


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. UI コードでのイベント ハンドラーのアタッチ。

詳細な設計

  • 割り当ての右側は、条件付きアクセスの受信側が 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
}
  • 条件付きアクセス式はまだ左辺値ではないため、たとえば ref を取ることはまだ許可されません。
M(ref a?.b); // error
  • 条件付きアクセスへの参照割り当ては許可されません。 この主な理由は、ref 変数に条件付きでアクセスする唯一の方法は ref フィールドであり、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
  • この機能は、通常、条件付きアクセスの受信側が値型の場合は機能しません。 これは、次の 2 つのケースのいずれかに分類されるためです。
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 = caで、System.Nullable<T>が読み取り専用セッターを持つプロパティである場合、bに対して妥当な動作を定義できます。

仕様

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 条件付き代入 が表示される場合、そのセマンティクスは次のようになります。

  • P?.A = Bif (P is not null) P.A = B;と同じですが、 P は 1 回だけ評価される点が異なります。
  • P?[A] = Bif (P is not null) P[A] = Bと同じですが、 P は 1 回だけ評価される点が異なります。

それ以外の場合、セマンティクスは次のようになります。

  • P?.A = B(P is null) ? (T?)null : (P.A = B)に相当します。 TP.A = Bの結果の型ですが、 P は 1 回だけ評価される点が異なります。
  • P?[A] = B(P is null) ? (T?)null : (P[A] = B)に相当します。 TP[A] = Bの結果の型ですが、 P は 1 回だけ評価される点が異なります。

実装

現在、標準の文法は、実装で使用される構文設計に強く対応していません。 この機能が実装された後も、この問題が発生し続けることを期待しています。 実装での構文設計は実際には変更されません。使用する方法のみが変更されます。 例えば次が挙げられます。

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;

デメリット

条件付きアクセス内で割り当てを保持することを選択すると、IDE に追加の作業がいくつか導入されます。IDE には、割り当てから割り当てられているものを識別するために逆方向に作業する必要がある多くのコード パスがあります。

選択肢

代わりに、構文的に ?.=の子にすることができます。 これにより、 = 式の処理は、左側に ?. がある場合に右側の条件を認識する必要があります。 また、構文の構造がセマンティクスに強く対応しないようにもなります。

未解決の質問

デザインに関する会議