Bagikan melalui


Ungkapan koleksi

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:

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[] atau new[] sebelum nilai { ... }.
    • Rentang, yang dapat menggunakan stackalloc dan konstruksi rumit lainnya.
    • Penginisialisasi koleksi, yang memerlukan sintaks seperti new List<T> (yang mungkin kekurangan inferensi Tyang panjang) sebelum penetapan nilainya, dan yang dapat menyebabkan banyak realokasi memori karena menggunakan pemanggilan N .Add tanpa 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 (seperti ImmutableArray.CreateBuilder) sulit diatur dan masih menghasilkan sampah yang tidak dapat dihindari.
  • 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_expression akan disebut sebagai "harfiah" di bagian berikut.

  • expression_element instance biasanya akan disebut sebagai e1, e_n, dll.

  • spread_element instance biasanya akan disebut sebagai ..s1, ..s_n, dll.

  • jenis rentang berarti Span<T> atau ReadOnlySpan<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_element di dalamnya.
    • Literal tanpa spread_element di dalamnya.
    • Literal dengan urutan sembarang dari tipe elemen mana pun.
  • Jenis iterasi dari ..s_n adalah jenis variabel iterasi yang ditentukan seolah-olah s_n digunakan sebagai ekspresi yang diiterasi dalam foreach_statement.

  • Variabel yang dimulai dengan __name digunakan untuk mewakili hasil evaluasi name, disimpan di lokasi sehingga hanya dievaluasi sekali. Misalnya __e1 merupakan evaluasi dari e1.

  • List<T>, IEnumerable<T>, dll. mengacu pada jenis masing-masing di namespace System.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 ekspresi new int[] { 1, 2, 3 } yang dengan sendirinya menyematkan data mentah ke dalam bahasa rakitan, menghilangkan kebutuhan akan __index atau 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.
  • 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 stackalloc yang 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 Count pada koleksi akan menghasilkan nilai yang sama dengan jumlah elemen ketika dijumlahkan.
    • Jenis yang digunakan dalam spesifikasi ini yang ditentukan dalam namespace System.Collections.Generic dianggap 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 pada x dan menambahkan semua nilai yang disebutkan satu per satu ke dalam koleksi dengan .Add.
    • Perilaku literal koleksi dengan koleksi yang tidak berperilaku baik tidak terdefinisi.

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 elemen adalah tipe iterasiyang ditentukan dari metode instans 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 Add di 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 dari Eᵢ ke T.
  • Jika Eᵢ adalah elemen spread ..Sᵢ, terdapat konversi implisit dari tipe iterasi dari Sᵢ ke T.

Tidak ada konversi ekspresi koleksi dari ekspresi koleksi ke tipe array multi dimensi.

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 nullableT? di mana terdapat konversi ekspresi koleksi dari ekspresi koleksi ke jenis nilai T. Konversi adalah konversi ekspresi koleksi ke T diikuti oleh konversi implisit nullable dari T ke T?.

  • Ke jenis referensi T di mana ada metode pembuat yang terkait dengan T yang mengembalikan jenis U dan konversi referensi implisit dari . Konversi adalah konversi ekspresi koleksi ke diikuti oleh konversi referensi implisit dari ke .

  • Untuk jenis antarmuka di mana ada metode create terkait dengan yang mengembalikan jenis dan konversi boxing implisit dari ke . Konversi adalah konversi ekspresi koleksi ke V diikuti dengan konversi pembungkusan implisit dari V ke I.

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 yang memiliki konversi identitas dari ke jenis elemen dari jenis koleksi, itu adalah metode buat untuk jenis koleksi. Sebaliknya, jenis koleksi tidak memiliki metode buat.

Kesalahan dilaporkan jika atribut [CollectionBuilder] tidak merujuk ke metode yang dapat dipanggil dengan tanda tangan yang diharapkan.

