Bagikan melalui


kata kunci field pada properti

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 .

Isu Terkemuka: https://github.com/dotnet/csharplang/issues/8635

Ringkasan

Perluas semua properti untuk memungkinkan mereka mereferensikan bidang dukungan yang dihasilkan secara otomatis menggunakan kata kunci kontekstual baru field. Properti sekarang juga dapat berisi aksesor tanpa tubuh bersama aksesor dengan tubuh.

Motivasi

Properti otomatis hanya memungkinkan pengaturan langsung atau mendapatkan bidang pendukung, memberikan beberapa kontrol hanya dengan menempatkan pengubah akses pada aksesor. Terkadang ada kebutuhan untuk memiliki kontrol tambahan atas apa yang terjadi pada salah satu atau kedua aksesor, tetapi ini membuat pengguna menghadapi beban tambahan untuk mendeklarasikan sebuah lapangan pendukung. Nama field pendukung kemudian harus tetap sinkron dengan properti, dan field pendukung tersebut dicakupkan pada seluruh kelas, yang dapat mengakibatkan pengabaian aksesor yang tidak disengaja dari dalam kelas.

Ada beberapa skenario umum. Dalam getter, terdapat inisialisasi tunda, atau nilai default jika properti belum diinisialisasi. Dalam setter, ada penerapan batasan untuk memastikan validitas nilai, atau mendeteksi dan menyebarkan pembaruan seperti dengan menaikkan peristiwa INotifyPropertyChanged.PropertyChanged.

Dalam kasus ini, Anda sekarang selalu harus membuat bidang instans dan mendefinisikan seluruh propertinya sendiri. Ini tidak hanya menambahkan sejumlah kode yang signifikan, tetapi juga membocorkan field pendukung ke seluruh cakupan tipe, sementara sering kali diinginkan agar hanya tersedia bagi badan aksesor.

Daftar istilah

  • Properti otomatis: Singkatan dari "properti yang diimplementasikan secara otomatis" (§15.7.4). Akses pada properti otomatis tidak memiliki bodi. Implementasi dan penyimpanan backing keduanya disediakan oleh kompilator. Properti otomatis memiliki { get; }, { get; set; }, atau { get; init; }.

  • Aksesor otomatis: Singkatan dari "aksesor yang diimplementasikan secara otomatis." Ini adalah aksesor yang tidak memiliki isi. Implementasi dan penyimpanan backing keduanya disediakan oleh kompilator. get;, set;, dan init; adalah aksesor otomatis.

  • Aksesori lengkap: Ini adalah aksesori yang memiliki tubuh. Implementasi tidak disediakan oleh pengkompilasi, meskipun penyimpanan cadangan mungkin masih (seperti dalam contoh set => field = value;).

  • properti yang didukung oleh bidang: Ini adalah properti dengan kata kunci field dalam isi aksesor, atau properti otomatis.

  • Bidang pendukung: Ini adalah variabel yang ditandai oleh kata kunci field dalam aksesor properti, yang juga dibaca atau ditulis secara implisit dalam aksesor yang diimplementasikan secara otomatis (get;, set;, atau init;).

Desain terperinci

Untuk properti dengan aksesor init, semua yang berlaku di bawah ini untuk set akan berlaku sebagai gantinya untuk aksesor init.

Ada dua perubahan sintaks:

  1. Ada kata kunci kontekstual baru, field, yang dapat digunakan dalam badan aksesor properti untuk mengakses bidang dukungan untuk deklarasi properti (keputusan LDM).

  2. Properti-properti sekarang dapat menggabungkan dan menyesuaikan pengakses otomatis dengan pengakses penuh (keputusan LDM). "Properti otomatis" akan terus berarti properti yang aksesornya tidak memiliki badan. Tidak ada contoh di bawah ini yang akan dianggap properti otomatis.

Contoh:

{ get; set => Set(ref field, value); }
{ get => field ?? parent.AmbientValue; set; }

Kedua aksesori dapat merupakan aksesori penuh, dengan salah satu atau keduanya memanfaatkan field:

{ get => field; set => field = value; }
{ get => field; set => throw new InvalidOperationException(); }
{ get => overriddenValue; set => field = value; }
{
    get;
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged();
    }
}

Properti berisi ekspresi dan properti dengan hanya satu aksesori get juga dapat menggunakan field:

public string LazilyComputed => field ??= Compute();
public string LazilyComputed { get => field ??= Compute(); }

Properti set-only juga dapat menggunakan field:

{
    set
    {
        if (field == value) return;
        field = value;
        OnXyzChanged(new XyzEventArgs(value));
    }
}

Perubahan signifikan

Keberadaan kata kunci kontekstual field dalam blok aksesor properti adalah perubahan yang berpotensi menimbulkan masalah.

Karena field adalah kata kunci dan bukan pengidentifikasi, itu hanya dapat "dibayangi" oleh pengidentifikasi menggunakan cara menghindari kata kunci yang biasa: @field. Semua pengidentifikasi bernama field yang dideklarasikan dalam badan aksesor properti dapat menghindari kerusakan saat pemutakhiran dari versi C# sebelum 14 dengan menambahkan @awal.

