Bagikan melalui


Penetapan dengan kondisi null

Nota

Artikel ini adalah spesifikasi fitur. Spesifikasi berfungsi sebagai dokumen desain untuk fitur tersebut. Ini termasuk perubahan spesifikasi yang diusulkan, bersama dengan informasi yang diperlukan selama desain dan pengembangan fitur. Artikel ini diterbitkan sampai perubahan spesifikasi yang diusulkan diselesaikan dan dimasukkan dalam spesifikasi ECMA saat ini.

Mungkin ada beberapa perbedaan antara spesifikasi fitur dan implementasi yang selesai. Perbedaan tersebut tertuangkan dalam catatan rapat terkait desain bahasa (LDM) .

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

Edisi unggulan: https://github.com/dotnet/csharplang/issues/8677

Ringkasan

Mengizinkan penugasan terjadi secara kondisional dalam ekspresi a?.b atau 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();
}

Motivasi

Berbagai kasus penggunaan yang memotivasi dapat ditemukan dalam masalah yang diperjuangkan. Motivasi utama meliputi:

  1. Paritas antara properti dan Set() metode.
  2. Melampirkan penanganan aktivitas dalam kode UI.

Desain terperinci

  • Sisi kanan penugasan hanya dievaluasi ketika penerima akses kondisional tidak bernilai 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();
  • Semua bentuk penugasan gabungan diizinkan.
a?.b -= M(); // ok
a?.b += M(); // ok
// etc.
  • Jika hasil ekspresi digunakan, jenis ekspresi harus diketahui dari jenis nilai atau jenis referensi. Ini konsisten dengan perilaku yang ada pada akses bersyarat.
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
}
  • Ekspresi akses bersyarat masih belum lvalues, dan masih belum diizinkan untuk misalnya, mengambil ref ke ekspresi tersebut.
M(ref a?.b); // error
  • Tidak diperbolehkan untuk mengubah referensi menjadi akses kondisional. Alasan utama untuk ini adalah bahwa satu-satunya cara Anda akan secara kondisional mengakses variabel ref adalah bidang ref, dan struktur ref dilarang digunakan dalam jenis nilai nullable. Jika skenario yang valid untuk penetapan ref kondisional muncul di masa mendatang, kami dapat menambahkan dukungan pada saat itu.
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'.
}
  • Tidak dimungkinkan untuk misalnya menetapkan ke akses bersyarat melalui penugasan dekonstruksi. Kami mengantisipasinya akan jarang orang ingin melakukan ini, dan bukan kelemahan yang signifikan untuk perlu melakukannya melalui beberapa ekspresi penugasan terpisah sebagai gantinya.
(a?.b, c?.d) = (x, y); // error
a?.b++; // error
--a?.b; // error
  • Fitur ini umumnya tidak berfungsi ketika penerima akses bersyarat adalah jenis nilai. Ini karena akan jatuh ke dalam salah satu dari dua kasus berikut:
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 mengusulkan untuk melonggarkan ini, dalam hal ini kita dapat menentukan perilaku yang wajar untuk a?.b = c, ketika a adalah System.Nullable<T> dan b merupakan properti dengan readonly setter.

Spesifikasi

Tata bahasa penetapan kondisi null didefinisikan sebagai berikut:

null_conditional_assignment
    : null_conditional_member_access assignment_operator expression
    : null_conditional_element_access assignment_operator expression

Lihat §11.7.7 dan §11.7.11 untuk referensi.

Saat penetapan kondisi null muncul dalam pernyataan ekspresi, semantiknya adalah sebagai berikut:

  • P?.A = B setara dengan if (P is not null) P.A = B;, kecuali yang P hanya dievaluasi sekali.
  • P?[A] = B setara dengan if (P is not null) P[A] = B, kecuali yang P hanya dievaluasi sekali.

Jika tidak, semantiknya adalah sebagai berikut:

  • P?.A = B setara dengan (P is null) ? (T?)null : (P.A = B), di mana T adalah jenis hasil , P.A = Bkecuali yang P hanya dievaluasi sekali.
  • P?[A] = B setara dengan (P is null) ? (T?)null : (P[A] = B), di mana T adalah jenis hasil , P[A] = Bkecuali yang P hanya dievaluasi sekali.

Pelaksanaan

Tata bahasa dalam standar saat ini tidak sangat sesuai dengan desain sintaks yang digunakan dalam implementasi. Kami berharap bahwa tetap terjadi setelah fitur ini diterapkan. Desain sintaks dalam implementasi tidak diharapkan benar-benar berubah--hanya cara yang digunakan yang akan berubah. Contohnya:

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

Contoh kompleks

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;

Kekurangan

Pilihan untuk mempertahankan penugasan dalam akses bersyarat memperkenalkan beberapa pekerjaan tambahan untuk IDE, yang memiliki banyak jalur kode yang perlu bekerja mundur dari penugasan untuk mengidentifikasi hal yang ditugaskan.

Alternatif

Kita bisa menjadikan ?. secara sintis sebagai anak dari =. Ini membuat pengelolaan ekspresi = perlu menyadari kondisionalitas sisi kanan di hadapan ?. di sisi kiri. Ini juga menyebabkan struktur sintaks tidak terlalu sesuai dengan semantik.

Pertanyaan yang belum terselesaikan

Rapat desain