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 tertangkap dalam catatan terkait rapat desain bahasa (LDM) .
Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .
Isu utama: https://github.com/dotnet/csharplang/issues/8652
Ringkasan
Ekspresi koleksi memperkenalkan sintaks baru yang ringkas, [e1, e2, e3, etc], untuk membuat nilai-nilai koleksi yang umum. Menginlin koleksi lain ke dalam nilai-nilai ini dimungkinkan menggunakan elemen spread ..e seperti itu: [e1, ..c2, e2, ..c2].
Beberapa tipe yang mirip dengan koleksi dapat dibuat tanpa memerlukan dukungan BCL eksternal. Jenis-jenis ini adalah:
-
Jenis array, seperti
int[]. -
Span<T>danReadOnlySpan<T>. - Jenis yang mendukung penginisialisasi koleksi, seperti
List<T>.
Dukungan lebih lanjut tersedia untuk jenis-jenis yang mirip dengan koleksi yang tidak tercakup dalam kategori di atas melalui atribut baru dan pola API yang dapat diterapkan langsung pada jenis itu sendiri.
Motivasi
Nilai seperti koleksi sangat ada dalam pemrograman, algoritma, dan terutama dalam ekosistem C#/.NET. Hampir semua program akan menggunakan nilai-nilai ini untuk menyimpan data dan mengirim atau menerima data dari komponen lain. Saat ini, hampir semua program C# harus menggunakan berbagai pendekatan berbeda yang sayangnya verbos untuk menciptakan instance dari nilai-nilai tersebut. Beberapa pendekatan juga memiliki kelemahan performa. Berikut adalah beberapa contoh umum:
- Array, yang memerlukan
new Type[]ataunew[]sebelum nilai{ ... }. - Rentang, yang dapat menggunakan
stackallocdan konstruksi rumit lainnya. - Penginisialisasi koleksi, yang memerlukan sintaks seperti
new List<T>(yang mungkin kekurangan inferensiTyang panjang) sebelum penetapan nilainya, dan yang dapat menyebabkan banyak realokasi memori karena menggunakan pemanggilan N.Addtanpa menyediakan kapasitas awal. - Koleksi yang tidak dapat diubah, yang memerlukan sintaks seperti
ImmutableArray.Create(...)untuk menginisialisasi nilai, dan yang dapat menyebabkan alokasi perantara dan penyalinan data. Bentuk konstruksi yang lebih efisien (sepertiImmutableArray.CreateBuilder) sulit diatur dan masih menghasilkan sampah yang tidak dapat dihindari.
- Array, yang memerlukan
Melihat ekosistem di sekitarnya, kami juga menemukan contoh di mana-mana pembuatan daftar menjadi lebih nyaman dan menyenangkan untuk digunakan. TypeScript, Dart, Swift, Elm, Python, dan banyak lagi memilih sintaksis yang ringkas untuk tujuan ini, dengan penggunaan yang luas, dan untuk efek yang besar. Penyelidikan singkat telah mengungkapkan tidak ada masalah substantif yang muncul dalam ekosistem tersebut dengan elemen bawaan ini.
C# juga telah menambahkan pola daftar di C# 11. Pola ini memungkinkan pencocokan dan dekonstruksi nilai seperti daftar menggunakan sintaks yang bersih dan intuitif. Namun, tidak seperti hampir semua konstruksi pola lainnya, sintaks pencocokan/dekonstruksi ini tidak memiliki sintaks konstruksi yang sesuai.
Mendapatkan performa terbaik untuk membangun setiap jenis koleksi bisa sulit. Solusi sederhana sering membuang CPU dan memori. Memiliki bentuk literal memungkinkan fleksibilitas maksimum pada implementasi kompilator untuk mengoptimalkan literal tersebut. Ini menghasilkan setidaknya hasil yang sebaik yang bisa diberikan pengguna, tetapi dengan kode yang lebih sederhana. Sangat sering kompilator akan dapat melakukan yang lebih baik, dan spesifikasi bertujuan untuk memungkinkan implementasi sejumlah besar kelonggaran dalam hal strategi implementasi untuk memastikan ini.
Solusi inklusif diperlukan untuk C#. Ini harus memenuhi sebagian besar casse untuk pelanggan dalam hal jenis dan nilai seperti koleksi yang sudah mereka miliki. Ini juga harus terasa alami dalam bahasa dan mencerminkan pekerjaan yang dilakukan dalam pencocokan pola.
Ini mengarah pada kesimpulan alami bahwa sintaks harus seperti [e1, e2, e3, e-etc] atau [e1, ..c2, e2], yang sesuai dengan pola yang setara dengan [p1, p2, p3, p-etc] dan [p1, ..p2, p3].
Desain terperinci
Berikut ini produksi tata bahasa ditambahkan:
primary_no_array_creation_expression
...
+ | collection_expression
;
+ collection_expression
: '[' ']'
| '[' collection_element ( ',' collection_element )* ']'
;
+ collection_element
: expression_element
| spread_element
;
+ expression_element
: expression
;
+ spread_element
: '..' expression
;
Literal koleksi bertipe target .
Klarifikasi spesifikasi
Untuk keperluan singkat,
collection_expressionakan disebut sebagai "harfiah" di bagian berikut.expression_elementinstance biasanya akan disebut sebagaie1,e_n, dll.spread_elementinstance biasanya akan disebut sebagai..s1,..s_n, dll.jenis rentang berarti
Span<T>atauReadOnlySpan<T>.Literal biasanya akan ditampilkan sebagai
[e1, ..s1, e2, ..s2, etc]untuk menyampaikan sejumlah elemen dalam urutan apa pun. Yang penting, formulir ini akan digunakan untuk mewakili semua kasus seperti:- Literal kosong
[] - Literal tanpa
expression_elementdi dalamnya. - Literal tanpa
spread_elementdi dalamnya. - Literal dengan urutan sembarang dari tipe elemen mana pun.
- Literal kosong
Jenis iterasi dari
..s_nadalah jenis variabel iterasi yang ditentukan seolah-olahs_ndigunakan sebagai ekspresi yang diiterasi dalamforeach_statement.Variabel yang dimulai dengan
__namedigunakan untuk mewakili hasil evaluasiname, disimpan di lokasi sehingga hanya dievaluasi sekali. Misalnya__e1merupakan evaluasi darie1.List<T>,IEnumerable<T>, dll. mengacu pada jenis masing-masing di namespaceSystem.Collections.Generic.Spesifikasi mendefinisikan terjemahan literal ke konstruksi C# yang ada. Mirip dengan terjemahan ekspresi kueri , literal itu sendiri hanya sah jika terjemahan akan menghasilkan kode yang valid. Tujuan dari aturan ini adalah untuk menghindari harus mengulangi aturan lain dari bahasa yang tersirat (misalnya, tentang konvertibilitas ekspresi saat ditetapkan ke lokasi penyimpanan).
Implementasi tidak diharuskan untuk menerjemahkan kata secara harfiah sesuai dengan yang ditentukan di bawah ini. Terjemahan apa pun legal jika hasil yang sama dihasilkan dan tidak ada perbedaan yang dapat diamati dalam produksi hasilnya.
- Misalnya, implementasi dapat menerjemahkan literal seperti
[1, 2, 3]langsung ke dalam ekspresinew int[] { 1, 2, 3 }yang dengan sendirinya menyematkan data mentah ke dalam bahasa rakitan, menghilangkan kebutuhan akan__indexatau urutan instruksi untuk menetapkan setiap nilai. Yang penting, ini berarti bahwa jika ada langkah terjemahan yang dapat menyebabkan pengecualian pada runtime, keadaan program tetap berada dalam keadaan yang ditunjukkan oleh terjemahan.
- Misalnya, implementasi dapat menerjemahkan literal seperti
Referensi ke 'alokasi tumpukan' mengacu pada strategi apa pun untuk mengalokasikan pada tumpukan dan bukan heap. Yang penting, itu tidak menyiratkan atau mengharuskan strategi itu melalui mekanisme
stackallocyang sebenarnya. Misalnya, penggunaan array sebaris juga merupakan pendekatan yang diizinkan dan diinginkan untuk mencapai alokasi tumpukan ketika memungkinkan. Perhatikan bahwa dalam C# 12, array inline tidak dapat diinisialisasi dengan ekspresi koleksi. Itu adalah usulan yang masih terbuka.Koleksi diasumsikan berperilaku baik. Misalnya:
- Diasumsikan bahwa nilai
Countpada koleksi akan menghasilkan nilai yang sama dengan jumlah elemen ketika dijumlahkan. - Jenis yang digunakan dalam spesifikasi ini yang ditentukan dalam namespace
System.Collections.Genericdianggap bebas efek samping. Dengan demikian, pengkompilasi dapat mengoptimalkan skenario di mana jenis tersebut dapat digunakan sebagai nilai perantara, tetapi sebaliknya tidak diekspos. - Diasumsikan bahwa memanggil anggota
.AddRange(x)tertentu yang berlaku pada koleksi akan menghasilkan nilai akhir yang sama seperti melakukan iterasi padaxdan menambahkan semua nilai yang disebutkan satu per satu ke dalam koleksi dengan.Add. - Perilaku literal koleksi dengan koleksi yang tidak berperilaku baik tidak terdefinisi.
- Diasumsikan bahwa nilai
Konversi
Konversi ekspresi koleksi memungkinkan ekspresi koleksi dikonversi menjadi tipe.
Konversi ekspresi koleksi implisit ada dari ekspresi koleksi ke jenis-jenis berikut:
- Jenis array dimensi tunggal
T[], di mana jenis elemen adalahT - Jenis rentang tipe:
System.Span<T>System.ReadOnlySpan<T>
Jenis elemen , dalam hal ini, adalahT
- Jenis
dengan metode pembuatan yang sesuai , dalam hal ini, jenis elemenadalah tipe iterasi dari metode instansyang ditentukan atau antarmuka yang dapat dihitung, bukan dari metode ekstensi. -
struct atau tipe kelas
Jenis type memiliki konstruktor yang berlaku yang dapat dipanggil tanpa argumen, dan konstruktor tersebut dapat diakses di lokasi dari ekspresi koleksi.
Jika ekspresi koleksi memiliki elemen apa pun, jenis memiliki instans atau metode ekstensi
Adddi mana:- Metode dapat dipanggil dengan argumen nilai tunggal.
- Jika metode tersebut generik, argumen tipe dapat disimpulkan dari koleksi dan argumen.
- Metode ini dapat diakses di lokasi ekspresi koleksi.
Dalam hal ini jenis elemen adalah jenis iterasi dari jenis .
- Jenis antarmuka :
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
Jenis elemen , dalam hal ini, adalahT
Konversi implisit ada jika tipe memiliki elemen tipe T yang mana untuk setiap elemen Eᵢ dalam ekspresi kumpulan:
- Jika
Eᵢadalah elemen ekspresi , ada konversi implisit dariEᵢkeT. - Jika
Eᵢadalah elemen spread..Sᵢ, terdapat konversi implisit dari tipe iterasi dariSᵢkeT.
Tidak ada konversi ekspresi koleksi
Jenis di mana terdapat konversi ekspresi koleksi implisit dari ekspresi koleksi adalah jenis target yang valid untuk ekspresi koleksi tersebut.
Konversi implisit tambahan berikut ada dari sebuah ekspresi koleksi:
Ke jenis nilai nullable
T?di mana terdapat konversi ekspresi koleksi dari ekspresi koleksi ke jenis nilaiT. Konversi adalah konversi ekspresi koleksi keTdiikuti oleh konversi implisit nullable dariTkeT?.Ke jenis referensi
Tdi mana ada metode pembuat yang terkait denganTyang mengembalikan jenisUdan konversi referensi implisit dari . Konversi adalah konversi ekspresi koleksike diikuti oleh konversi referensi implisit dari ke . Untuk jenis antarmuka
di mana ada metode terkait dengancreate yang mengembalikan jenis dan konversi boxing implisit dari ke . Konversi adalah konversi ekspresi koleksi ke Vdiikuti dengan konversi pembungkusan implisit dariVkeI.
Membuat metode
Metode pembuatan ditandai dengan atribut [CollectionBuilder(...)] pada jenis koleksi .
Atribut menentukan tipe penyusun dan nama metode dari suatu metode yang akan dipanggil untuk membuat sebuah instans dari tipe koleksi.
namespace System.Runtime.CompilerServices
{
[AttributeUsage(
AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface,
Inherited = false,
AllowMultiple = false)]
public sealed class CollectionBuilderAttribute : System.Attribute
{
public CollectionBuilderAttribute(Type builderType, string methodName);
public Type BuilderType { get; }
public string MethodName { get; }
}
}
Atribut dapat diterapkan ke class, struct, ref struct, atau interface.
Atribut tidak diwariskan walaupun atribut tersebut dapat diterapkan pada dasar class atau abstract class.
Jenis pembangun harus berupa class yang non-generik atau struct.
Pertama, kumpulan metode pembuatan yang berlakuCM ditentukan.
Ini terdiri dari metode yang memenuhi persyaratan berikut:
- Metode harus memiliki nama yang ditentukan dalam atribut
[CollectionBuilder(...)]. - Metode harus didefinisikan secara langsung pada tipe penyusun .
- Metode tersebut harus
static. - Metode harus dapat diakses di tempat di mana ekspresi koleksi digunakan.
- Aritas dari metode harus sesuai dengan aritas dari tipe koleksi.
- Metode harus memiliki parameter tunggal jenis
System.ReadOnlySpan<E>, dikirim sebagai nilai. - Ada konversi identitas, konversi referensi implisit, atau konversi pembungkusan dari jenis pengembalian metode ke jenis koleksi .
Metode yang dideklarasikan pada jenis dasar atau antarmuka diabaikan dan bukan bagian dari set CM.
Jika set CM kosong, maka jenis koleksi tidak memiliki jenis elemen dan tidak memiliki metode buat . Tidak ada langkah-langkah berikut yang berlaku.
Jika hanya satu metode di antara mereka dalam set
Kesalahan dilaporkan jika atribut [CollectionBuilder] tidak merujuk ke metode yang dapat dipanggil dengan tanda tangan yang diharapkan.
Untuk ekspresi koleksi
Parameter rentang untuk metode buat dapat ditandai secara eksplisit scoped atau [UnscopedRef]. Jika parameter secara implisit atau eksplisit scoped, kompiler dapat mengalokasikan penyimpanan untuk rentang pada stack daripada heap.
Misalnya, kemungkinan membuat metode untuk ImmutableArray<T>:
[CollectionBuilder(typeof(ImmutableArray), "Create")]
public struct ImmutableArray<T> { ... }
public static class ImmutableArray
{
public static ImmutableArray<T> Create<T>(ReadOnlySpan<T> items) { ... }
}
Dengan metode buat di atas, ImmutableArray<int> ia = [1, 2, 3]; dapat dipancarkan sebagai:
[InlineArray(3)] struct __InlineArray3<T> { private T _element0; }
Span<int> __tmp = new __InlineArray3<int>();
__tmp[0] = 1;
__tmp[1] = 2;
__tmp[2] = 3;
ImmutableArray<int> ia =
ImmutableArray.Create((ReadOnlySpan<int>)__tmp);
Pembangunan
Elemen ekspresi koleksi dievaluasi secara berurutan, kiri ke kanan. Setiap elemen dievaluasi tepat sekali, dan referensi lebih lanjut ke elemen mengacu pada hasil evaluasi awal ini.
Elemen yang disebarkan mungkin diiterasikan sebelum atau sesudah elemen berikutnya dalam ekspresi koleksi dievaluasi.
Pengecualian yang tidak tertangani yang dilemparkan dari salah satu metode yang digunakan selama konstruksi akan tidak tertangkap dan akan mencegah langkah-langkah lebih lanjut dalam konstruksi.
Length, Count, dan GetEnumerator diasumsikan tidak memiliki efek samping.
Jika jenis target adalah struct
Elemen dievaluasi secara berurutan. Beberapa atau semua elemen dapat dievaluasi selama langkah-langkah di bawah ini daripada sebelumnya.
Kompilator dapat menentukan panjang diketahui ekspresi koleksi dengan memanggil properti yang dapat dihitung — atau properti yang setara dari antarmuka atau tipe terkenal — pada setiap ekspresi elemen sebaran.
Konstruktor yang berlaku tanpa argumen dipanggil.
Untuk setiap elemen secara berurutan:
- Jika elemen adalah elemen ekspresi , instans
Addatau metode ekstensi yang berlaku dipanggil dengan elemen ekspresi sebagai argumen. (Tidak seperti perilaku penginisialisasi koleksi klasik , evaluasi elemen dan panggilanAddtidak harus terjalin.) - Jika elemen adalah elemen spread maka salah satu hal berikut ini digunakan:
- Instans atau metode ekstensi
GetEnumeratoryang berlaku dipanggil pada ekspresi elemen spread , dan untuk setiap item dari enumerator, metode atau instans ekstensiAddyang berlaku dipanggil pada instans koleksi dengan item sebagai argumen. Jika enumerator menerapkanIDisposable, makaDisposeakan dipanggil setelah enumerasi, terlepas dari pengecualian. - Instans
AddRangeatau metode ekstensi yang berlaku dipanggil pada instans pengumpulan dengan elemen spread ekspresi sebagai argumen. - Instans
CopyToatau metode ekstensi yang berlaku dipanggil pada ekspresi elemen spread dengan instans koleksi dan indeksintsebagai argumen.
- Instans atau metode ekstensi
- Jika elemen adalah elemen ekspresi , instans
Selama langkah-langkah konstruksi di atas, instans
EnsureCapacityatau metode ekstensi yang berlaku dapat dipanggil satu atau beberapa kali pada instans pengumpulan dengan argumen kapasitasint.
Jika tipe target adalah larik , rentang , tipe dengan metode buat , atau antarmuka , koleksi tersebut dibangun sebagai berikut:
Elemen dievaluasi secara berurutan. Beberapa atau semua elemen dapat dievaluasi selama langkah-langkah di bawah ini daripada sebelumnya.
Kompilator dapat menentukan panjang diketahui ekspresi koleksi dengan memanggil properti yang dapat dihitung — atau properti yang setara dari antarmuka atau tipe terkenal — pada setiap ekspresi elemen sebaran.
Instans inisialisasi dibuat sebagai berikut:
- Jika jenis target adalah array dan ekspresi koleksi memiliki panjang yang diketahui, sebuah array dialokasikan dengan panjang yang diharapkan.
- Jika tipe target adalah tipe rentang atau tipe dengan metode pembuatan , dan koleksi memiliki panjang yang diketahui , rentang dengan panjang yang diharapkan dibuat dengan mengacu pada memori bersebelahan.
- Jika tidak, penyimpanan sementara dialokasikan.
Untuk setiap elemen secara berurutan:
- Jika elemen adalah elemen ekspresi , instans inisialisasi pengindeks dipanggil untuk menambahkan ekspresi yang dievaluasi pada indeks saat ini.
- Jika elemen adalah elemen spread maka salah satu hal berikut ini digunakan:
- Anggota antarmuka atau tipe yang dikenal dipanggil untuk mengkopi item dari ekspresi elemen spread ke instans inisialisasi.
- Instans
GetEnumeratoratau metode ekstensi yang berlaku dipanggil pada ekspresi elemen menyebarkan dan untuk setiap item dari enumerator, instans inisialisasi pengindeks dipanggil untuk menambahkan item pada indeks saat ini. Jika enumerator menerapkanIDisposable, makaDisposeakan dipanggil setelah enumerasi, terlepas dari pengecualian. - Instans
CopyToatau metode ekstensi yang berlaku dipanggil pada ekspresi elemen spread dengan instans inisialisasi dan indeksintsebagai argumen.
Jika ada alokasi penyimpanan sementara untuk koleksi, sebuah instans koleksi dialokasikan dengan panjang koleksi aktual dan nilai dari instans inisialisasi disalin ke instans koleksi, atau jika rentang dibutuhkan, kompilator dapat menggunakan rentang sepanjang panjang koleksi aktual dari penyimpanan sementara. Jika tidak, instans inisialisasi adalah instans koleksi.
Jika jenis target memiliki metode pembuatan , maka metode pembuatan tersebut akan dipanggil dengan instance rentang.
Catatan: Pengkompilasi dapat menunda penambahan elemen ke koleksi — atau menunda iterasi terhadap elemen yang disebarkan — sampai setelah mengevaluasi elemen-elemen berikutnya. (Ketika elemen spread berikutnya memiliki properti yang dapat dihitung yang akan memungkinkan penghitungan panjang koleksi yang diharapkan sebelum mengalokasikan koleksi.) Sebaliknya, pengkompilasi dapat dengan bersemangat menambahkan elemen ke koleksi — dan dengan bersemangat iterasi melalui elemen spread — ketika tidak ada keuntungan untuk menunda.
Pertimbangkan ekspresi kumpulan berikut:
int[] x = [a, ..b, ..c, d];Jika elemen spread
bdancdapat dihitung, pengkompilasi dapat menunda penambahan item dariadanbhingga setelahcdievaluasi, untuk memungkinkan alokasi array yang dihasilkan pada panjang yang diharapkan. Setelah itu, pengkompilasi dapat dengan bersemangat menambahkan item daric, sebelum mengevaluasid.var __tmp1 = a; var __tmp2 = b; var __tmp3 = c; var __result = new int[2 + __tmp2.Length + __tmp3.Length]; int __index = 0; __result[__index++] = __tmp1; foreach (var __i in __tmp2) __result[__index++] = __i; foreach (var __i in __tmp3) __result[__index++] = __i; __result[__index++] = d; x = __result;
Literal koleksi kosong
Literal kosong
[]tidak memiliki tipe. Namun, mirip dengan null-literal, literal ini dapat dikonversi secara implisit ke jenis koleksi yang dapat dibangun .Misalnya, berikut ini tidak legal karena tidak ada jenis target dan tidak ada konversi lain yang terlibat:
var v = []; // illegalPenyebaran literal kosong boleh dihilangkan. Misalnya:
bool b = ... List<int> l = [x, y, .. b ? [1, 2, 3] : []];Di sini, jika
bsalah, tidak diharuskan bahwa nilai apa pun benar-benar dibangun untuk ekspresi koleksi kosong karena akan segera disebarkan ke dalam nilai nol dalam literal akhir.Ekspresi koleksi kosong diizinkan menjadi singleton jika digunakan untuk membangun nilai koleksi akhir yang diketahui tidak dapat diubah. Misalnya:
// Can be a singleton, like Array.Empty<int>() int[] x = []; // Can be a singleton. Allowed to use Array.Empty<int>(), Enumerable.Empty<int>(), // or any other implementation that can not be mutated. IEnumerable<int> y = []; // Must not be a singleton. Value must be allowed to mutate, and should not mutate // other references elsewhere. List<int> z = [];
Keamanan ref
Lihat batasan konteks aman untuk definisi nilai konteks-aman: blok deklarasi, anggota-fungsi, dan konteks pemanggil.
konteks aman ekspresi koleksi adalah:
Konteks aman dari ekspresi koleksi yang kosong
adalah konteks pemanggil . Jika jenis target adalah jenis rentang
System.ReadOnlySpan<T>, danTadalah salah satu jenis primitifbool,sbyte,byte,short,ushort,char,int,uint,long,ulong,float, ataudouble, dan ekspresi koleksi berisi hanya nilai konstanta, maka konteks aman dari ekspresi koleksi adalah konteks pemanggil .Jika jenis target adalah jenis rentang
System.Span<T>atauSystem.ReadOnlySpan<T>, ekspresi koleksi yang aman adalah blok deklarasi .Jika jenis target adalah jenis ref struct dengan metode pembuat , konteks aman dari ekspresi koleksi adalah konteks aman dari pemanggilan metode pembuat di mana ekspresi koleksi adalah argumen rentang ke metode.
Jika tidak, konteks ekspresi koleksi yang aman adalah konteks pemanggil .
Ekspresi koleksi dengan konteks aman dari blok deklarasi tidak dapat keluar dari lingkup yang melingkupinya, dan kompilator dapat menyimpan koleksi pada tumpukan daripada longgokan.
Untuk memungkinkan ekspresi koleksi tipe ref struct keluar dari blok deklarasi , mungkin perlu untuk menkonversi ekspresi ke jenis lain.
static ReadOnlySpan<int> AsSpanConstants()
{
return [1, 2, 3]; // ok: span refers to assembly data section
}
static ReadOnlySpan<T> AsSpan2<T>(T x, T y)
{
return [x, y]; // error: span may refer to stack data
}
static ReadOnlySpan<T> AsSpan3<T>(T x, T y, T z)
{
return (T[])[x, y, z]; // ok: span refers to T[] on heap
}
Inferensi jenis
var a = AsArray([1, 2, 3]); // AsArray<int>(int[])
var b = AsListOfArray([[4, 5], []]); // AsListOfArray<int>(List<int[]>)
static T[] AsArray<T>(T[] arg) => arg;
static List<T[]> AsListOfArray<T>(List<T[]> arg) => arg;
Aturan inferensi jenis
Aturan yang ada untuk fase pertama diekstrak ke bagian inferensi jenis input baru, dan aturan ditambahkan ke inferensi jenis input dan inferensi jenis output untuk ekspresi koleksi.
11.6.3.2 Fase pertama
Untuk setiap argumen metode
Eᵢ:
- Inferensi jenis input dibuat dari ke jenis parameter yang sesuai.
Inferensi tipe input dibuat dari sebuah ekspresi
Emenjadi sebuah tipeTdengan cara berikut:
- Jika
adalah ekspresi koleksi dengan elemen , dan adalah tipe dengan tipe elemen atau adalah tipe nilai nullable dan memiliki tipe elemen , maka untuk setiap : - [aturan yang ada dari fase pertama] ...
11.6.3.7 Inferensi jenis output
inferensi tipe keluaran dibuat dari sebuah ekspresi ke sebuah tipe dengan cara berikut:
- Jika
adalah ekspresi koleksi dengan elemen , dan adalah tipe dengan tipe elemen atau adalah tipe nilai nullable dan memiliki tipe elemen , maka untuk setiap :
- Jika
adalah elemen ekspresi , maka inferensi jenis output dibuat dari ke . - Jika
Eᵢadalah elemen spread , tidak ada inferensi yang dibuat dariEᵢ.- [aturan yang ada dari inferensi jenis output] ...
Metode ekstensi
Tidak ada perubahan pada aturan pemanggilan metode ekstensi .
12.8.10.3 Pemanggilan metode ekstensi
Metode ekstensi
Cᵢ.Mₑadalah memenuhi syarat jika:
- ...
- Identitas implisit, referensi, atau konversi pembungkusan ada dari expr ke tipe parameter pertama dari
Mₑ.
Ekspresi koleksi tidak memiliki tipe alami sehingga konversi yang ada dari tipe ke tidak dapat diterapkan. Akibatnya, ekspresi koleksi tidak dapat digunakan langsung sebagai parameter pertama untuk pemanggilan metode ekstensi.
static class Extensions
{
public static ImmutableArray<T> AsImmutableArray<T>(this ImmutableArray<T> arg) => arg;
}
var x = [1].AsImmutableArray(); // error: collection expression has no target type
var y = [2].AsImmutableArray<int>(); // error: ...
var z = Extensions.AsImmutableArray([3]); // ok
Resolusi kelebihan beban
Konversi yang lebih baik dari ekspresi diperbarui untuk lebih memilih jenis target tertentu dalam konversi ekspresi koleksi.
Dalam aturan yang diperbarui:
-
span_type adalah salah satu dari:
System.Span<T>-
System.ReadOnlySpan<T>.
-
array_or_array_interface adalah salah satu dari:
- jenis array
- salah satu jenis antarmuka berikut diimplementasikan oleh jenis array :
System.Collections.Generic.IEnumerable<T>System.Collections.Generic.IReadOnlyCollection<T>System.Collections.Generic.IReadOnlyList<T>System.Collections.Generic.ICollection<T>System.Collections.Generic.IList<T>
- jenis array
Mengingat
C₁konversi implisit yang mengonversi dari ekspresiEke tipeT₁, danC₂konversi implisit yang mengonversi dari ekspresiEke tipeT₂,C₁adalah konversi yang lebih baik daripadaC₂jika salah satu kondisi berikut:
Eadalah ekspresi koleksi dan salah satu dari berikut ini berlaku:
T₁System.ReadOnlySpan<E₁>, danT₂System.Span<E₂>, dan konversi implisit ada dariE₁keE₂T₁adalahSystem.ReadOnlySpan<E₁>atauSystem.Span<E₁>, danT₂adalah array_or_array_interface dengan jenis elemenE₂, dan terdapat konversi implisit dariE₁keE₂T₁bukan span_type, danT₂bukan span_type, dan konversi implisit ada dariT₁keT₂Ebukan ekspresi koleksi dan salah satu dari berikut ini berlaku:
Esama persis denganT₁danEtidak sama persis denganT₂Ecocok persis dengan salah satu atau tidak satupun dariT₁danT₂, danT₁adalah target konversi yang lebih baik daripadaT₂Eadalah grup metode, ...
Contoh perbedaan dengan resolusi kelebihan beban antara penginisialisasi array dan ekspresi koleksi:
static void Generic<T>(Span<T> value) { }
static void Generic<T>(T[] value) { }
static void SpanDerived(Span<string> value) { }
static void SpanDerived(object[] value) { }
static void ArrayDerived(Span<object> value) { }
static void ArrayDerived(string[] value) { }
// Array initializers
Generic(new[] { "" }); // string[]
SpanDerived(new[] { "" }); // ambiguous
ArrayDerived(new[] { "" }); // string[]
// Collection expressions
Generic([""]); // Span<string>
SpanDerived([""]); // Span<string>
ArrayDerived([""]); // ambiguous
Jenis rentang
Jenis rentang ReadOnlySpan<T> dan Span<T> adalah dapat dibangun jenis koleksi . Dukungan untuk mereka sesuai dengan desain params Span<T>. Secara khusus, membangun salah satu rentang tersebut akan menghasilkan array T[] yang dibuat pada tumpukan jika array param berada dalam batas (jika ada) yang ditetapkan oleh pengkompilasi. Jika tidak, array akan dialokasikan pada heap.
Jika pengkompilasi memilih untuk mengalokasikan pada tumpukan, tidak diharuskan untuk menerjemahkan literal langsung ke stackalloc pada titik tertentu. Sebagai contoh, diberikan:
foreach (var x in y)
{
Span<int> span = [a, b, c];
// do things with span
}
Pengkompilasi diizinkan untuk mengompilasikan dengan stackalloc selama artinya Span tetap sama dan keamanan rentang dipertahankan. Misalnya, ini dapat menerjemahkan hal di atas ke:
Span<int> __buffer = stackalloc int[3];
foreach (var x in y)
{
__buffer[0] = a
__buffer[1] = b
__buffer[2] = c;
Span<int> span = __buffer;
// do things with span
}
Pengkompilasi juga dapat menggunakan array sebaris , jika tersedia, saat memilih untuk mengalokasikan pada tumpukan. Perhatikan bahwa dalam C# 12, array inline tidak dapat diinisialisasi dengan ekspresi koleksi. Fitur itu adalah usulan yang masih terbuka.
Jika kompilator memutuskan untuk mengalokasikan pada heap, terjemahan untuk Span<T> hanyalah:
T[] __array = [...]; // using existing rules
Span<T> __result = __array;
Terjemahan literal dari koleksi
Ekspresi koleksi memiliki panjang diketahui jika tipe waktu kompilasi dari setiap elemen penyebaran pada ekspresi koleksi dapat dihitung.
Terjemahan antarmuka
Terjemahan antarmuka yang tidak dapat diubah
Mengingat jenis target yang tidak berisi anggota yang bermutasi, yaitu IEnumerable<T>, IReadOnlyCollection<T>, dan IReadOnlyList<T>, implementasi yang sesuai diperlukan untuk menghasilkan nilai yang mengimplementasikan antarmuka tersebut. Bila suatu jenis disintesis, disarankan agar jenis tersebut mengimplementasikan semua antarmuka ini, serta ICollection<T> dan IList<T>, terlepas dari jenis antarmuka mana yang ditargetkan. Ini memastikan kompatibilitas maksimal dengan pustaka yang ada, termasuk yang mengintrospeksi antarmuka yang diterapkan oleh nilai untuk meningkatkan pengoptimalan performa.
Selain itu, nilai harus mengimplementasikan antarmuka ICollection dan IList nongenerik. Ini memungkinkan ekspresi pengumpulan untuk mendukung introspeksi dinamis dalam skenario seperti pengikatan data.
Implementasi yang sesuai bebas untuk:
- Gunakan jenis yang ada yang mengimplementasikan antarmuka yang diperlukan.
- Sintesis tipe yang mengimplementasikan antarmuka yang dibutuhkan.
Dalam kedua kasus, jenis yang digunakan diizinkan untuk mengimplementasikan serangkaian antarmuka yang lebih besar daripada yang sangat diperlukan.
Jenis yang disintesis bebas untuk menggunakan strategi apa pun yang ingin mereka gunakan untuk menerapkan antarmuka yang diperlukan dengan tepat. Misalnya, jenis yang disintesis mungkin memasukkan elemen langsung ke dalam dirinya sendiri, menghindari kebutuhan untuk alokasi pengumpulan internal tambahan. Jenis yang disintesis juga tidak dapat menggunakan penyimpanan apa pun, memilih untuk menghitung nilai secara langsung. Misalnya, mengembalikan index + 1 untuk [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].
- Nilai harus mengembalikan
trueketika dikueri untukICollection<T>.IsReadOnly(jika diimplementasikan), danIList.IsReadOnlyyang tidak generik sertaIList.IsFixedSizeyang tidak generik. Ini memastikan konsumen dapat dengan tepat mengetahui bahwa koleksi data tidak dapat diubah, meskipun menggunakan tampilan yang bersifat dapat diubah. - Nilai harus melemparkan panggilan apa pun ke metode mutasi (seperti
IList<T>.Add). Ini memastikan keamanan, mencegah koleksi tetap dari mutasi tanpa sengaja.
Terjemahan antarmuka bersifat dapat diubah
Jenis target yang diberikan berisi anggota yang bermutasi, yaitu ICollection<T> atau IList<T>:
- Nilai harus berupa instance
List<T>.
Terjemahan panjang yang diketahui
Memiliki panjang yang diketahui memungkinkan konstruksi hasil yang efisien dengan potensi tanpa penyalinan data dan tanpa ruang kosong yang tidak perlu dalam hasilnya.
Tidak adanya panjang yang diketahui tidak menghalangi pembuatan hasil apa pun. Namun, hal ini dapat mengakibatkan biaya CPU dan memori tambahan yang menghasilkan data, lalu pindah ke tujuan akhir.
Untuk panjang yang diketahui literal
[e1, ..s1, etc], terjemahan dimulai dengan hal berikut:int __len = count_of_expression_elements + __s1.Count; ... __s_n.Count;Diberikan tipe target
Tuntuk literal tersebut:Jika
Tadalah sejumlahT1[], maka diterjemahkan secara harfiah sebagai:T1[] __result = new T1[__len]; int __index = 0; __result[__index++] = __e1; foreach (T1 __t in __s1) __result[__index++] = __t; // further assignments of the remaining elementsImplementasi diizinkan untuk menggunakan cara lain untuk mengisi array. Misalnya, menggunakan metode penyalinan massal yang efisien seperti
.CopyTo().Jika
Tadalah beberapaSpan<T1>, maka terjemahan literalnya sama dengan di atas, kecuali bahwa inisialisasi__resultditerjemahkan sebagai:Span<T1> __result = new T1[__len]; // same assignments as the array translationTerjemahan dapat menggunakan
stackalloc T1[]atau array sebaris daripadanew T1[]jika keamanan rentang dipertahankan.Jika
Tadalah suatuReadOnlySpan<T1>, maka literal diterjemahkan sama seperti untuk kasusSpan<T1>, kecuali bahwa hasil akhirnya adalahSpan<T1>dikonversi secara implisit ke dalamReadOnlySpan<T1>.ReadOnlySpan<T1>di manaT1adalah tipe primitif tertentu, dan semua elemen koleksi bersifat konstan, tidak memerlukan datanya berada di heap, atau di tumpukan. Misalnya, implementasi dapat membangun rentang ini secara langsung sebagai referensi ke bagian dari segmen data program.Formulir di atas (untuk array dan rentang) adalah representasi dasar ekspresi koleksi dan digunakan untuk aturan terjemahan berikut:
Jika
TadalahC<S0, S1, …>tertentu yang memiliki metode pembuatanB.M<U0, U1, …>()yang sesuai, maka literal diterjemahkan sebagai:// Collection literal is passed as is as the single B.M<...>(...) argument C<S0, S1, …> __result = B.M<S0, S1, …>([...])Karena metode create harus memiliki tipe argumen dari beberapa
ReadOnlySpan<T>yang telah diinstansiasi, aturan terjemahan untuk rentang berlaku saat melewatkan ekspresi koleksi ke metode create.Jika
Tmendukung penginisialisasi koleksi , maka:jika jenis
Tberisi konstruktor yang dapat diakses dengan parameter tunggalint capacity, maka harfiah diterjemahkan sebagai:T __result = new T(capacity: __len); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsCatatan: nama parameter harus
capacity.Formulir ini memungkinkan penggunaan nilai tetap untuk menginformasikan jenis baru yang dibangun berdasarkan jumlah elemen, guna pengalokasian penyimpanan internal yang lebih efektif. Ini menghindari realokasi yang boros saat elemen ditambahkan.
jika tidak, literal tersebut diterjemahkan sebagai:
T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsIni memungkinkan pembuatan jenis target, meskipun tanpa pengoptimalan kapasitas untuk mencegah realokasi penyimpanan internal.
Terjemahan panjang tidak diketahui
Diberikan jenis target
untuk literal dengan panjang tidak diketahui : Jika
Tmendukung penginisialisasi koleksi, maka literal diterjemahkan sebagai:T __result = new T(); __result.Add(__e1); foreach (var __t in __s1) __result.Add(__t); // further additions of the remaining elementsIni memungkinkan penyebaran jenis yang dapat diulang, meskipun dengan jumlah pengoptimalan sesedikit mungkin.
Jika
Tadalah suatuT1[], maka literal memiliki makna yang sama dengan:List<T1> __list = [...]; /* initialized using predefined rules */ T1[] __result = __list.ToArray();Hal di atas sebenarnya tidak efisien; itu membuat list perantara, lalu membuat salinan array akhir dari list tersebut. Implementasi bebas untuk mengoptimalkan ini, misalnya menghasilkan kode seperti:
T1[] __result = <private_details>.CreateArray<T1>( count_of_expression_elements); int __index = 0; <private_details>.Add(ref __result, __index++, __e1); foreach (var __t in __s1) <private_details>.Add(ref __result, __index++, __t); // further additions of the remaining elements <private_details>.Resize(ref __result, __index);Ini memungkinkan pemborosan dan penyalinan minimal, tanpa beban tambahan yang mungkin terjadi pada koleksi perpustakaan.
Jumlah yang diteruskan ke
CreateArraydigunakan untuk memberikan indikasi ukuran awal untuk mencegah pengubahan ukuran yang tidak perlu.Jika
Tadalah suatu jenis rentang tipe, implementasi dapat mengikuti strategiT[]yang disebutkan di atas, atau strategi lain dengan semantik yang sama, tetapi dengan performa yang lebih baik. Misalnya, alih-alih mengalokasikan array sebagai salinan elemen daftar,CollectionsMarshal.AsSpan(__list)dapat digunakan untuk mendapatkan nilai rentang secara langsung.
Skenario yang tidak didukung
Meskipun literatur koleksi dapat digunakan untuk banyak skenario, ada beberapa skenario yang tidak bisa mereka ganti. Ini termasuk:
- Array multidimensi (misalnya
new int[5, 10] { ... }). Tidak ada fasilitas untuk menyertakan dimensi, dan semua koleksi literal hanya bersifat linier atau pemetaan. - Koleksi yang meneruskan nilai khusus ke konstruktornya. Tidak ada cara untuk mengakses konstruktor yang digunakan.
- Penginisialisasi koleksi berlapis, misalnya
new Widget { Children = { w1, w2, w3 } }. Formulir ini perlu tetap ada karena memiliki semantik yang sangat berbeda dariChildren = [w1, w2, w3]. Yang pertama memanggil.Addberulang kali pada.Childrensementara yang kedua akan menetapkan koleksi baru pada.Children. Kita dapat mempertimbangkan untuk memungkinkan formulir terakhir untuk menambah ke koleksi yang sudah ada jika.Childrentidak dapat ditetapkan, tetapi tampaknya itu bisa sangat membingungkan.
Ambiguitas sintaksis
Ada dua ambiguitas sintaksis "benar" di mana ada beberapa interpretasi sintaksis yang sah dari kode yang menggunakan
collection_literal_expression.spread_elementmemiliki ambiguitas denganrange_expression. Seseorang secara teknis dapat memiliki:Range[] ranges = [range1, ..e, range2];Untuk mengatasinya, kita dapat:
- Mengharuskan pengguna untuk menambahkan tanda kurung
(..e)atau memasukkan indeks mulai0..ejika mereka menginginkan rentang. - Pilih sintaks yang berbeda (seperti
...) untuk penyebaran. Sayangnya, ini akan mengakibatkan kurangnya konsistensi dengan pola irisan.
- Mengharuskan pengguna untuk menambahkan tanda kurung
Ada dua kasus di mana tidak ada ambiguitas sejati tetapi di mana sintaksnya sangat meningkatkan kompleksitas penguraian. Meskipun tidak menjadi masalah jika waktu rekayasa tersedia, ini tetap meningkatkan beban kognitif bagi pengguna saat melihat kode.
Ambiguitas antara
collection_literal_expressiondanattributespada pernyataan atau fungsi lokal. Anggap:[X(), Y, Z()]Ini bisa menjadi salah satu dari:
// A list literal inside some expression statement [X(), Y, Z()].ForEach(() => ...); // The attributes for a statement or local function [X(), Y, Z()] void LocalFunc() { }Tanpa pencarian ke depan yang kompleks, tidak mungkin mengetahui tanpa memproses keseluruhan literal.
Opsi untuk mengatasi hal ini meliputi:
- Izinkan hal ini, lakukan pekerjaan penguraian untuk menentukan kasus mana dari beberapa kasus ini.
- Larang ini, dan wajibkan pengguna membungkus harfiah dalam tanda kurung seperti
([X(), Y, Z()]).ForEach(...). - Ambiguitas antara
collection_literal_expressiondiconditional_expressiondannull_conditional_operations. Anggap:
M(x ? [a, b, c]Ini bisa menjadi salah satu dari:
// A ternary conditional picking between two collections M(x ? [a, b, c] : [d, e, f]); // A null conditional safely indexing into 'x': M(x ? [a, b, c]);Tanpa pencarian ke depan yang kompleks, tidak mungkin mengetahui tanpa memproses keseluruhan literal.
Catatan: ini adalah masalah bahkan tanpa jenis alami karena pengetikan target berlaku melalui
conditional_expressions.Seperti halnya yang lain, kita bisa mewajibkan tanda kurung untuk membedakan. Dengan kata lain, anggaplah interpretasi
null_conditional_operationkecuali ditulis seperti itu:x ? ([1, 2, 3]) :. Namun, itu tampaknya agak disayangkan. Kode semacam ini tampaknya tidak wajar untuk ditulis dan kemungkinan akan membingungkan orang.
Kekurangan
- Ini memperkenalkan satu bentuk lagi untuk ekspresi koleksi, menambah berbagai cara yang sudah kita miliki. Ini adalah kompleksitas tambahan bagi bahasa. Meskipun demikian, ini juga memungkinkan untuk menyatukan sintaks cincin
menjadi satu untuk mengendalikan semuanya, yang berarti basis kode yang ada dapat disederhanakan dan dipindahkan ke tampilan seragam di mana-mana. - Menggunakan
[...]alih-alih{...}menjauh dari sintaks yang sudah umumnya kita gunakan untuk array dan penginisialisasi koleksi. Khususnya, bahwa ia menggunakan[...]alih-alih{...}. Namun, ini sudah diputuskan oleh tim bahasa ketika kita mencantumkan pola. Kami mencoba membuat{...}berfungsi dengan pola daftar dan mengalami masalah yang tidak terpecahkan. Karena itu, kami pindah ke[...]yang, sementara baru untuk C#, terasa alami dalam banyak bahasa pemrograman dan memungkinkan kami untuk mulai segar tanpa ambiguitas. Menggunakan[...]sebagai bentuk literal yang sesuai adalah komplementer dengan keputusan terbaru kami, dan memberi kita lingkungan kerja yang bersih tanpa hambatan.
Ini memang memperkenalkan masalah ke dalam bahasa. Misalnya, berikut ini adalah hukum dan (untungnya) berarti hal yang sama persis:
int[] x = { 1, 2, 3 };
int[] x = [ 1, 2, 3 ];
Namun, mengingat luasnya dan konsistensi yang dibawa oleh sintaksis harfiah baru, kita harus mempertimbangkan untuk merekomendasikan agar orang pindah ke bentuk baru. Saran dan perbaikan IDE dapat membantu dalam hal itu.
Alternatif
- Desain lain apa yang telah dipertimbangkan? Apa dampak dari tidak melakukan ini?
Pertanyaan yang diatasi
Haruskah pengompilasi menggunakan
stackallocuntuk alokasi tumpukan saat array sebaris tidak tersedia dan jenis iterasi adalah jenis primitif?Resolusi: Tidak. Mengelola buffer
stackallocmemerlukan upaya tambahan dibandingkan dengan array sebaris untuk memastikan buffer tidak dialokasikan berulang kali ketika ekspresi koleksi berada dalam perulangan. Kompleksitas tambahan pada kompilator dan dalam kode yang dihasilkan melebihi manfaat dari alokasi tumpukan pada platform yang lebih lama.Dalam urutan manakah kita harus mengevaluasi elemen harfiah dibandingkan dengan evaluasi properti Panjang/Hitung? Haruskah kita mengevaluasi semua elemen terlebih dahulu, kemudian semua ukuran? Atau haruskah kita mengevaluasi elemen, kemudian panjangnya, lalu elemen berikutnya, dan sebagainya?
Resolusi: Kami mengevaluasi semua elemen terlebih dahulu, lalu yang lainnya mengikutinya.
Dapatkah literal dengan panjang yang tidak diketahui membuat jenis koleksi yang membutuhkan panjang diketahui, seperti array, span, atau koleksi Construct(array/span)? Ini akan lebih sulit untuk dilakukan secara efisien, tetapi mungkin melalui penggunaan array yang digabungkan dan/atau pembangun yang cerdas.
Resolusi: Ya, kami mengizinkan pembuatan koleksi dengan panjang tetap dari literal panjang tidak diketahui. Pengkompilasi diizinkan untuk mengimplementasikan ini seefisien mungkin.
Teks berikut ada untuk merekam diskusi asli topik ini.
Pengguna selalu dapat membuat panjang tidak diketahui harfiah menjadi panjang diketahui satu dengan kode seperti ini:
ImmutableArray<int> x = [a, ..unknownLength.ToArray(), b];Namun, ini disayangkan karena kebutuhan untuk memaksa alokasi penyimpanan sementara. Kita berpotensi lebih efisien jika kita mengontrol bagaimana ini dipancarkan.
Dapatkah sebuah
collection_expressiondisesuaikan tipenya menjadiIEnumerable<T>atau antarmuka koleksi lainnya?Misalnya:
void DoWork(IEnumerable<long> values) { ... } // Needs to produce `longs` not `ints` for this to work. DoWork([1, 2, 3]);Resolusi: Ya, literal dapat ditargetkan ke jenis antarmuka apa pun
I<T>yang diimplementasikan olehList<T>. Misalnya,IEnumerable<long>. Ini sama dengan pengetikan target keList<long>lalu menetapkan hasil tersebut ke jenis antarmuka yang ditentukan. Teks berikut ada untuk merekam diskusi asli topik ini.Pertanyaan terbuka di sini adalah menentukan tipe dasar yang sebenarnya harus dibuat. Salah satu opsinya adalah melihat proposal untuk
params IEnumerable<T>. Di sana, kita akan menghasilkan array untuk meneruskan nilai, mirip dengan apa yang terjadi denganparams T[].Dapat/haruskah pengkompilasi memancarkan
Array.Empty<T>()untuk[]? Haruskah kita mengamanatkan bahwa hal ini dilakukan, untuk menghindari alokasi jika memungkinkan?Ya. Pengkompilasi harus memancarkan
Array.Empty<T>()untuk setiap kasus di mana ini legal dan hasil akhir tidak dapat diubah. Misalnya, menargetkanT[],IEnumerable<T>,IReadOnlyCollection<T>atauIReadOnlyList<T>. Ini tidak boleh menggunakanArray.Empty<T>ketika target dapat diubah (ICollection<T>atauIList<T>).Haruskah kita memperluas inisialisasi koleksi untuk mencari metode
AddRangeyang sering digunakan? Ini dapat digunakan oleh tipe yang dibangun secara mendasar untuk melakukan penambahan elemen yang tersebar dengan potensi lebih efisien. Kita mungkin juga ingin mencari hal-hal seperti.CopyTo. Mungkin ada kelemahan di sini karena metode tersebut mungkin akhirnya menyebabkan kelebihan alokasi/pembagian tugas dibandingkan dengan secara langsung menyusun daftar dalam kode yang diterjemahkan.Ya. Implementasi diizinkan untuk menggunakan metode lain untuk menginisialisasi nilai koleksi, dengan anggapan bahwa metode ini memiliki semantik yang terdefinisi dengan baik, dan jenis koleksi tersebut harus "berperilaku baik". Namun, dalam praktiknya, implementasi harus berhati-hati karena manfaat dengan satu cara (penyalinan massal) juga dapat datang dengan konsekuensi negatif (misalnya, mengonversi koleksi struct menjadi objek).
Implementasi harus memanfaatkan kesempatan dalam situasi di mana tidak ada kelemahan. Misalnya, dengan metode
.AddRange(ReadOnlySpan<T>).
Pertanyaan yang belum terselesaikan
- Haruskah kita mengizinkan menyimpulkan jenis elemen ketika jenis iterasi "ambigu" (menurut beberapa definisi)? Misalnya:
Collection x = [1L, 2L];
// error CS1640: foreach statement cannot operate on variables of type 'Collection' because it implements multiple instantiations of 'IEnumerable<T>'; try casting to a specific interface instantiation
foreach (var x in new Collection) { }
static class Builder
{
public Collection Create(ReadOnlySpan<long> items) => throw null;
}
[CollectionBuilder(...)]
class Collection : IEnumerable<int>, IEnumerable<string>
{
IEnumerator<int> IEnumerable<int>.GetEnumerator() => throw null;
IEnumerator<string> IEnumerable<string>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Haruskah legal untuk membuat dan segera mengindeks ke dalam koleksi harfiah? Catatan: Ini memerlukan jawaban atas pertanyaan yang belum terselesaikan di bawah ini mengenai apakah literal koleksi memiliki jenis alami .
Alokasi stack untuk koleksi besar mungkin menyebabkan stack overflow. Haruskah kompilator memiliki heuristik untuk menempatkan data ini pada tumpukan? Haruskah bahasa tidak ditentukan untuk memungkinkan fleksibilitas ini? Kita sebaiknya mengikuti spesifikasi untuk
params Span<T>.Apakah kita perlu menargetkan tipe
spread_element? Pertimbangkan, misalnya:Span<int> span = [a, ..b ? [c] : [d, e], f];Catatan: ini mungkin umumnya muncul dalam bentuk berikut untuk memungkinkan penyertaan bersyarat dari sekumpulan elemen, atau tidak sama sekali jika kondisinya salah.
Span<int> span = [a, ..b ? [c, d, e] : [], f];Untuk mengevaluasi harfiah penuh ini, kita perlu mengevaluasi ekspresi elemen di dalamnya. Itu berarti mampu mengevaluasi
b ? [c] : [d, e]. Namun, jika tidak ada jenis target untuk mengevaluasi ekspresi ini dalam konteks yang dimaksud, dan tidak ada jenis yang alami, maka kita tidak akan dapat menentukan apa yang harus dilakukan dengan[c]atau[d, e]di sini.Untuk mengatasinya, kita dapat menyatakan bahwa ketika mengevaluasi ekspresi harfiah
spread_element, ada jenis target implisit yang setara dengan jenis target literal itu sendiri. Jadi, dalam hal yang di atas, akan ditulis ulang sebagai:int __e1 = a; Span<int> __s1 = b ? [c] : [d, e]; int __e2 = f; Span<int> __result = stackalloc int[2 + __s1.Length]; int __index = 0; __result[__index++] = a; foreach (int __t in __s1) __result[index++] = __t; __result[__index++] = f; Span<int> span = __result;
Spesifikasi jenis koleksi yang dapat dibangun dengan menggunakan metode pembuatan peka terhadap konteks tempat konversi diklasifikasikan
Keberadaan konversi dalam kasus ini tergantung pada gagasan tentang iterasi jenis dari jenis koleksi . Jika ada metode pembuatan yang menerima ReadOnlySpan<T>, di mana T adalah jenis iterasi, maka konversi tersebut ada. Jika tidak, maka tidak.
Namun, jenis iterasi sensitif terhadap konteks di mana foreach dilakukan. Untuk jenis koleksi yang sama, itu dapat berbeda tergantung pada metode ekstensi yang ada dalam cakupan, dan juga dapat tidak terdefinisikan.
Itu cukup baik dalam konteks foreach ketika tipe tidak dirancang untuk menjadi foreach-able pada dirinya sendiri. Jika ya, metode ekstensi tidak dapat mengubah cara tipe diiterasi dengan foreach, apa pun konteksnya.
Namun, itu terasa agak aneh jika konversi menjadi sensitif terhadap konteks seperti itu. Pada dasarnya, konversi "tidak stabil". Jenis koleksi dirancang secara eksplisit agar dapat dibangun dan diizinkan untuk meninggalkan definisi detail yang sangat penting - jenis iterasi . Membiarkan jenis "tidak dapat dikonversi" dengan sendirinya.
Berikut adalah contohnya:
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
[CollectionBuilder(typeof(MyCollectionBuilder), nameof(MyCollectionBuilder.Create))]
class MyCollection
{
}
class MyCollectionBuilder
{
public static MyCollection Create(ReadOnlySpan<long> items) => throw null;
public static MyCollection Create(ReadOnlySpan<string> items) => throw null;
}
namespace Ns1
{
static class Ext
{
public static IEnumerator<long> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
long s = l;
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
}
}
}
namespace Ns2
{
static class Ext
{
public static IEnumerator<string> GetEnumerator(this MyCollection x) => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l;
}
MyCollection x1 = ["a",
2]; // error CS0029: Cannot implicitly convert type 'int' to 'string'
}
}
}
namespace Ns3
{
class Program
{
static void Main()
{
// error CS1579: foreach statement cannot operate on variables of type 'MyCollection' because 'MyCollection' does not contain a public instance or extension definition for 'GetEnumerator'
foreach (var l in new MyCollection())
{
}
MyCollection x1 = ["a", 2]; // error CS9188: 'MyCollection' has a CollectionBuilderAttribute but no element type.
}
}
}
Mengingat desain saat ini, jika jenis tidak menentukan jenis iterasi itu sendiri, pengkompilasi tidak dapat memvalidasi aplikasi atribut CollectionBuilder dengan andal. Jika kita tidak tahu jenis iterasi, kita tidak tahu seperti apa tanda tangan metode pembuatan seharusnya. Jika jenis iterasi berasal dari konteks, tidak ada jaminan bahwa jenis tersebut selalu akan digunakan dalam konteks yang sama.
fitur Koleksi Params juga dipengaruhi oleh ini. Rasanya aneh untuk tidak dapat memprediksi jenis elemen dengan andal dari parameter params pada titik deklarasi. Proposal saat ini juga mengharuskan untuk memastikan bahwa metode pembuatan setidaknya dapat diakses seperti jenis koleksi params. Tidak mungkin untuk melakukan pemeriksaan ini dengan cara yang dapat diandalkan, kecuali jenis koleksi mendefinisikan jenis iterasi itu sendiri.
Perhatikan bahwa kami juga memiliki https://github.com/dotnet/roslyn/issues/69676 yang dibuka untuk kompilator, yang pada dasarnya mengamati masalah yang sama, tetapi membahasnya dari sudut pandang pengoptimalan.
Usulan
Memerlukan jenis yang menggunakan atribut CollectionBuilder untuk menentukan jenis iterasi pada dirinya sendiri.
Dengan kata lain, ini berarti bahwa tipe harus mengimplementasikan IEnumarable/IEnumerable<T>, atau tipe tersebut harus memiliki metode publik GetEnumerator dengan tanda tangan yang benar (ini tidak termasuk metode ekstensi apa pun).
Selain itu, saat ini metode pembuatan
Kesimpulan
Disetujui dengan modifikasi LDM-2024-01-08
Gagasan jenis iterasi tidak diterapkan secara konsisten di seluruh konversi
- Ke
struct atau tipe kelasyang mengimplementasikan di mana:
- Untuk setiap elemen
Ei, terdapat konversi implisit keT.
Sepertinya asumsi dibuat bahwa T adalah tipe iterasi dari struktur atau tipe kelas dalam kasus ini.
Namun, asumsi itu salah. Yang dapat menyebabkan perilaku yang sangat aneh. Misalnya:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public void Add(string l) => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
MyCollection x1 = ["a", // error CS0029: Cannot implicitly convert type 'string' to 'long'
2];
MyCollection x2 = new MyCollection() { "b" };
}
}
- Untuk struct atau tipe kelas yang mengimplementasikan
System.Collections.IEnumerabledan tidak menerapkanSystem.Collections.Generic.IEnumerable<T>.
Sepertinya implementasi menganggap bahwa jenis iterasi adalah object, tetapi spesifikasinya membiarkan fakta ini tidak diperjelas, dan tidak mewajibkan setiap elemen untuk mengonversi menjadi apa pun. Namun, secara umum, jenis iterasi bukan merupakan jenis object. Yang dapat diamati dalam contoh berikut:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
public IEnumerator<string> GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
class Program
{
static void Main()
{
foreach (var l in new MyCollection())
{
string s = l; // Iteration type is string
}
}
}
Gagasan jenis iterasi sangat mendasar untuk fitur Koleksi Params . Dan masalah ini menyebabkan perbedaan aneh antara kedua fitur tersebut. Misalnya:
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable<long>
{
IEnumerator<long> IEnumerable<long>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(long l) => throw null;
public void Add(string l) => throw null;
}
class Program
{
static void Main()
{
Test("2"); // error CS0029: Cannot implicitly convert type 'string' to 'long'
Test(["2"]); // error CS1503: Argument 1: cannot convert from 'collection expressions' to 'string'
Test(3); // error CS1503: Argument 1: cannot convert from 'int' to 'string'
Test([3]); // Ok
MyCollection x1 = ["2"]; // error CS0029: Cannot implicitly convert type 'string' to 'long'
MyCollection x2 = [3];
}
static void Test(params MyCollection a)
{
}
}
using System.Collections;
using System.Collections.Generic;
class MyCollection : IEnumerable
{
IEnumerator IEnumerable.GetEnumerator() => throw null;
public IEnumerator<string> GetEnumerator() => throw null;
public void Add(object l) => throw null;
}
class Program
{
static void Main()
{
Test("2", 3); // error CS1503: Argument 2: cannot convert from 'int' to 'string'
Test(["2", 3]); // Ok
}
static void Test(params MyCollection a)
{
}
}
Mungkin akan baik untuk menyelaraskan ke satu arah atau lainnya.
Usulan
Tentukan konvertibilitas struktur
Kesimpulan
Disetujui LDM-2024-01-08
Haruskah konversi ekspresi koleksi memerlukan ketersediaan sekumpulan API minimal untuk pembangunan?
Jenis koleksi yang dapat dibangun sesuai dengan konversi mungkin tidak dapat dibangun, yang kemungkinan menyebabkan beberapa perilaku penyelesaian overloading yang tidak terduga. Misalnya:
class C1
{
public static void M1(string x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS0121: The call is ambiguous between the following methods or properties: 'C1.M1(string)' and 'C1.M1(char[])'
}
}
Namun, 'C1. M1(string)' bukan kandidat yang dapat digunakan karena:
error CS1729: 'string' does not contain a constructor that takes 0 arguments
error CS1061: 'string' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'string' could be found (are you missing a using directive or an assembly reference?)
Berikut adalah contoh lain dengan jenis yang ditentukan pengguna dan kesalahan yang lebih kuat yang bahkan tidak menyebutkan kandidat yang valid:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(C1 x)
{
}
public static void M1(char[] x)
{
}
void Test()
{
M1(['a', 'b']); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
}
public static implicit operator char[](C1 x) => throw null;
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Sepertinya situasinya sangat mirip dengan apa yang biasa kita miliki dengan grup metode untuk mendelegasikan konversi. Yaitu ada skenario di mana konversi ada, tetapi salah. Kami memutuskan untuk meningkatkannya dengan memastikan bahwa, jika konversi salah, maka itu tidak ada.
Perhatikan bahwa dengan fitur "Koleksi Parameter" kita akan menghadapi masalah serupa. Mungkin baik untuk melarang penggunaan pengubah params untuk koleksi yang tidak dapat dibangun. Namun, dalam proposal saat ini pemeriksaan itu didasarkan pada konversi bagian. Berikut adalah contohnya:
using System.Collections;
using System.Collections.Generic;
class C1 : IEnumerable<char>
{
public static void M1(params C1 x) // It is probably better to report an error about an invalid `params` modifier
{
}
public static void M1(params ushort[] x)
{
}
void Test()
{
M1('a', 'b'); // error CS1061: 'C1' does not contain a definition for 'Add' and no accessible extension method 'Add' accepting a first argument of type 'C1' could be found (are you missing a using directive or an assembly reference?)
M2('a', 'b'); // Ok
}
public static void M2(params ushort[] x)
{
}
IEnumerator<char> IEnumerable<char>.GetEnumerator() => throw null;
IEnumerator IEnumerable.GetEnumerator() => throw null;
}
Sepertinya masalah ini agak dibahas sebelumnya, lihat https://github.com/dotnet/csharplang/blob/main/meetings/2023/LDM-2023-10-02.md#collection-expressions. Pada saat itu diajukan argumen bahwa aturan yang ditentukan saat ini konsisten dengan cara handler string terinterpolasi ditentukan. Berikut adalah kutipan:
Secara khusus, handler string terinterpolasi awalnya ditentukan dengan cara ini, tetapi kami merevisi spesifikasi setelah mempertimbangkan masalah ini.
Meskipun ada beberapa kesamaan, ada juga perbedaan penting yang perlu dipertimbangkan. Berikut adalah kutipan dari https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md#interpolated-string-handler-conversion:
Jenis
Tdisebut sebagai applicable_interpolated_string_handler_type jika diberi atribut denganSystem.Runtime.CompilerServices.InterpolatedStringHandlerAttribute. Ada interpolated_string_handler_conversion implisit keTdari interpolated_string_expression, atau additive_expression yang sepenuhnya terdiri dari interpolated_string_expression dan hanya menggunakan operator+.
Jenis target harus memiliki atribut khusus yang merupakan indikator kuat niat penulis agar jenis menjadi handler string terinterpolasi. Adil untuk mengasumsikan bahwa kehadiran atribut bukanlah kebetulan.
Sebaliknya, fakta bahwa tipe tersebut "enumerable", tidak selalu berarti bahwa ada niat penulis agar tipe tersebut dapat dikonstruksi. Kehadiran sebuah metode membuatyang ditandai dengan atribut [CollectionBuilder(...)] pada tipe koleksi , memberikan indikasi kuat bahwa penulis bermaksud agar tipe tersebut dapat dikonstruksi.
Usulan
Untuk jenis struct
- Konstruktor yang dapat diakses yang berlaku tanpa argumen.
- Instans
Addyang dapat diakses atau metode ekstensi yang dapat dipanggil dengan nilai jenis iterasi sebagai argumen.
Untuk tujuan fitur
Kesimpulan
Disetujui dengan modifikasi LDM-2024-01-10
C# feature specifications