Jika variabel bernama field dideklarasikan dalam aksesor properti, kesalahan akan dilaporkan.

Dalam versi bahasa 14 atau yang lebih baru, peringatan akan diberikan jika ekspresi utama field mengacu pada field dukungan, tetapi seharusnya mengacu pada simbol yang berbeda pada versi bahasa sebelumnya.

Atribut yang ditargetkan untuk bidang

Seperti halnya properti otomatis, properti apa pun yang menggunakan field pendukung di salah satu aksesornya akan dapat menggunakan atribut yang ditargetkan field:

[field: Xyz]
public string Name => field ??= Compute();

[field: Xyz]
public string Name { get => field; set => field = value; }

Atribut yang ditargetkan bidang akan tetap tidak valid kecuali pengases menggunakan bidang dukungan:

// ❌ Error, will not compile
[field: Xyz]
public string Name => Compute();

Inisialisasi properti

Properti dengan penginisialisasi dapat menggunakan field. Bidang backing diinisialisasi secara langsung, bukan melalui pemanggilan setter (keputusan LDM).

Memanggil setter untuk penginisialisasi bukanlah opsi; inisialisasi diproses sebelum memanggil konstruktor dasar, dan ilegal untuk memanggil metode instans apa pun sebelum konstruktor dasar dipanggil. Ini juga penting untuk inisialisasi default/penetapan struktur yang pasti.

Ini menghasilkan kontrol fleksibel atas inisialisasi. Jika Anda ingin menginisialisasi tanpa memanggil setter, Anda menggunakan penginisialisasi properti. Jika Anda ingin menginisialisasi dengan memanggil setter, Anda menetapkan nilai awal pada properti di konstruktor.

Berikut adalah contoh di mana ini berguna. Kami percaya kata kunci field akan banyak digunakan dalam model tampilan karena solusi elegan yang dibawanya untuk pola INotifyPropertyChanged. View setter properti model kemungkinan akan terhubung dengan data ke UI dan cenderung menyebabkan pelacakan perubahan atau memicu perilaku lainnya. Kode berikut perlu menginisialisasi nilai default IsActive tanpa mengatur HasPendingChanges ke true:

class SomeViewModel
{
    public bool HasPendingChanges { get; private set; }

    public bool IsActive { get; set => Set(ref field, value); } = true;

    private bool Set<T>(ref T location, T value)
    {
        if (EqualityComparer<T>.Default.Equals(location, value))
            return false;

        location = value;
        HasPendingChanges = true;
        return true;
    }
}

Perbedaan perilaku antara penginisialisasi properti dan penetapan dari konstruktor ini juga dapat dilihat dengan properti otomatis virtual dalam versi bahasa sebelumnya:

using System;

// Nothing is printed; the property initializer is not
// equivalent to `this.IsActive = true`.
_ = new Derived();

class Base
{
    public virtual bool IsActive { get; set; } = true;
}

class Derived : Base
{
    public override bool IsActive
    {
        get => base.IsActive;
        set
        {
            base.IsActive = value;
            Console.WriteLine("This will not be reached");
        }
    }
}

Penetapan konstruktor

Seperti halnya properti otomatis, penetapan di konstruktor memanggil setter (berpotensi virtual) jika ada; jika tidak, akan kembali ke penetapan langsung ke field penyangga.

class C
{
    public C()
    {
        P1 = 1; // Assigns P1's backing field directly
        P2 = 2; // Assigns P2's backing field directly
        P3 = 3; // Calls P3's setter
        P4 = 4; // Calls P4's setter
    }

    public int P1 => field;
    public int P2 { get => field; }
    public int P4 { get => field; set => field = value; }
    public int P3 { get => field; set; }
}

Penugasan tegas dalam struktur

Meskipun tidak dapat dirujuk dalam konstruktor, bidang pencadangan yang ditandai oleh kata kunci field tunduk pada inisialisasi default dan peringatan nonaktif secara default dalam kondisi yang sama dengan bidang struct lainnya (keputusan LDM 1, keputusan LDM 2).

Misalnya (diagnostik ini diam secara default):

public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        _ = P1;
    }

    public int P1 { get => field; }
}
public struct S
{
    public S()
    {
        // CS9020 The 'this' object is read before all of its fields have been assigned, causing preceding implicit
        // assignments of 'default' to non-explicitly assigned fields.
        P2 = 5;
    }

    public int P2 { get => field; set => field = value; }
}

Properti yang mengembalikan referensi

Seperti halnya properti otomatis (auto property), kata kunci field tidak akan tersedia untuk digunakan dalam properti yang mengembalikan ref. Properti ref-returning tidak boleh memiliki aksesor yang ditetapkan, dan tanpa aksesor yang ditetapkan, aksesor get dan penginisialisasi properti akan menjadi satu-satunya hal yang dapat mengakses bidang backing. Tidak ada kasus penggunaan untuk ini, sekarang bukan saatnya bagi properti ref-returning untuk mulai dapat ditulis sebagai properti otomatis.