Untuk ekspresi koleksi dengan jenis target di mana deklarasi jenis memiliki metode pembangun terkait , argumen jenis generik dari jenis target diterapkan secara berurutan — dan dari jenis terluar yang berisi ke terdalam — ke metode pembangun .

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 atau jenis kelas yang mengimplementasikan , dan jenis target tidak memiliki metode buat , konstruksi instans pengumpulan adalah 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.

  • Konstruktor yang berlaku tanpa argumen dipanggil.

  • Untuk setiap elemen secara berurutan:

    • Jika elemen adalah elemen ekspresi , instans Add atau metode ekstensi yang berlaku dipanggil dengan elemen ekspresi sebagai argumen. (Tidak seperti perilaku penginisialisasi koleksi klasik , evaluasi elemen dan panggilan Add tidak harus terjalin.)
    • Jika elemen adalah elemen spread maka salah satu hal berikut ini digunakan:
      • Instans atau metode ekstensi GetEnumerator yang berlaku dipanggil pada ekspresi elemen spread , dan untuk setiap item dari enumerator, metode atau instans ekstensi Add yang berlaku dipanggil pada instans koleksi dengan item sebagai argumen. Jika enumerator menerapkan IDisposable, maka Dispose akan dipanggil setelah enumerasi, terlepas dari pengecualian.
      • Instans AddRange atau metode ekstensi yang berlaku dipanggil pada instans pengumpulan dengan elemen spread ekspresi sebagai argumen.
      • Instans CopyTo atau metode ekstensi yang berlaku dipanggil pada ekspresi elemen spread dengan instans koleksi dan indeks int sebagai argumen.
  • Selama langkah-langkah konstruksi di atas, instans EnsureCapacity atau metode ekstensi yang berlaku dapat dipanggil satu atau beberapa kali pada instans pengumpulan dengan argumen kapasitas int.


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 GetEnumerator atau 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 menerapkan IDisposable, maka Dispose akan dipanggil setelah enumerasi, terlepas dari pengecualian.
      • Instans CopyTo atau metode ekstensi yang berlaku dipanggil pada ekspresi elemen spread dengan instans inisialisasi dan indeks int sebagai 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 b dan cdapat dihitung, pengkompilasi dapat menunda penambahan item dari a dan b hingga setelah c dievaluasi, untuk memungkinkan alokasi array yang dihasilkan pada panjang yang diharapkan. Setelah itu, pengkompilasi dapat dengan bersemangat menambahkan item dari c, sebelum mengevaluasi d.

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 = []; // illegal
    
  • Penyebaran literal kosong boleh dihilangkan. Misalnya:

    bool b = ...
    List<int> l = [x, y, .. b ? [1, 2, 3] : []];
    

    Di sini, jika b salah, 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>, dan T adalah salah satu jenis primitif bool, sbyte, byte, short, ushort, char, int, uint, long, ulong, float, atau double, 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> atau System.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 diperbarui sebagai berikut.

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 tipe T 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 input dibuat darike.
    • Jika Eᵢ adalah elemen spread dengan jenis iterasi Sᵢ, maka inferensi terikat bawah dibuat dariSᵢkeTₑ.
  • [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 darike.
    • Jika Eᵢ adalah elemen spread , tidak ada inferensi yang dibuat dari Eᵢ.
  • [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>

Mengingat C₁ konversi implisit yang mengonversi dari ekspresi E ke tipe T₁, dan C₂ konversi implisit yang mengonversi dari ekspresi E ke tipe T₂, C₁ adalah konversi yang lebih baik daripada C₂ jika salah satu kondisi berikut:

  • E adalah ekspresi koleksi dan salah satu dari berikut ini berlaku:
    • T₁ System.ReadOnlySpan<E₁>, dan T₂System.Span<E₂>, dan konversi implisit ada dari E₁ ke E₂
    • T₁ adalah System.ReadOnlySpan<E₁> atau System.Span<E₁>, dan T₂ adalah array_or_array_interface dengan jenis elemen E₂, dan terdapat konversi implisit dari E₁ ke E₂
    • T₁ bukan span_type, dan T₂ bukan span_type, dan konversi implisit ada dari T₁ ke T₂
  • E bukan ekspresi koleksi dan salah satu dari berikut ini berlaku:
    • E sama persis dengan T₁ dan E tidak sama persis dengan T₂
    • E cocok persis dengan salah satu atau tidak satupun dari T₁ dan T₂, dan T₁ adalah target konversi yang lebih baik daripada T₂
  • E adalah 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:

  1. Gunakan jenis yang ada yang mengimplementasikan antarmuka yang diperlukan.
  2. 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].

  1. Nilai harus mengembalikan true ketika dikueri untuk ICollection<T>.IsReadOnly (jika diimplementasikan), dan IList.IsReadOnly yang tidak generik serta IList.IsFixedSize yang tidak generik. Ini memastikan konsumen dapat dengan tepat mengetahui bahwa koleksi data tidak dapat diubah, meskipun menggunakan tampilan yang bersifat dapat diubah.
  2. 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>:

  1. 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 T untuk literal tersebut:

    • Jika T adalah sejumlah T1[], 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 elements
      

      Implementasi diizinkan untuk menggunakan cara lain untuk mengisi array. Misalnya, menggunakan metode penyalinan massal yang efisien seperti .CopyTo().

    • Jika T adalah beberapa Span<T1>, maka terjemahan literalnya sama dengan di atas, kecuali bahwa inisialisasi __result diterjemahkan sebagai:

      Span<T1> __result = new T1[__len];
      
      // same assignments as the array translation
      

      Terjemahan dapat menggunakan stackalloc T1[] atau array sebaris daripada new T1[] jika keamanan rentang dipertahankan.

    • Jika T adalah suatu ReadOnlySpan<T1>, maka literal diterjemahkan sama seperti untuk kasus Span<T1>, kecuali bahwa hasil akhirnya adalah Span<T1>dikonversi secara implisit ke dalam ReadOnlySpan<T1>.

      ReadOnlySpan<T1> di mana T1 adalah 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 T adalah C<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 T mendukung penginisialisasi koleksi , maka:

        • jika jenis T berisi konstruktor yang dapat diakses dengan parameter tunggal int 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 elements
          

          Catatan: 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 elements
          

          Ini 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 T mendukung 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 elements
      

      Ini memungkinkan penyebaran jenis yang dapat diulang, meskipun dengan jumlah pengoptimalan sesedikit mungkin.

    • Jika T adalah suatu T1[], 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 CreateArray digunakan untuk memberikan indikasi ukuran awal untuk mencegah pengubahan ukuran yang tidak perlu.

    • Jika T adalah suatu jenis rentang tipe, implementasi dapat mengikuti strategi T[] 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 dari Children = [w1, w2, w3]. Yang pertama memanggil .Add berulang kali pada .Children sementara yang kedua akan menetapkan koleksi baru pada .Children. Kita dapat mempertimbangkan untuk memungkinkan formulir terakhir untuk menambah ke koleksi yang sudah ada jika .Children tidak 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_element memiliki ambiguitas dengan range_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 mulai 0..e jika mereka menginginkan rentang.
      • Pilih sintaks yang berbeda (seperti ...) untuk penyebaran. Sayangnya, ini akan mengakibatkan kurangnya konsistensi dengan pola irisan.
  • 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_expression dan attributes pada 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_expression di conditional_expression dan null_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_operation kecuali 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 stackalloc untuk alokasi tumpukan saat array sebaris tidak tersedia dan jenis iterasi adalah jenis primitif?

    Resolusi: Tidak. Mengelola buffer stackalloc memerlukan 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_expression disesuaikan tipenya menjadi IEnumerable<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 oleh List<T>. Misalnya, IEnumerable<long>. Ini sama dengan pengetikan target ke List<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 dengan params 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, menargetkan T[], IEnumerable<T>, IReadOnlyCollection<T> atau IReadOnlyList<T>. Ini tidak boleh menggunakan Array.Empty<T> ketika target dapat diubah (ICollection<T> atau IList<T>).

  • Haruskah kita memperluas inisialisasi koleksi untuk mencari metode AddRange yang 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 diperlukan untuk "dapat diakses di mana ekspresi koleksi digunakan". Ini adalah aspek ketergantungan konteks lain yang didasarkan pada aksesibilitas. Tujuan metode ini sangat mirip dengan tujuan metode konversi yang ditentukan pengguna, dan bahwa metode tersebut harus bersifat publik. Oleh karena itu, kita harus mempertimbangkan untuk mengharuskan metode create bersifat publik juga.

Kesimpulan

Disetujui dengan modifikasi LDM-2024-01-08

Gagasan jenis iterasi tidak diterapkan secara konsisten di seluruh konversi

  • Ke struct atau tipe kelas yang mengimplementasikan di mana:
    • Untuk setiap elemen Ei, terdapat konversi implisit ke T.

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.IEnumerable dan 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 atau jenis kelas yang mengimplementasikan atau dalam hal jenis iterasi dan memerlukan konversi implisit untuk setiap elemen ke jenis iterasi .

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 T disebut sebagai applicable_interpolated_string_handler_type jika diberi atribut dengan System.Runtime.CompilerServices.InterpolatedStringHandlerAttribute. Ada interpolated_string_handler_conversion implisit ke T dari 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 atau jenis kelas yang mengimplementasikan dan tidak memiliki metode "create" pada bagian konversi, harus diperlukan adanya setidaknya API berikut:

  • Konstruktor yang dapat diakses yang berlaku tanpa argumen.
  • Instans Add yang dapat diakses atau metode ekstensi yang dapat dipanggil dengan nilai jenis iterasi sebagai argumen.

Untuk tujuan fitur Params Collectons, jenis tersebut adalah jenis yang valid ketika API ini dinyatakan publik dan merupakan metode instans (vs. ekstensi).

Kesimpulan

Disetujui dengan modifikasi LDM-2024-01-10