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 .
Prioritas utama: https://github.com/dotnet/csharplang/issues/7431
Ringkasan
Menyediakan mekanisme umum dan aman untuk mengonsumsi jenis struktur yang menggunakan fitur InlineArrayAttribute. Menyediakan mekanisme tujuan umum dan aman untuk mendeklarasikan array sebaris dalam kelas, struktur, dan antarmuka C#.
Catatan: Versi sebelumnya dari spesifikasi ini menggunakan istilah "ref-safe-to-escape" dan "safe-to-escape", yang diperkenalkan dalam spesifikasi fitur keamanan rentang . Komite standar ECMA mengubah nama menjadi "konteks aman-ref" dan "konteks aman". Nilai konteks aman telah disempurnakan untuk menggunakan "declaration-block", "function-member", dan "caller-context" secara konsisten. Speklet telah menggunakan penyusunan frasa yang berbeda untuk istilah-istilah ini, dan juga menggunakan "safe-to-return" sebagai sinonim untuk "caller-context". Speklet ini telah diperbarui untuk menggunakan istilah dalam standar C# 7.3.
Motivasi
Proposal ini bertujuan untuk mengatasi banyak batasan https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers. Secara khusus bertujuan untuk memungkinkan:
- mengakses elemen jenis struct menggunakan fitur InlineArrayAttribute;
- deklarasi array inline untuk tipe terkelola dan tidak terkelola dalam
struct,class, atauinterface.
Dan memberikan verifikasi keamanan bahasa untuk mereka.
Desain Terperinci
Baru-baru ini runtime menambahkan fitur InlineArrayAttribute. Singkatnya, pengguna dapat mendeklarasikan jenis struktur seperti berikut ini:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer
{
private object _element0;
}
Runtime menyediakan tata letak jenis khusus untuk jenis Buffer:
- Ukuran tipe diperluas untuk menampung 10 elemen (angka ini berasal dari atribut InlineArray) dari tipe
object(tipe ini berasal dari tipe satu-satunya bidang instans dalam struktur,_element0dalam contoh ini). - Elemen pertama diselaraskan dengan bidang instans dan dengan awal struktur
- Elemen-elemen diletakkan secara berurutan dalam memori seolah-olah mereka adalah elemen array.
Runtime menyediakan pelacakan GC reguler untuk semua elemen dalam struktur.
Proposal ini akan merujuk ke jenis seperti ini sebagai "jenis array sebaris".
Elemen tipe array sebaris dapat diakses melalui pointer atau melalui instans rentang yang dikembalikan oleh System.Runtime.InteropServices.MemoryMarshal.CreateSpan/System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan API. Namun, baik pendekatan pointer maupun API tidak secara otomatis menyediakan pengecekan tipe dan batas.
Bahasa akan menyediakan cara yang aman tipe/rujukan untuk mengakses elemen tipe array linier. Akses akan berbasis jangkauan. Ini membatasi dukungan untuk tipe array yang sebaris dengan tipe elemen yang dapat digunakan sebagai argumen tipe. Misalnya, jenis penunjuk tidak dapat digunakan sebagai jenis elemen. Contoh lain dari tipe rentang.
Mendapatkan instans tipe rentang untuk tipe array dalam baris
Karena ada jaminan bahwa elemen pertama dalam jenis array sebaris diratakan di awal jenis (tanpa celah), pengkompilasi akan menggunakan kode berikut untuk mendapatkan nilai Span:
MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
Dan kode berikut untuk mendapatkan nilai ReadOnlySpan:
MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
Untuk mengurangi ukuran IL di lokasi penggunaan, kompilator harus dapat menambahkan dua pembantu generik yang dapat digunakan kembali ke dalam tipe detail implementasi privat dan menggunakannya di semua lokasi penggunaan dalam program yang sama.
public static System.Span<TElement> InlineArrayAsSpan<TBuffer, TElement>(ref TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateSpan(ref Unsafe.As<TBuffer, TElement>(ref buffer), size);
}
public static System.ReadOnlySpan<TElement> InlineArrayAsReadOnlySpan<TBuffer, TElement>(in TBuffer buffer, int size) where TBuffer : struct
{
return MemoryMarshal.CreateReadOnlySpan(ref Unsafe.As<TBuffer, TElement>(ref Unsafe.AsRef(in buffer)), size);
}
Akses elemen
Akses Element akan diperluas untuk mendukung akses elemen array sebaris.
element_access terdiri dari primary_no_array_creation_expression, diikuti dengan token "[", diikuti oleh argument_list, diikuti dengan token "]".
argument_list terdiri dari satu atau beberapa argumen , dipisahkan oleh koma.
element_access
: primary_no_array_creation_expression '[' argument_list ']'
;
argument_listelement_access tidak diizinkan untuk berisi argumen ref atau out.
element_access terikat secara dinamis (§11.3.3) jika setidaknya salah satu dari kondisi berikut terpenuhi:
-
primary_no_array_creation_expression memiliki tipe waktu kompilasi
dynamic. - Setidaknya satu ekspresi argument_list memiliki tipe waktu kompilasi
dynamicdan primary_no_array_creation_expression tidak memiliki tipe array, dan primary_no_array_creation_expression tidak memiliki tipe array sebaris atau ada lebih dari satu item dalam daftar argumen.
Dalam hal ini, pengkompilasi mengklasifikasikan element_access sebagai nilai jenis dynamic. Aturan di bawah ini untuk menentukan arti element_access kemudian diterapkan pada waktu run-time, dengan menggunakan jenis run-time sebagai pengganti jenis waktu kompilasi dari ekspresi primary_no_array_creation_expression dan argument_list yang memiliki jenis waktu kompilasi dynamic. Jika primary_no_array_creation_expression tidak memiliki jenis waktu kompilasi dynamic, maka akses elemen mengalami pemeriksaan waktu kompilasi terbatas seperti yang dijelaskan dalam §11.6.5.
Jika primary_no_array_creation_expression dari element_access adalah nilai dari array_type, maka element_access adalah akses array (§12.8.12.2). Jika primary_no_array_creation_expressionelement_access adalah variabel atau nilai dari tipe array in-line dan argument_list terdiri dari satu argumen, maka element_access adalah akses elemen array in-line. Jika tidak, primary_no_array_creation_expression harus berupa variabel atau nilai dari tipe kelas, struktur, atau antarmuka yang memiliki satu atau beberapa anggota pengindeks, dalam hal ini element_access adalah akses melalui pengindeks (§12.8.12.3).
Akses elemen array sebaris
Untuk akses elemen array sebaris, ekspresi primary_no_array_creation_expression dari element_access harus berupa variabel atau nilai dari jenis array sebaris. Selain itu, argument_list akses elemen array sebaris tidak diizinkan untuk berisi argumen bernama. argument_list harus berisi ekspresi tunggal, dan ekspresi tersebut harus
- jenis
int, atau - secara implisit dapat dikonversi ke
int, atau - secara implisit dapat dikonversi ke
System.Index, atau - secara implisit dapat dikonversi ke
System.Range.
Saat jenis ekspresi adalah int
Jika primary_no_array_creation_expression adalah variabel yang dapat ditulis, hasil dari mengevaluasi akses elemen array inline adalah variabel yang dapat ditulis yang setara dengan memanggil public ref T this[int index] { get; } dengan nilai bilangan bulat tersebut pada instans System.Span<T> yang dikembalikan oleh metode System.Span<T> InlineArrayAsSpan pada primary_no_array_creation_expression. Untuk tujuan analisis keamanan ref, konteks ref-aman /dan konteks aman dari akses setara dengan konteks yang sama untuk pemanggilan metode dengan tanda tangan static ref T GetItem(ref InlineArrayType array).
Variabel yang dihasilkan dianggap dapat bergerak jika dan hanya jika primary_no_array_creation_expression dapat bergerak.
Jika primary_no_array_creation_expression adalah variabel baca-saja, hasil mengevaluasi akses elemen array sebaris adalah variabel baca-saja yang setara dengan memanggil public ref readonly T this[int index] { get; } dengan nilai bilangan bulat tersebut pada instans System.ReadOnlySpan<T> yang dikembalikan oleh metode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan pada primary_no_array_creation_expression. Untuk tujuan analisis keamanan ref, konteks ref-aman /dan konteks aman dari akses setara dengan konteks yang sama untuk pemanggilan metode dengan tanda tangan static ref readonly T GetItem(in InlineArrayType array).
Variabel yang dihasilkan dianggap dapat bergerak jika dan hanya jika primary_no_array_creation_expression dapat bergerak.
Jika primary_no_array_creation_expression adalah nilai, hasil dari evaluasi akses elemen array sebaris tersebut adalah nilai yang setara dengan memanggil public ref readonly T this[int index] { get; } menggunakan nilai bilangan bulat tersebut pada sebuah instans System.ReadOnlySpan<T> yang dikembalikan oleh metode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan pada primary_no_array_creation_expression. Untuk tujuan analisis keamanan ref, konteks ref-aman /dan konteks aman dari akses setara dengan konteks yang sama untuk pemanggilan metode dengan tanda tangan static T GetItem(InlineArrayType array).
Misalnya:
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
}
void M1(Buffer10<int> x)
{
ref int a = ref x[0]; // Ok, equivalent to `ref int a = ref InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)[0]`
}
void M2(in Buffer10<int> x)
{
ref readonly int a = ref x[0]; // Ok, equivalent to `ref readonly int a = ref InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)[0]`
ref int b = ref x[0]; // An error, `x` is a readonly variable => `x[0]` is a readonly variable
}
Buffer10<int> GetBuffer() => default;
void M3()
{
int a = GetBuffer()[0]; // Ok, equivalent to `int a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(GetBuffer(), 10)[0]`
ref readonly int b = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
ref int c = ref GetBuffer()[0]; // An error, `GetBuffer()[0]` is a value
}
Mengindeks ke dalam array inline dengan ekspresi konstan di luar batas yang dideklarasikan adalah merupakan kesalahan kompilasi.
Saat ekspresi secara implisit dapat dikonversi ke int
Ekspresi dikonversi ke int lalu akses elemen ditafsirkan seperti yang dijelaskan dalam Saat jenis ekspresi adalah bagian int.
Saat ekspresi secara implisit dapat dikonversi ke System.Index
Ekspresi dikonversi ke System.Index, yang kemudian diubah menjadi nilai indeks berbasis int seperti yang dijelaskan pada https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-index-support, dengan asumsi bahwa panjang koleksi diketahui pada waktu kompilasi dan sama dengan jumlah elemen dalam jenis array sebaris primary_no_array_creation_expression. Kemudian akses elemen ditafsirkan seperti yang dijelaskan dalam Saat jenis ekspresi adalah bagian int.
Saat ekspresi secara implisit dapat dikonversi ke System.Range
Jika primary_no_array_creation_expression adalah variabel yang dapat ditulis, hasil dari evaluasi akses elemen array sebaris adalah nilai yang setara dengan memanggil public Span<T> Slice (int start, int length) pada instance System.Span<T> yang dihasilkan oleh metode System.Span<T> InlineArrayAsSpan pada primary_no_array_creation_expression.
Untuk tujuan analisis keamanan ref, konteks ref-aman /dan konteks aman dari akses setara dengan konteks yang sama untuk pemanggilan metode dengan tanda tangan static System.Span<T> GetSlice(ref InlineArrayType array).
Jika primary_no_array_creation_expression adalah variabel baca-saja, hasil mengevaluasi akses elemen array sebaris adalah nilai yang setara dengan memanggil public ReadOnlySpan<T> Slice (int start, int length) pada instans System.ReadOnlySpan<T> yang dikembalikan oleh metode System.ReadOnlySpan<T> InlineArrayAsReadOnlySpan pada primary_no_array_creation_expression.
Untuk tujuan analisis keamanan ref, konteks ref-aman /dan konteks aman dari akses setara dengan konteks yang sama untuk pemanggilan metode dengan tanda tangan static System.ReadOnlySpan<T> GetSlice(in InlineArrayType array).
Jika primary_no_array_creation_expression adalah nilai, kesalahan akan dilaporkan.
Argumen untuk pemanggilan metode Slice dihitung dari ekspresi indeks yang dikonversi ke System.Range seperti yang dijelaskan pada https://github.com/dotnet/csharplang/blob/main/proposals/csharp-8.0/ranges.md#implicit-range-support, dengan asumsi bahwa panjang koleksi diketahui pada waktu kompilasi dan sama dengan jumlah elemen dalam jenis array sebaris primary_no_array_creation_expression.
Compiler dapat menghilangkan panggilan Slice jika diketahui pada waktu kompilasi bahwa start adalah 0 dan length kurang atau sama dengan jumlah elemen dalam jenis array sebaris. Compiler juga dapat melaporkan kesalahan jika pada waktu kompilasi diketahui bahwa slicing keluar dari batas array inline.
Misalnya:
void M1(Buffer10<int> x)
{
System.Span<int> a = x[..]; // Ok, equivalent to `System.Span<int> a = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10).Slice(0, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x[..]; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10).Slice(0, 10)`
System.Span<int> b = x[..]; // An error, System.ReadOnlySpan<int> cannot be converted to System.Span<int>
}
Buffer10<int> GetBuffer() => default;
void M3()
{
_ = GetBuffer()[..]; // An error, `GetBuffer()` is a value
}
Konversi
Konversi baru akan ditambahkan, yaitu konversi array sebaris dari ekspresi. Konversi array sebaris adalah konversi standar .
Ada konversi implisit dari ekspresi jenis array sebaris ke jenis berikut:
System.Span<T>System.ReadOnlySpan<T>
Namun, mengonversi variabel readonly menjadi System.Span<T> atau mengonversi nilai ke salah satu jenis adalah kesalahan.
Misalnya:
void M1(Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // Ok, equivalent to `System.Span<int> b = InlineArrayAsSpan<Buffer10<int>, int>(ref x, 10)`
}
void M2(in Buffer10<int> x)
{
System.ReadOnlySpan<int> a = x; // Ok, equivalent to `System.ReadOnlySpan<int> a = InlineArrayAsReadOnlySpan<Buffer10<int>, int>(in x, 10)`
System.Span<int> b = x; // An error, readonly mismatch
}
Buffer10<int> GetBuffer() => default;
void M3()
{
System.ReadOnlySpan<int> a = GetBuffer(); // An error, ref-safety
System.Span<int> b = GetBuffer(); // An error, ref-safety
}
Untuk keperluan analisis keamanan ref, konteks aman dari konversi setara dengan konteks aman untuk pemanggilan metode dengan tanda tangan static System.Span<T> Convert(ref InlineArrayType array), atau static System.ReadOnlySpan<T> Convert(in InlineArrayType array).
Daftar pola
Pola daftar tidak akan didukung untuk instans jenis array sebaris.
Pemeriksaan pengalokasian yang pasti
Aturan penetapan pasti reguler berlaku untuk variabel yang memiliki jenis array sebaris.
Literal koleksi
Instans tipe array in-line adalah ekspresi yang valid dalam spread_element.
Fitur berikut tidak dikirim dalam C# 12. Ini tetap menjadi proposal terbuka. Kode dalam contoh ini menghasilkan CS9174:
Jenis array sebaris adalah jenis target koleksi yang valid untuk ekspresi koleksi . Misalnya:
Buffer10<int> b = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; // initializes user-defined inline array
Panjang koleksi literal harus sesuai dengan panjang tipe array sebaris target. Jika panjang harfiah diketahui pada waktu kompilasi dan tidak cocok dengan panjang target, kesalahan akan dilaporkan. Jika tidak, pengecualian akan dilemparkan pada runtime setelah ketidakcocokan ditemui. Jenis pengecualian yang tepat adalah TBD. Beberapa kandidat adalah: System.NotSupportedException, System.InvalidOperationException.
Validasi aplikasi InlineArrayAttribute
Compiler akan memvalidasi aspek berikut dari aplikasi InlineArrayAttribute:
- Jenis target adalah struktur non-rekam
- Jenis target hanya memiliki satu bidang
- Panjang yang ditentukan > 0
- Struktur target tidak memiliki tata letak eksplisit yang ditentukan
Elemen Array Sebaris dalam penginisialisasi objek
Secara default, inisialisasi elemen tidak akan didukung melalui initializer_target formulir '[' argument_list ']' (lihat https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/expressions.md#128173-object-initializers):
static C M2() => new C() { F = {[0] = 111} }; // error CS1913: Member '[0]' cannot be initialized. It is not a field or property.
class C
{
public Buffer10<int> F;
}
Namun, jika jenis array sebaris secara eksplisit menentukan pengindeks yang sesuai, penginisialisasi objek akan menggunakannya:
static C M2() => new C() { F = {[0] = 111} }; // Ok, indexer is invoked
class C
{
public Buffer10<int> F;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
public T this[int i]
{
get => this[i];
set => this[i] = value;
}
}
Pernyataan foreach
Pernyataan foreach akan disesuaikan untuk memungkinkan penggunaan tipe array dalam satu baris sebagai koleksi dalam pernyataan foreach.
Misalnya:
foreach (var a in getBufferAsValue())
{
WriteLine(a);
}
foreach (var b in getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in getBufferAsReadonlyVariable())
{
WriteLine(c);
}
Buffer10<int> getBufferAsValue() => default;
ref Buffer10<int> getBufferAsWritableVariable() => default;
ref readonly Buffer10<int> getBufferAsReadonlyVariable() => default;
setara dengan:
Buffer10<int> temp = getBufferAsValue();
foreach (var a in (System.ReadOnlySpan<int>)temp)
{
WriteLine(a);
}
foreach (var b in (System.Span<int>)getBufferAsWritableVariable())
{
WriteLine(b);
}
foreach (var c in (System.ReadOnlySpan<int>)getBufferAsReadonlyVariable())
{
WriteLine(c);
}
Kami akan mendukung foreach untuk array sebaris, meskipun awalnya terbatas dalam metode async karena keterlibatan tipe rentang dalam terjemahan.
Pertanyaan desain terbuka
Alternatif
Sintaks jenis array sebaris
Tata bahasa pada https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general akan disesuaikan sebagai berikut:
array_type
: non_array_type rank_specifier+
;
rank_specifier
: '[' ','* ']'
+ | '[' constant_expression ']'
;
Jenis constant_expression harus secara implisit dapat dikonversi ke jenis int, dan nilainya harus berupa bilangan bulat positif bukan nol.
Bagian yang relevan dari bagian https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#1721-general akan disesuaikan sebagai berikut.
Produksi aturan tata bahasa untuk tipe array terdapat pada https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/types.md#821-general.
Jenis array ditulis sebagai non_array_type diikuti oleh satu atau beberapa rank_specifier.
non_array_type adalah jenis yang bukan merupakan array_type.
Tingkat jenis array ditentukan oleh rank_specifier paling kiri dalam array_type: A rank_specifier mengindikasikan bahwa array adalah array dengan tingkat satu ditambah jumlah token "," di rank_specifier.
Jenis elemen dalam tipe array adalah jenis yang didapatkan dari menghapus rank_specifier paling kiri.
- Jenis array dalam bentuk
T[ constant_expression ]adalah jenis array anonim sebaris dengan panjang yang dinyatakan oleh konstanta constant_expression dan jenis elemen non-arrayT. - Jenis array berbentuk
T[ constant_expression ][R₁]...[Rₓ]adalah jenis array anonim tertanam dengan panjang yang ditentukan oleh ekspresi_konstan dan jenis elemenT[R₁]...[Rₓ]. - Jenis array dari bentuk
T[R](di mana R bukan sebuah ekspresi_konstan) adalah jenis array reguler dengan peringkatRdan tipe elemen yang bukan arrayT. - Jenis array dari jenis
T[R][R₁]...[Rₓ](di mana R bukan sebuah constant_expression) adalah jenis array reguler dengan tingkatanRdan jenis elemenT[R₁]...[Rₓ].
Akibatnya, rank_specifierdibaca dari kiri ke kanan sebelum jenis elemen non-array akhir.
Contoh: Jenis dalam
int[][,,][,]adalah array berdimensi tunggal dari array tiga dimensi dari array dua dimensi dariint. contoh akhir
Pada waktu eksekusi, nilai dari jenis array reguler dapat berupa null atau referensi ke instans dari jenis array tersebut.
Catatan: Mengikuti aturan https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/arrays.md#176-array-covariance, nilainya mungkin juga merupakan referensi ke jenis array kovarian. catatan akhir
Jenis array sebaris anonim adalah jenis array sebaris yang disintesis kompilator dengan aksesibilitas internal. Jenis elemen harus berupa jenis yang dapat digunakan sebagai argumen jenis. Tidak seperti jenis array sebaris yang dideklarasikan secara eksplisit, jenis array sebaris anonim tidak dapat direferensikan berdasarkan nama, hanya dapat direferensikan dengan sintaks array_type. Dalam konteks program yang sama, dua array_typeyang menandakan tipe array sebaris dari tipe elemen yang sama dan dengan panjang yang sama, merujuk pada tipe array sebaris anonim yang sama.
Selain aksesibilitas internal, pengompilasi akan mencegah penggunaan API yang menggunakan jenis array sebaris anonim melintasi batas perakitan dengan menerapkan pengubah khusus yang diperlukan (jenis tepat TBD) pada referensi jenis array sebaris anonim dalam tanda tangan.
Ekspresi pembuatan array
array_creation_expression
: 'new' non_array_type '[' expression_list ']' rank_specifier*
array_initializer?
| 'new' array_type array_initializer
| 'new' rank_specifier array_initializer
;
Mengingat tata bahasa saat ini, penggunaan constant_expression sebagai pengganti expression_list sudah berarti mengalokasikan tipe array satu dimensi biasa dengan panjang yang ditentukan. Oleh karena itu, array_creation_expression akan terus mewakili alokasi array reguler.
Namun, bentuk baru dari rank_specifier dapat digunakan untuk memasukkan jenis array anonim sebaris ke dalam jenis elemen array yang dialokasikan.
Misalnya, ekspresi berikut membuat array reguler panjang 2 dengan jenis elemen dari jenis array sebaris anonim dengan tipe elemen int dan panjang 5:
new int[2][5];
new int[][5] {default, default};
new [] {default(int[5]), default(int[5])};
Penginisialisasi array
Penginisialisasi array tidak diimplementasikan dalam C# 12. Bagian ini tetap menjadi proposal aktif.
Bagian penginisialisasi Array akan disesuaikan untuk memungkinkan penggunaan array_initializer untuk menginisialisasi jenis array inline (tidak ada perubahan pada tata bahasa yang diperlukan).
array_initializer
: '{' variable_initializer_list? '}'
| '{' variable_initializer_list ',' '}'
;
variable_initializer_list
: variable_initializer (',' variable_initializer)*
;
variable_initializer
: expression
| array_initializer
;
Panjang array sebaris harus diberikan secara eksplisit oleh tipe target.
Misalnya:
int[5] a = {1, 2, 3, 4, 5}; // initializes anonymous inline array of length 5
Buffer10<int> b = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // initializes user-defined inline array
var c = new int[][] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
var d = new int[][2] {{11, 12}, {21, 22}, {31, 32}}; // An error for the nested array initializer
Desain Terperinci (Opsi 2)
Perhatikan, bahwa untuk tujuan proposal ini istilah "buffer ukuran tetap" mengacu pada fitur "buffer ukuran tetap aman" yang diusulkan daripada ke buffer yang dijelaskan di https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/unsafe-code.md#238-fixed-size-buffers.
Dalam desain ini, jenis buffer berukuran tetap tidak mendapatkan perlakuan khusus secara umum oleh bahasa. Ada sintaks khusus untuk mendeklarasikan anggota yang mewakili buffer ukuran tetap dan aturan baru tentang penggunaan anggota tersebut. Bidang tersebut bukan bidang dari sudut pandang bahasa.
Tata bahasa untuk variable_declarator dalam https://github.com/dotnet/csharpstandard/blob/draft-v8/standard/classes.md#155-fields akan diperluas untuk memungkinkan menentukan ukuran buffer:
field_declaration
: attributes? field_modifier* type variable_declarators ';'
;
field_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| 'static'
| 'readonly'
| 'volatile'
| unsafe_modifier // unsafe code support
;
variable_declarators
: variable_declarator (',' variable_declarator)*
;
variable_declarator
: identifier ('=' variable_initializer)?
+ | fixed_size_buffer_declarator
;
fixed_size_buffer_declarator
: identifier '[' constant_expression ']'
;
Deklarasi buffer_ukuran_tetap memperkenalkan sebuah buffer berukuran tetap untuk tipe elemen tertentu.
Jenis elemen buffer adalah jenis ditentukan dalam field_declaration. Deklarator buffer dengan ukuran tetap memperkenalkan anggota baru, dan terdiri dari pengidentifikasi yang menamai anggota, diikuti dengan ekspresi konstanta yang diapit dalam token [ dan ]. Ekspresi konstanta menunjukkan jumlah elemen dalam anggota yang diperkenalkan oleh deklarator buffer ukuran tetap tersebut. Tipe ekspresi konstanta harus secara implisit dapat dikonversi ke tipe int, dan nilainya harus berupa bilangan bulat positif bukan nol.
Elemen buffer ukuran tetap harus ditata secara berurutan dalam memori seolah-olah mereka adalah elemen array.
field_declaration dengan fixed_size_buffer_declarator dalam antarmuka harus mempunyai pengubah static.
Bergantung pada situasi (detail ditentukan di bawah), akses ke anggota buffer ukuran tetap diklasifikasikan sebagai nilai (tidak pernah variabel) baik dari System.ReadOnlySpan<S> atau System.Span<S>, di mana S adalah jenis elemen dari buffer ukuran tetap. Kedua jenis menyediakan pengindeks yang mengembalikan referensi ke elemen tertentu dengan "readonly-ness" yang sesuai, yang mencegah penugasan langsung ke elemen ketika aturan bahasa tidak mengizinkannya.
Ini membatasi kumpulan jenis yang dapat digunakan sebagai jenis elemen buffer ukuran tetap ke jenis yang dapat digunakan sebagai argumen jenis. Misalnya, jenis penunjuk tidak dapat digunakan sebagai jenis elemen.
Instans rentang yang dihasilkan akan memiliki panjang yang sama dengan ukuran yang dideklarasikan pada buffer ukuran tetap. Pengindeksan ke dalam rentang dengan ekspresi konstan yang berada di luar batas yang dinyatakan untuk buffer ukuran tetap adalah kesalahan pada waktu kompilasi.
konteks aman dari nilai akan sama dengan konteks aman dari kontainer, seperti ketika data cadangan diakses sebagai suatu bidang.
Buffer ukuran tetap dalam ekspresi
Pencarian anggota buffer ukuran tetap berlanjut persis seperti pencarian anggota kolom.
Buffer berukuran tetap dapat dirujuk dalam sebuah ekspresi menggunakan simple_name atau member_access .
Ketika anggota buffer ukuran tetap instans direferensikan sebagai nama sederhana, efeknya sama dengan akses anggota formulir this.I, di mana I adalah anggota buffer ukuran tetap. Ketika anggota buffer ukuran tetap statis direferensikan sebagai nama sederhana, efek yang ditimbulkan serupa dengan akses anggota dalam bentuk E.I, di mana I adalah anggota buffer ukuran tetap dan E adalah tipe yang mendeklarasikan.
Buffer ukuran tetap non-readonly
Dalam bentuk akses anggota E.I, jika E adalah tipe struct dan pencarian anggota I dalam tipe struct tersebut mengidentifikasi anggota instans berukuran tetap yang tidak readonly, maka E.I dievaluasi dan diklasifikasikan sebagai berikut:
- Jika
Ediklasifikasikan sebagai nilai, makaE.Ihanya dapat digunakan sebagai primary_no_array_creation_expression dari akses elemen dengan indeks dari jenisSystem.Index, atau jenis yang secara implisit dapat dikonversi ke int. Hasil dari akses elemen adalah elemen dari anggota dengan ukuran tetap pada posisi yang ditentukan, yang diklasifikasikan sebagai nilai. - Jika tidak, jika
Ediklasifikasikan sebagai variabel hanya baca dan hasil ekspresi diklasifikasikan sebagai nilai dengan jenisSystem.ReadOnlySpan<S>, di mana S adalah jenis elemenI. Nilai dapat digunakan untuk mengakses elemen anggota. - Jika tidak,
Ediklasifikasikan sebagai variabel yang dapat ditulis dan hasil ekspresi diklasifikasikan sebagai nilai jenisSystem.Span<S>, di mana S adalah jenis elemenI. Nilai dapat digunakan untuk mengakses elemen anggota.
Dalam akses anggota dengan bentuk E.I, jika E bertipe kelas dan pencarian anggota I dalam tipe kelas tersebut mengidentifikasi anggota instans berdimensi tetap non-readonly, maka E.I dievaluasi dan diklasifikasikan sebagai nilai dari jenis System.Span<S>, dengan S adalah tipe elemen dari I.
Dalam akses anggota berbentuk E.I, jika pencarian anggota I mengidentifikasi anggota ukuran tetap statis non-readonly, maka E.I dievaluasi dan diklasifikasikan sebagai nilai tipe System.Span<S>, di mana S merupakan tipe elemen I.
Buffer hanya baca berukuran tetap
Ketika field_declaration menyertakan pengubah readonly, anggota yang diperkenalkan oleh fixed_size_buffer_declarator adalah buffer ukuran tetap readonly .
Penugasan langsung ke elemen buffer berukuran tetap yang hanya-baca hanya dapat terjadi dalam konstruktor instans, anggota inisialisasi, atau konstruktor statis dalam tipe yang sama.
Secara khusus, penugasan langsung ke elemen buffer berukuran tetap yang bersifat baca-saja hanya diizinkan dalam konteks berikut:
- Untuk anggota instans, dalam konstruktor instans atau anggota init dari jenis yang berisi deklarasi anggota; untuk anggota statis, dalam konstruktor statis dari jenis yang berisi deklarasi anggota. Ini juga merupakan satu-satunya konteks di mana elemen buffer ukuran tetap baca-saja dapat diteruskan secara valid sebagai parameter
outatauref.
Mencoba menetapkan elemen dari buffer ukuran tetap yang bersifat hanya-baca atau meneruskannya sebagai parameter out atau ref dalam konteks lain adalah kesalahan pada waktu kompilasi.
Hal ini dicapai dengan yang berikut ini.
Akses anggota untuk buffer ukuran tetap baca-saja dievaluasi dan diklasifikasikan sebagai berikut:
- Dalam akses anggota dengan bentuk
E.I, jikaEadalah jenis struct danEdiklasifikasikan sebagai nilai, makaE.Ihanya dapat digunakan sebagai primary_no_array_creation_expression dari akses elemen dengan indeks dari jenisSystem.Index, atau dari jenis yang secara implisit dapat dikonversi ke int. Hasil akses elemen adalah elemen anggota berukuran tetap pada posisi yang ditentukan, diklasifikasikan sebagai nilai. - Jika akses terjadi dalam konteks di mana penempatan langsung ke elemen buffer ukuran tetap baca-saja diizinkan, hasil ekspresi diklasifikasikan sebagai nilai dengan jenis
System.Span<S>, di mana S adalah jenis elemen dari buffer ukuran tetap. Nilai dapat digunakan untuk mengakses elemen anggota. - Jika tidak, ekspresi diklasifikasikan sebagai nilai jenis
System.ReadOnlySpan<S>, di mana S adalah jenis elemen dari buffer ukuran tetap. Nilai dapat digunakan untuk mengakses elemen anggota.
Pemeriksaan pengalokasian yang pasti
Buffer ukuran tetap tidak terkena pemeriksaan penetapan pasti, dan anggota buffer ukuran tetap diabaikan dalam konteks pemeriksaan penetapan pasti pada variabel tipe struct.
Ketika anggota buffer ukuran tetap bersifat statis, atau struktur terluar yang berisi variabel anggota buffer ukuran tetap adalah variabel statis, variabel instans kelas, atau elemen array, maka elemen-elemen dari buffer ukuran tetap tersebut secara otomatis diinisialisasi ke nilai defaultnya. Dalam semua kasus lain, konten awal buffer berukuran tetap tidak terdefinisi.
Metadata
Pemancaran metadata dan pembuatan kode
Compiler akan mengandalkan System.Runtime.CompilerServices.InlineArrayAttributeyang baru ditambahkan untuk pengodean metadata.
Buffer ukuran tetap seperti pseudocode berikut:
// Not valid C#
public partial class C
{
public int buffer1[10];
public readonly int buffer2[10];
}
akan dipancarkan sebagai bidang dari jenis struct yang didekorasi secara khusus.
Kode C# yang setara adalah:
public partial class C
{
public Buffer10<int> buffer1;
public readonly Buffer10<int> buffer2;
}
[System.Runtime.CompilerServices.InlineArray(10)]
public struct Buffer10<T>
{
private T _element0;
[UnscopedRef]
public System.Span<T> AsSpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateSpan(ref _element0, 10);
}
[UnscopedRef]
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()
{
return System.Runtime.InteropServices.MemoryMarshal.CreateReadOnlySpan(
ref System.Runtime.CompilerServices.Unsafe.AsRef(in _element0), 10);
}
}
Konvensi penamaan untuk jenis dan anggotanya belum ditentukan. Kerangka kerja kemungkinan akan mencakup sekumpulan jenis "buffer" yang telah ditentukan sebelumnya yang mencakup serangkaian ukuran buffer terbatas. Ketika jenis yang telah ditentukan sebelumnya tidak ada, pengkompilasi akan mensintesisnya dalam modul yang sedang dibangun. Nama jenis yang dihasilkan akan "dapat diucapkan" untuk mendukung konsumsi dari bahasa lain.
Kode yang dihasilkan untuk akses seperti:
public partial class C
{
void M1(int val)
{
buffer1[1] = val;
}
int M2()
{
return buffer2[1];
}
}
akan setara dengan:
public partial class C
{
void M1(int val)
{
buffer.AsSpan()[1] = val;
}
int M2()
{
return buffer2.AsReadOnlySpan()[1];
}
}
Impor metadata
Saat pengkompilasi mengimpor deklarasi bidang jenis T dan kondisi berikut semuanya terpenuhi:
-
T adalah jenis struct yang dihiasi dengan atribut
InlineArray, dan - Bidang instans pertama yang dideklarasikan dalam T bertipe F, dan
- Ada
public System.Span<F> AsSpan()dalam T, dan - Ada
public readonly System.ReadOnlySpan<T> AsReadOnlySpan()ataupublic System.ReadOnlySpan<T> AsReadOnlySpan()dalam T.
bidang akan diperlakukan sebagai buffer ukuran tetap C# dengan jenis elemen F. Jika tidak, bidang akan diperlakukan sebagai bidang jenis reguler T.
Metode atau kelompok properti yang mirip dengan pendekatan dalam bahasa
Salah satu pemikiran adalah memperlakukan anggota ini lebih seperti kelompok metode, di mana mereka tidak secara otomatis menjadi nilai tersendiri, tetapi bisa dijadikan nilai jika diperlukan. Berikut cara kerjanya:
- Akses buffer ukuran tetap yang aman memiliki klasifikasinya sendiri (seperti grup metode dan lambda)
- Mereka dapat diindeks langsung sebagai operasi bahasa pemrograman (bukan melalui jenis rentang) untuk menghasilkan variabel (yang hanya-baca jika buffer berada dalam konteks hanya-baca, sama seperti bidang pada struct)
- Mereka memiliki konversi implisit dari ekspresi ke
Span<T>danReadOnlySpan<T>, tetapi penggunaan konversi pertama adalah kesalahan jika digunakan dalam konteks baca-saja. - Jenis alaminya adalah
ReadOnlySpan<T>, jadi itulah yang mereka kontribusikan jika mereka berpartisipasi dalam inferensi jenis (misalnya, var, jenis umum terbaik atau generik)
Buffer ukuran tetap C/C++
C/C++ memiliki gagasan buffer ukuran tetap yang berbeda. Misalnya, ada gagasan "buffer berukuran tetap panjang nol", yang sering digunakan sebagai cara untuk menunjukkan bahwa data adalah "panjang variabel". Bukan tujuan dari proposal ini untuk dapat berinteroperasi dengan hal itu.
Rapat LDM
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-03.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-04-10.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-01.md#fixed-size-buffers
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-03.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-05-17.md#inline-arrays
- https://github.com/dotnet/csharplang/blob/main/meetings/2024/LDM-2024-06-17.md#inline-arrays-as-record-structs
C# feature specifications