Keterbatalan

Prinsip fitur Jenis Referensi Nullable adalah untuk memahami pola pengkodean idiomatik yang ada di C# dan untuk memerlukan sesedikit mungkin "formalitas" di sekitar pola-pola tersebut. Proposal kata kunci field memungkinkan pola idiomatik sederhana untuk mengatasi skenario yang banyak diminta, seperti properti yang diinisialisasi secara malas. Penting bagi Jenis Referensi Nullable untuk berintegrasi dengan baik dengan pola pengkodean baru ini.

Tujuan:

  • Tingkat keamanan null yang wajar harus dipastikan untuk berbagai pola penggunaan fitur kata kunci field.

  • Pola yang menggunakan kata kunci field harus terasa seolah-olah mereka selalu menjadi bagian dari bahasa. Hindari membuat pengguna bersusah payah untuk mengaktifkan Nullable Reference Types dalam kode yang sepenuhnya sesuai dengan fitur kata kunci field.

Salah satu skenario kunci adalah properti yang diinisialisasi secara lambat:

public class C
{
    public C() { } // It would be undesirable to warn about 'Prop' being uninitialized here

    string Prop => field ??= GetPropValue();
}

Aturan ketidaknullan berikut ini akan berlaku tidak hanya bagi properti yang memakai kata kunci field, melainkan juga bagi properti otomatis yang sudah ada.

Ketidakjelasan null dari backing field

Lihat Glosarium untuk definisi istilah baru.

Bidang pendukung memiliki jenis yang sama dengan properti. Namun, anotasi nullable-nya mungkin berbeda dari properti. Untuk menentukan anotasi nullable ini, kami memperkenalkan konsep ketahanan terhadap null. Ketahanan terhadap null secara intuitif berarti bahwa akses properti get mempertahankan keamanan terhadap null bahkan ketika field berisi nilai tipe default.

Properti dengan dukungan bidang ditentukan untuk tahan terhadap null atau tidak dengan melakukan analisis nullable khusus dari aksesor get-nya.

  • Untuk keperluan analisis ini, diasumsikan untuk sementara telah menganotasi nullability dengan , misalnya . Hal ini menyebabkan field memiliki mungkin-null atau mungkin-default status awal di aksesor get, tergantung pada jenisnya.
  • Kemudian, jika analisis nullable dari getter tidak menghasilkan peringatan nullable, maka properti tersebut tahan terhadap null. Jika tidak, itu tidak tahan terhadap null.
  • Jika properti tidak memiliki fungsi akses get, itu secara otomatis tahan-null.
  • Jika aksesor get diimplementasikan secara otomatis, properti tersebut tidak handal terhadap nilai null.

Keberadaan nilai kosong dari bidang pendukung ditentukan sebagai berikut:

  • Jika bidang memiliki atribut nullability seperti [field: MaybeNull], AllowNull, NotNull, atau DisallowNull, maka anotasi bidang yang nullable sama dengan anotasi properti yang nullable.
    • Ini karena ketika pengguna mulai menerapkan atribut nullability ke bidang , kami tidak lagi ingin menyimpulkan apa pun, kami hanya ingin nullability menjadi apa yang dikatakan pengguna.
  • Jika properti yang berisi memiliki nullability yang tidak sadar atau nullability yang dianotasi, maka field tersebut memiliki nullability yang sama dengan properti.
  • Jika properti memiliki tidak dianotasi nullability (misalnya string atau T) atau memiliki atribut [NotNull], dan properti tahan null, maka bidang backing memiliki anotasi nullability.
  • Jika properti yang berisi memiliki tidak dianotasi nullability (misalnya string atau T) atau memiliki atribut [NotNull], dan properti tidak resistan null, maka bidang pencadangan memiliki tidak dianotasi nullability.

Analisis pembangun

Saat ini, properti otomatis diperlakukan sangat mirip dengan bidang biasa dalam analisis konstruktor nullable. Kami memperluas perawatan ini untuk properti yang didukung oleh bidang, dengan memperlakukan setiap properti yang didukung oleh bidang sebagai proksi ke bidang dukungannya.

Kami memperbarui bahasa spesifikasi berikut dari pendekatan sebelumnya yang diusulkan untuk mencapai hal ini:

Pada setiap 'pengembalian' eksplisit atau implisit dalam konstruktor, kami memberikan peringatan untuk setiap elemen yang keadaan alirannya tidak kompatibel dengan anotasi dan atribut nullability-nya. Jika anggota adalah properti dengan dukungan bidang, anotasi null dari bidang pendukung digunakan untuk pemeriksaan ini. Jika tidak, anotasi null dari anggota itu sendiri digunakan. Proksi yang wajar untuk ini adalah: jika menetapkan anggota ke dirinya sendiri saat titik pengembalian akan menghasilkan peringatan nullability, maka peringatan nullability akan muncul pada titik pengembalian.

