Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
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 dicatat dalam catatan rapat desain bahasa terkait (LDM).
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Masalah utama: https://github.com/dotnet/csharplang/issues/5737
Ringkasan
Fitur ini memastikan bahwa dalam konstruktor struktur, kami mengidentifikasi bidang yang belum ditetapkan secara eksplisit oleh pengguna sebelum pengembalian atau penggunaan, dan menginisialisasinya secara implisit menjadi default
, untuk menghindari terjadinya kesalahan penugasan yang pasti.
Motivasi
Proposal ini dimunculkan sebagai kemungkinan mitigasi untuk masalah kegunaan yang ditemukan di dotnet/csharplang#5552 dan dotnet/csharplang#5635, serta mengatasi #5563 (semua bidang harus benar-benar ditetapkan, tetapi field
tidak dapat diakses dalam konstruktor).
Sejak C# 1.0, konstruktor struct telah diharuskan untuk menetapkan this
seolah-olah itu adalah parameter out
.
public struct S
{
public int x, y;
public S() // error: Fields 'S.x' and 'S.y' must be fully assigned before control is returned to the caller
{
}
}
Hal ini menimbulkan masalah ketika pengatur didefinisikan secara manual pada properti semi-automatis, karena pengompilasi tidak dapat memperlakukan penetapan properti setara dengan penugasan ke bidang cadangan.
public struct S
{
public int X { get => field; set => field = value; }
public S() // error: struct fields aren't fully assigned. But caller can only assign 'this.field' by assigning 'this'.
{
}
}
Kami berasumsi bahwa memperkenalkan pembatasan dengan tingkat ketelitian lebih tinggi untuk setter, seperti skema di mana setter tidak mengambil ref this
melainkan mengambil out field
sebagai parameter, akan menjadi terlalu spesifik dan tidak lengkap untuk beberapa kasus penggunaan.
Salah satu tantangan mendasar yang kami hadapi adalah ketika properti struct memiliki setter yang diterapkan secara manual, pengguna sering kali harus melakukan beberapa bentuk pengulangan, baik dengan berulang kali menetapkan nilai atau mengulangi logika mereka.
struct S
{
private int _x;
public int X
{
get => _x;
set => _x = value >= 0 ? value : throw new ArgumentOutOfRangeException();
}
// Solution 1: assign some value in the constructor before "really" assigning through the property setter.
public S(int x)
{
_x = default;
X = x;
}
// Solution 2: assign the field once in the constructor, repeating the implementation of the setter.
public S(int x)
{
_x = x >= 0 ? x : throw new ArgumentOutOfRangeException();
}
}
Diskusi sebelumnya
Grup kecil telah melihat masalah ini dan mempertimbangkan beberapa solusi yang mungkin:
- Mengharuskan pengguna menetapkan
this = default
saat properti semi-otomatis telah menerapkan setter secara manual. Kami setuju ini adalah solusi yang salah karena menghapus nilai yang ditetapkan dalam penginisialisasi lapangan. - Menginisialisasi semua bidang dukungan properti otomatis/semi-otomatis secara implisit.
- Ini memecahkan masalah "setter properti semi-otomatis", dan secara kuadrat menempatkan bidang yang dideklarasikan secara eksplisit di bawah aturan yang berbeda: "jangan menginisialisasi bidang saya secara implisit, tetapi secara implisit menginisialisasi properti otomatis saya."
- Berikan cara untuk menetapkan bidang dukungan properti semi-otomatis dan mengharuskan pengguna untuk menetapkannya.
- Ini bisa rumit dibandingkan dengan (2). Sebuah properti otomatis seharusnya "otomatis", dan mungkin itu termasuk inisialisasi "otomatis" dari bidang tersebut. Ini dapat menimbulkan kebingungan ketika bidang dasar ditetapkan melalui penugasan properti, dan ketika fungsi setter dari properti sedang dipanggil.
Kami juga telah menerima umpan balik dari pengguna yang ingin, misalnya, menyertakan beberapa penginisialisasi bidang dalam struktur tanpa harus secara eksplisit menetapkan semuanya. Kami dapat menyelesaikan masalah ini serta masalah "properti semi-otomatis dengan setter yang diimplementasikan secara manual" secara bersamaan.
struct MagnitudeVector3d
{
double X, Y, Z;
double Magnitude = 1;
public MagnitudeVector3d() // error: must assign 'X', 'Y', 'Z' before returning
{
}
}
Menyesuaikan penugasan yang pasti
Alih-alih melakukan analisis penugasan pasti untuk mengidentifikasi kesalahan pada bidang yang tidak ditetapkan di this
, kami melakukannya untuk mengidentifikasi bidang mana yang perlu diinisialisasi secara implisit. Inisialisasi tersebut disisipkan di awal konstruktor.
struct S
{
int x, y;
// Example 1
public S()
{
// ok. Compiler inserts an assignment of `this = default`.
}
// Example 2
public S()
{
// ok. Compiler inserts an assignment of `y = default`.
x = 1;
}
// Example 3
public S()
{
// valid since C# 1.0. Compiler inserts no implicit assignments.
x = 1;
y = 2;
}
// Example 4
public S(bool b)
{
// ok. Compiler inserts assignment of `this = default`.
if (b)
x = 1;
else
y = 2;
}
// Example 5
void M() { }
public S(bool b)
{
// ok. Compiler inserts assignment of `y = default`.
x = 1;
if (b)
M();
y = 2;
}
}
Dalam contoh (4) dan (5), codegen yang dihasilkan terkadang memiliki "penugasan ganda" bidang. Ini umumnya baik-baik saja, tetapi bagi pengguna yang peduli dengan penugasan ganda seperti itu, kita dapat mengeluarkan diagnostik kesalahan penugasan pasti yang sebelumnya sebagai diagnostik peringatan yang dinonaktifkan secara default
struct S
{
int x;
public S() // warning: 'S.x' is implicitly initialized to 'default'.
{
}
}
Pengguna yang mengatur tingkat keparahan diagnostik ini menjadi "kesalahan" akan ikut serta dalam perilaku pra-C# 11. Pengguna tersebut pada dasarnya "diblokir" dari properti semi-otomatis dengan setter yang diimplementasikan secara manual.
struct S
{
public int X
{
get => field;
set => field = field < value ? value : field;
}
public S() // error: backing field of 'S.X' is implicitly initialized to 'default'.
{
X = 1;
}
}
Pada pandangan pertama, ini terasa seperti "lubang" dalam fitur, tetapi sebenarnya ini adalah langkah yang tepat. Dengan mengaktifkan diagnostik, pengguna memberi tahu kami bahwa mereka tidak ingin pengkompilasi menginisialisasi bidang mereka secara implisit di konstruktor. Tidak ada cara untuk menghindari inisialisasi implisit di sini, jadi solusi bagi mereka adalah dengan menggunakan cara yang berbeda untuk menginisialisasi bidang daripada setter yang diimplementasikan secara manual, seperti mendeklarasikan bidang secara manual dan menetapkannya, atau dengan menyertakan penginisialisasi bidang.
Saat ini, JIT tidak menghilangkan penyimpanan mati melalui ref, yang berarti bahwa inisialisasi implisit ini memang memiliki biaya nyata. Tapi itu mungkin bisa diperbaiki. https://github.com/dotnet/runtime/issues/13727
Perlu diingat bahwa menginisialisasi bidang individu alih-alih seluruh instans sebenarnya hanyalah upaya pengoptimalan. Kompilator mungkin harus bebas untuk mengimplementasikan heuristik apa pun yang diinginkannya, selama memenuhi invarian bahwa bidang yang tidak pasti ditetapkan di semua titik pengembalian atau diinisialisasi secara implisit sebelum akses anggota non-bidang this
.
Misalnya, jika struct memiliki 100 bidang, dan hanya salah satunya yang diinisialisasi secara eksplisit, mungkin lebih masuk akal untuk melakukan initobj
pada seluruh hal, daripada secara implisit memancarkan initobj
untuk 99 bidang lainnya. Namun, implementasi yang secara implisit memancarkan initobj
untuk 99 bidang lain masih akan valid.
Perubahan pada spesifikasi bahasa
Kami menyesuaikan bagian berikut dari standar:
https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#12814-this-access
Jika deklarasi konstruktor tidak memiliki inisialisasi konstruktor, variabel
this
berperilaku sama persis dengan parameterout
dari jenis struct. Secara khusus, ini berarti bahwa variabel harus ditetapkan di setiap jalur eksekusi konstruktor instans.
Kami menyesuaikan bahasa ini agar dapat dibaca dengan lebih baik.
Jika deklarasi konstruktor tidak memiliki inisialisasi konstruktor, variabel this
berperilaku mirip dengan parameter out
dari jenis struct, kecuali bahwa itu bukan kesalahan ketika persyaratan penugasan yang pasti (§9.4.1) tidak terpenuhi. Sebagai gantinya, kami memperkenalkan perilaku berikut:
- Ketika variabel
this
itu sendiri tidak memenuhi persyaratan, maka semua variabel instans yang tidak ditetapkan dalamthis
di semua titik di mana persyaratan dilanggar secara implisit diinisialisasi ke nilai default (§9,3) dalam fase inisialisasi sebelum kode lain dalam konstruktor berjalan. - Ketika variabel instans
v dalamtidak memenuhi persyaratan, atau variabel instans apa pun pada tingkat berlapis apa pun dalam v tidak memenuhi persyaratan, makav secara implisit diinisialisasi ke nilai default dalam fase inisialisasisebelum kode lain dalam konstruktor berjalan.
Rapat perancangan
C# feature specifications