Perhatikan bahwa ini pada dasarnya adalah analisis antarprosedural yang dibatasi. Kami mengantisipasi bahwa untuk menganalisis konstruktor, perlu dilakukan analisis pengikatan dan daya tahan terhadap null pada semua aksesor get yang berlaku dalam jenis yang sama, yang menggunakan kata kunci kontekstual field dan memiliki ketidakpastian nilai null yang tidak dianotasikan. Kami berspekulasi bahwa ini tidak terlalu mahal karena tubuh getter biasanya tidak memiliki kompleksitas yang tinggi, dan bahwa analisis "ketahanan null" hanya perlu dilakukan sekali tanpa memandang berapa banyak konstruktor dalam jenis tersebut.

Analisis pengatur

Untuk kesederhanaan, kami menggunakan istilah "setter" dan "set accessor" untuk merujuk ke aksesor set atau init.

Ada kebutuhan untuk memeriksa bahwa setter dari properti dengan dukungan bidang memang menginisialisasi bidang pendukung tersebut.

class C
{
    string Prop
    {
        get => field;

        // getter is not null-resilient, so `field` is not-annotated.
        // We should warn here that `field` may be null when exiting.
        set { }
    }

    public C()
    {
        Prop = "a"; // ok
    }

    public static void Main()
    {
        new C().Prop.ToString(); // NRE at runtime
    }
}

Status alur awal bidang penyimpan dalam setter properti yang didukung oleh bidang ditentukan sebagai berikut:

  • Jika properti memiliki penginisialisasi, maka status alur awal sama dengan status alur properti setelah proses inisialisasi.
  • Jika tidak, status alur awal sama dengan status alur yang diberikan oleh field = default;.

Pada setiap pengembalian eksplisit atau implisit dalam setter, peringatan dilaporkan jika status aliran bidang pendukung tidak kompatibel dengan anotasi dan atribut nulabilitas.

Komentar

Rumusan ini sengaja sangat mirip dengan bidang biasa dalam konstruktor. Pada dasarnya, karena hanya pengakses properti yang benar-benar dapat mengacu pada field pendukung, setter diperlakukan sebagai "konstruktor mini" untuk field pendukung tersebut.

Sama seperti bidang biasa, kita biasanya tahu properti telah diinisialisasi di konstruktor karena sudah diatur, namun tidak selalu demikian. Mengembalikan secara sederhana dalam cabang di mana Prop != null benar juga sudah cukup untuk analisis konstruktor kami, karena kami memahami bahwa mekanisme yang tidak terlacak mungkin telah digunakan untuk mengatur properti.

Alternatif telah dipertimbangkan; lihat bagian alternatif nullability.

nameof

Di tempat-tempat di mana field adalah kata kunci, nameof(field) akan gagal mengkompilasi (keputusan LDM), seperti nameof(nint). Ini tidak seperti nameof(value), yang sering digunakan ketika setter properti melempar ArgumentException, seperti yang dilakukan beberapa pustaka inti .NET. Sebaliknya, nameof(field) tidak memiliki kasus penggunaan yang diharapkan.

Mengabaikan

Mengambil alih properti dapat menggunakan field. Penggunaan field seperti itu merujuk ke bidang pendukung untuk properti yang menimpa, terpisah dari bidang pendukung properti dasar jika memilikinya. Tidak ada ABI untuk mengekspos bidang backing dari properti dasar ke kelas turunan karena ini akan membongkar enkapsulasi.

Seperti properti otomatis, properti yang menggunakan kata kunci field dan mengambil alih properti dasar harus mengambil alih semua aksesor (keputusan LDM).

Tangkap

field harus dapat ditangkap dalam fungsi lokal dan lambda, dan referensi ke field dari dalam fungsi lokal dan lambda diizinkan bahkan jika tidak ada referensi lain (keputusan LDM 1, keputusan LDM 2):

public class C
{
    public static int P
    {
        get
        {
            Func<int> f = static () => field;
            return f();
        }
    }
}

Peringatan penggunaan lapangan

Ketika kata kunci field digunakan dalam aksesor, analisis kompilator yang ada dari bidang yang belum ditetapkan atau belum dibaca akan menyertakan bidang tersebut.

  • CS0414: Bidang penyangga untuk properti 'Xyz' disetel tetapi nilainya tidak pernah digunakan
  • CS0649: Bidang pendukung untuk properti 'Xyz' tidak pernah ditetapkan, dan akan selalu memiliki nilai standarnya.

Perubahan spesifikasi

Sintaksis

Saat mengkompilasi dengan bahasa versi 14 atau lebih tinggi, dianggap sebagai kata kunci saat digunakan sebagai ekspresi utama (keputusan LDM) di lokasi berikut (keputusan LDM):

  • Dalam badan metode aksesor get, set, dan init pada properti , tetapi tidak pada pengindeks
  • Dalam atribut yang diterapkan ke aksesor tersebut
  • Dalam ekspresi lambda berlapis dan fungsi lokal, serta dalam ekspresi LINQ pada aksesor tersebut

Dalam semua kasus lain, termasuk saat mengkompilasi dengan bahasa versi 12 atau lebih rendah, field dianggap sebagai pengidentifikasi.

primary_no_array_creation_expression
    : literal
+   | 'field'
    | interpolated_string_expression
    | ...
    ;

Properti

Properti Umum §15.7.1-

property_initializer hanya dapat diberikan untuk properti yang diimplementasikan secara otomatis, danproperti yang memiliki bidang dukungan yang akan dipancarkan. property_initializer menyebabkan inisialisasi bidang yang mendasar dari properti tersebut dengan nilai yang diberikan oleh ekspresi .

§15.7.4Properti yang diimplementasikan secara otomatis

Properti yang diimplementasikan secara otomatis (atau disingkat properti otomatis), adalah properti non-abstrak, non-ekstern, ber-nilai non-referensi dengan badan aksesor hanya titik koma. Properti otomatis harus memiliki aksesor get dan dapat secara opsional memiliki aksesor set. salah satu atau kedua:

  1. pengakses dengan tubuh yang hanya berisi titik koma
  2. penggunaan kata kunci kontekstual field dalam aksesor atau isi ekspresiproperti

Ketika properti ditentukan sebagai properti yang diimplementasikan secara otomatis, bidang pendukung tanpa nama secara otomatis tersedia untuk properti tersebut, dan aksesor diimplementasikan untuk membaca dari dan menulis ke bidang pendukung tersebut. Untuk properti otomatis, setiap pengakses get khusus titik koma diimplementasikan untuk dibaca, dan aksesorset khusus titik koma untuk menulis ke bidang dukungannya.

Bidang backing tersembunyi tidak dapat diakses, dapat dibaca dan ditulis hanya melalui pengakses properti yang diimplementasikan secara otomatis, bahkan dalam jenis yang berisi.Bidang backing dapat direferensikan secara langsung menggunakan kata kunci fielddalam semua aksesor dan dalam isi ekspresi properti. Karena bidang tidak bernama, bidang tidak dapat digunakan dalam ekspresinameof.

Jika properti otomatis memiliki tidak ada aksesor penyetelhanya aksesor pengambil dengan titik koma saja, bidang backing dianggap readonly (§15.5.3). Sama seperti field readonly, properti otomatis baca-saja (tanpa aksesor set atau aksesor init) juga dapat ditetapkan dalam isi konstruktor kelas pembungkus. Penugasan semacam itu menetapkan langsung ke field cadangan hanya-baca properti.

Properti otomatis tidak diperbolehkan hanya memiliki aksesor set yang hanya berupa titik koma tanpa aksesor get.

Properti otomatis dapat secara opsional memiliki property_initializer, yang diterapkan langsung ke field pendukung sebagai variable_initializer (§17.7).

Contoh berikut:

// No 'field' symbol in scope.
public class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

setara dengan deklarasi berikut:

// No 'field' symbol in scope.
public class Point
{
    public int X { get { return field; } set { field = value; } }
    public int Y { get { return field; } set { field = value; } }
}

yang setara dengan:

// No 'field' symbol in scope.
public class Point
{
    private int __x;
    private int __y;
    public int X { get { return __x; } set { __x = value; } }
    public int Y { get { return __y; } set { __y = value; } }
}

Contoh berikut:

// No 'field' symbol in scope.
public class LazyInit
{
    public string Value => field ??= ComputeValue();
    private static string ComputeValue() { /*...*/ }
}

setara dengan deklarasi berikut:

// No 'field' symbol in scope.
public class Point
{
    private string __value;
    public string Value { get { return __value ??= ComputeValue(); } }
    private static string ComputeValue() { /*...*/ }
}

Alternatif

Alternatif ketidaknullan

Selain pendekatan ketahanan null yang diuraikan di bagian nullability, grup kerja menyarankan alternatif berikut untuk pertimbangan LDM:

Jangan lakukan apa-apa

Kita tidak bisa memperkenalkan perilaku khusus di sini. Berlaku:

  • Perlakukan properti yang didukung oleh field dengan cara yang sama seperti properti otomatis yang diperlakukan saat ini—harus diinisialisasi dalam konstruktor kecuali jika ditandai sebagai dibutuhkan, dan lain-lain.
  • Tidak ada perlakuan khusus dari variabel lapangan ketika menganalisis pengakses properti. Ini hanyalah variabel dengan tipe dan atribut keter-null-an yang sama dengan properti.

Perhatikan bahwa ini akan mengakibatkan peringatan yang mengganggu untuk skenario "properti malas", dalam hal ini, pengguna mungkin perlu menetapkan null! atau serupa untuk menghilangkan peringatan konstruktor.
"Sub-alternatif" yang dapat kita pertimbangkan adalah juga sepenuhnya mengabaikan properti dengan menggunakan kata kunci field untuk analisis konstruktor nullable. Dalam hal ini, tidak akan ada peringatan di mana pun tentang pengguna yang perlu menginisialisasi apa pun, tetapi juga tidak ada gangguan bagi pengguna, terlepas dari pola inisialisasi apa yang mungkin mereka gunakan.

Karena kami hanya berencana untuk merilis fitur kata kunci field di bawah versi Pratinjau LangVersion di .NET 9, kami berharap dapat memungkinkan perubahan pada perilaku nullable untuk fitur tersebut di .NET 10. Oleh karena itu, kita dapat mempertimbangkan untuk mengadopsi solusi "biaya lebih rendah" seperti ini dalam jangka pendek, dan tumbuh menjadi salah satu solusi yang lebih kompleks dalam jangka panjang.

fieldatribut nullability yang ditargetkan

Kita dapat memperkenalkan default berikut, mencapai tingkat keamanan null yang wajar, tanpa melibatkan analisis antarprosesural sama sekali:

  1. Variabel field selalu memiliki anotasi nullable yang sama dengan properti .
  2. Atribut nullability [field: MaybeNull, AllowNull] dan lain sebagainya dapat digunakan untuk menyesuaikan kebolehan null dari medan penyangga.
  3. properti yang didukung bidang diperiksa untuk inisialisasi di konstruktor berdasarkan anotasi dan atribut bidang yang dapat diubah ke null.
  4. Dalam properti yang menggunakan penyimpanan di lapangan, setter memeriksa inisialisasi field serupa dengan cara konstruktor.

Ini berarti "skenario malas sederhana" akan terlihat seperti ini sebagai hasilnya:

class C
{
    public C() { } // no need to warn about initializing C.Prop, as the backing field is marked nullable using attributes.

    [field: AllowNull, MaybeNull]
    public string Prop => field ??= GetPropValue();
}

Salah satu alasan kami menghindari penggunaan atribut nullability di sini adalah bahwa yang kita miliki lebih berfokus pada menggambarkan input dan output dari signature. Mereka rumit dalam penggunaan untuk menggambarkan kemungkinan bervalue null variabel jangka panjang.

  • Dalam praktiknya, [field: MaybeNull, AllowNull] diperlukan untuk membuat ruang berfungsi secara "wajar" sebagai variabel nullable, yang memberikan status alur awal yang mungkin null, dan memungkinkan penulisan nilai null ke dalamnya. Ini terasa rumit untuk meminta pengguna melakukan skenario yang cukup biasa untuk situasi malas yang ringan.
  • Jika kami mengejar pendekatan ini, kami akan mempertimbangkan untuk menambahkan peringatan ketika [field: AllowNull] digunakan, menyarankan untuk juga menambahkan MaybeNull. Ini karena AllowNull dengan sendirinya tidak melakukan apa yang dibutuhkan pengguna dari variabel nullable: ini mengasumsikan bidang awalnya tidak-null ketika belum ada yang pernah menulis ke dalamnya.
  • Kita juga dapat mempertimbangkan untuk menyesuaikan perilaku [field: MaybeNull] pada kata kunci field, atau bahkan bidang secara umum, untuk memungkinkan null juga ditulis ke variabel, seolah-olah AllowNull secara implisit juga ada.

Menjawab pertanyaan LDM

Lokasi sintaks untuk kata kunci

Dalam akses di mana field dan value dapat menghubungkan ke bidang pendukung yang disintesis atau parameter setter implisit, pada lokasi sintaks mana pengidentifikasi harus dipandang sebagai kata kunci?

  1. selalu
  2. ekspresi utama saja
  3. tidak pernah

Dua kasus pertama adalah perubahan mendasar.

Jika pengidentifikasi selalu dianggap sebagai kata kunci, itu adalah perubahan yang mengganggu untuk hal berikut ini, misalnya:

class MyClass
{
    private int field;
    public int P => this.field; // error: expected identifier

    private int value;
    public int Q
    {
        set { this.value = value; } // error: expected identifier
    }
}

Jika pengidentifikasi adalah kata kunci ketika hanya digunakan sebagai ekspresi utama, dampak perubahan menjadi lebih kecil. Jeda yang paling umum mungkin adalah penggunaan anggota yang sudah ada tanpa kualifikasi bernama field.

class MyClass
{
    private int field;
    public int P => field; // binds to synthesized backing field rather than 'this.field'
}

Ada juga jeda ketika field atau value dinyatakan ulang dalam fungsi berlapis. Ini mungkin satu-satunya jeda value untuk ekspresi utama.

class MyClass
{
    private IEnumerable<string> _fields;
    public bool HasNotNullField
    {
        get => _fields.Any(field => field is { }); // 'field' binds to synthesized backing field
    }
    public IEnumerable<string> Fields
    {
        get { return _fields; }
        set { _fields = value.Where(value => Filter(value)); } // 'value' binds to setter parameter
    }
}

Jika pengidentifikasi tidak pernah dianggap sebagai kata kunci, maka pengidentifikasi hanya akan terikat kepada lapangan pendukung yang disintesis atau parameter implisit ketika pengidentifikasi tidak terikat kepada anggota lain. Tidak ada perubahan yang merusak untuk kasus ini.

Menjawab

field adalah kata kunci dalam aksesor yang sesuai saat digunakan sebagai ekspresi utama saja; value tidak pernah dianggap sebagai kata kunci.

Skenario yang mirip dengan { set; }

{ set; } saat ini dilarang dan ini masuk akal: area yang dibuat ini tidak dapat dibaca sama sekali. Sekarang ada cara baru menyebabkan situasi di mana setter memperkenalkan bidang penyimpanan cadangan yang tidak pernah diakses, misalnya pengembangan { set; } menjadi { set => field = value; }.

Manakah dari skenario ini yang harus diizinkan untuk dikompilasi? Asumsikan bahwa peringatan "bidang tidak pernah dibaca" akan berlaku seperti bidang yang dideklarasikan secara manual.

  1. { set; } - Tidak diizinkan hari ini, lanjutkan melarang
  2. { set => field = value; }
  3. { get => unrelated; set => field = value; }
  4. { get => unrelated; set; }
  5. {
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    
  6. {
        get => unrelated;
        set
        {
            if (field == value) return;
            field = value;
            SendEvent(nameof(Prop), value);
        }
    }
    

Menjawab

Hanya melarang apa yang sudah dilarang dalam properti otomatis saat ini, set;tanpa badan.

field dalam akses acara

Haruskah field menjadi kata kunci di aksesor peristiwa, dan haruskah pengkompilasi menghasilkan bidang backing?

class MyClass
{
    public event EventHandler E
    {
        add { field += value; }
        remove { field -= value; }
    }
}

Rekomendasi: field menjadi kata kunci dalam aksesor event, dan tidak ada bidang pendukung yang dihasilkan.

Menjawab

Rekomendasi diambil. field bukan kata kunci dalam aksesor acara, dan tidak ada field pendukung yang dihasilkan.

Nullability dari field

Haruskah ketidakberisian yang diusulkan untuk field diterima? Lihat bagian Nullability, dan pertanyaan yang belum terjawab di dalamnya.

Menjawab

Proposal umum diadopsi. Perilaku spesifik masih perlu ditinjau lebih lanjut.

field dalam penginisialisasi properti

Haruskah field menjadi kata kunci dalam penginisialisasi properti dan mengikat ke bidang backing?

class A
{
    const int field = -1;

    object P1 { get; } = field; // bind to const (ok) or backing field (error)?
}

Apakah ada skenario yang berguna untuk mereferensikan bidang dukungan di penginisialisasi?

class B
{
    object P2 { get; } = (field = 2);        // error: initializer cannot reference instance member
    static object P3 { get; } = (field = 3); // ok, but useful?
}

Dalam contoh di atas, pengikatan ke variabel pendukung akan mengakibatkan kesalahan: "penginisialisasi tidak dapat mereferensikan variabel non-statis".

Menjawab

Kami akan mengikat penginisialisasi seperti pada versi C#sebelumnya. Kami tidak akan menempatkan field pendukung dalam cakupan, dan tidak pula akan mencegah rujukan ke anggota lain yang bernama field.

Interaksi dengan properti parsial

Penginisialisasi

Ketika properti parsial menggunakan field, bagian mana yang dapat dibolehkan memiliki penginisialisasi?

partial class C
{
    public partial int Prop { get; set; } = 1;
    public partial int Prop { get => field; set => field = value; } = 2;
}
  • Tampaknya jelas bahwa kesalahan harus terjadi ketika kedua bagian memiliki pemberi inisialisasi.
  • Kita dapat memikirkan kasus penggunaan di mana definisi atau bagian implementasi mungkin ingin menetapkan nilai awal field.
  • Sepertinya jika kita mengizinkan penginisialisasi pada bagian definisi, secara efektif memaksa pelaksana untuk menggunakan field agar program valid. Apakah itu baik-baik saja?
  • Kami pikir akan umum bagi generator untuk menggunakan field setiap kali bidang dukungan dengan jenis yang sama diperlukan dalam implementasi. Ini sebagian karena generator sering ingin memungkinkan pengguna mereka menggunakan atribut yang ditargetkan [field: ...] pada bagian definisi properti. Dengan menggunakan kata kunci field, beban pengimplementasi generator berkurang dalam "meneruskan" atribut tersebut ke bidang yang ditentukan dan memadamkan peringatan pada properti. Generator yang sama tersebut kemungkinan juga ingin memungkinkan pengguna menentukan nilai awal untuk bidang tersebut.

Rekomendasi: Mengizinkan penginisialisasi pada salah satu bagian properti parsial saat bagian implementasi menggunakan field. Laporkan kesalahan jika kedua komponen memiliki penginisialisasi.

Menjawab

Rekomendasi diterima. Mendeklarasikan atau mengimplementasikan lokasi properti dapat menggunakan penginisialisasi, tetapi tidak keduanya secara bersamaan.

Aksesor otomatis

Sebagaimana dirancang semula, implementasi properti parsial harus memiliki badan untuk semua aksesor. Namun, iterasi terbaru dari fitur kata kunci field telah menyertakan gagasan "aksesor otomatis". Haruskah implementasi properti parsial dapat menggunakan aksesor tersebut? Jika digunakan secara eksklusif, itu akan tidak dapat dibedakan dari deklarasi yang mendefinisikan.

partial class C
{
    public partial int Prop0 { get; set; }
    public partial int Prop0 { get => field; set => field = value; } // this is equivalent to the two "semi-auto" forms below.

    public partial int Prop1 { get; set; }
    public partial int Prop1 { get => field; set; } // is this a valid implementation part?

    public partial int Prop2 { get; set; }
    public partial int Prop2 { get; set => field = value; } // what about this? will there be disagreement about which is the "best" style?

    public partial int Prop3 { get; }
    public partial int Prop3 { get => field; } // it will only be valid to use at most 1 auto-accessor, when a second accessor is manually implemented.

Rekomendasi: Melarang aksesor otomatis dalam implementasi properti parsial, karena batasan di sekitar kapan mereka akan dapat digunakan lebih membingungkan untuk diikuti daripada manfaat mengizinkannya.

Menjawab

Setidaknya satu aksesor implementasi harus diimplementasikan secara manual, tetapi aksesor lain dapat diimplementasikan secara otomatis.

Bidang Readonly

Kapan bidang backing yang disintesis harus dianggap sebagai hanya-baca?

struct S
{
    readonly object P0 { get => field; } = "";         // ok
    object P1          { get => field ??= ""; }        // ok
    readonly object P2 { get => field ??= ""; }        // error: 'field' is readonly
    readonly object P3 { get; set { _ = field; } }     // ok
    readonly object P4 { get; set { field = value; } } // error: 'field' is readonly
}

Ketika bidang pencadangan dianggap read-only, bidang yang dipancarkan ke metadata ditandai initonly, dan kesalahan dilaporkan jika field dimodifikasi selain dalam penginisialisasi atau konstruktor.

Rekomendasi: Bidang pencadangan yang disintesis hanya-baca ketika jenis yang berisi adalah struct dan properti atau jenis yang berisi dinyatakan readonly.

Menjawab

Rekomendasi diterima.

Konteks dan set baca-saja

Haruskah aksesor set diizinkan dalam konteks readonly untuk properti yang menggunakan field?

readonly struct S1
{
    readonly object _p1;
    object P1 { get => _p1; set { } }   // ok
    object P2 { get; set; }             // error: auto-prop in readonly struct must be readonly
    object P3 { get => field; set { } } // ok?
}

struct S2
{
    readonly object _p1;
    readonly object P1 { get => _p1; set { } }   // ok
    readonly object P2 { get; set; }             // error: auto-prop with set marked readonly
    readonly object P3 { get => field; set { } } // ok?
}

Menjawab

Mungkin ada skenario untuk ini di mana Anda menerapkan aksesor set pada struktur readonly dan melewatinya, atau melempar. Kami akan mengizinkan ini.

kode [Conditional]

Haruskah bidang yang disintesis dapat dihasilkan ketika field hanya digunakan dalam panggilan yang dihilangkan ke metode bersyarat?

Misalnya, haruskah bidang dukungan dibuat untuk yang berikut ini dalam build non-DEBUG?

class C
{
    object P
    {
        get
        {
            Debug.Assert(field is null);
            return null;
        }
    }
}

Untuk referensi, bidang untuk parameter konstruktor utama dihasilkan dalam kasus serupa - lihat sharplab.io.

Rekomendasi: Bidang pendukung dihasilkan ketika field hanya digunakan dalam panggilan yang diabaikan untuk metode bersyarat.

Menjawab

Kode Conditional dapat memiliki efek pada kode non-kondisional, seperti Debug.Assert mengubah ke-null-an. Akan aneh jika field tidak memiliki dampak yang sama. Ini juga tidak mungkin muncul di sebagian besar kode, jadi kami akan melakukan hal sederhana dan menerima rekomendasi.

Properti antarmuka dan aksesor otomatis

Apakah kombinasi aksesori yang diterapkan secara manual dan otomatis dikenali untuk properti interface, di mana aksesori yang diterapkan secara otomatis mengacu pada field penyimpanan yang disintesis?

Untuk properti instans, akan muncul kesalahan bahwa bidang instans tidak didukung.

interface I
{
           object P1 { get; set; }                           // ok: not an implementation
           object P2 { get => field; set { field = value; }} // error: instance field

           object P3 { get; set { } } // error: instance field
    static object P4 { get; set { } } // ok: equivalent to { get => field; set { } }
}

Rekomendasi: Aksesor otomatis dikenali di properti interface, dan aksesor otomatis merujuk ke lapangan penunjang yang disintesis. Untuk properti instans, terdapat kesalahan yang dilaporkan bahwa bidang instans tidak didukung.

Menjawab

Standardisasi pada field instans itu sendiri sebagai penyebab kesalahan konsisten dengan properti parsial dalam kelas, dan kami menyukai hasilnya. Rekomendasi diterima.