Bagikan melalui


Argumen untuk ekspresi koleksi

Edisi unggulan: https://github.com/dotnet/csharplang/issues/8887

Motivasi

Fitur ekspresi kamus telah mengidentifikasi kebutuhan ekspresi koleksi untuk meneruskan data yang ditentukan pengguna untuk mengonfigurasi perilaku pengumpulan akhir. Secara khusus, kamus memungkinkan pengguna untuk menyesuaikan bagaimana kunci mereka dibandingkan, menggunakannya untuk menentukan kesetaraan antara kunci, dan pengurutan atau hashing (dalam kasus koleksi yang diurutkan atau di-hash masing-masing). Kebutuhan ini berlaku saat membuat jenis kamus apa pun (seperti D d = new D(...), D d = D.CreateRange(...) dan bahkan IDictionary<...> d = <synthesized dict>)

Untuk mendukung ini, elemen baru with(...arguments...) diusulkan sebagai elemen pertama dari ekspresi koleksi seperti itu:

Dictionary<string, int> nameToAge = [with(comparer), .. d1, .. d2, .. d3];
  1. Saat menerjemahkan ke new CollectionType(...) panggilan, ini ...arguments... digunakan untuk menentukan konstruktor yang sesuai dan diteruskan sesuai.
  2. Saat menerjemahkan ke CollectionFactory.Create panggilan, ini ...arguments... diteruskan sebelumnya dengan ReadOnlySpan<ElementType> argumen elemen, yang semuanya digunakan untuk menentukan kelebihan beban yang sesuai Create , dan diteruskan sesuai.
  3. Saat menerjemahkan ke antarmuka (seperti IDictionary<,>) hanya satu argumen yang diizinkan. Ini mengimplementasikan salah satu antarmuka pembanding BCL yang terkenal, dan akan digunakan untuk mengontrol kunci yang membandingkan semantik instans akhir.

Sintaks ini dipilih sebagai:

  1. Menyimpan semua informasi dalam [...] sintaks. Memastikan bahwa kode masih dengan jelas menunjukkan koleksi yang dibuat.
  2. Tidak menyiratkan memanggil new konstruktor (ketika bukan itu cara semua koleksi dibuat).
  3. Tidak menyiratkan pembuatan/penyalinan nilai koleksi beberapa kali (seperti postfix with { ... } mungkin.
  4. Tidak menyimpulkan urutan operasi, terutama dengan semantik urutan evaluasi ekspresi kiri-ke-kanan C#yang konsisten. Misalnya, ini tidak mengevaluasi argumen yang digunakan untuk membuat koleksi setelah mengevaluasi ekspresi yang digunakan untuk mengisi koleksi.
  5. Tidak memaksa pengguna untuk membaca hingga akhir ekspresi koleksi (berpotensi besar) untuk menentukan semantik perilaku inti. Misalnya, harus melihat ke akhir kamus seratus baris, hanya untuk menemukan itu, ya, itu menggunakan perbandingan kunci yang tepat.
  6. Keduanya tidak halus, sementara juga tidak terlalu verbose. Misalnya, menggunakan ; alih-alih , untuk menunjukkan argumen adalah bagian sintaks yang sangat mudah untuk dilewatkan. with() hanya menambahkan 6 karakter, dan akan dengan mudah menonjol, terutama dengan pewarnaan with sintaks kata kunci.
  7. Membaca dengan baik. "Ini adalah ekspresi koleksi 'dengan' argumen ini, yang terdiri dari elemen-elemen ini."
  8. Memecahkan kebutuhan pembanding untuk kamus dan set.
  9. Memastikan setiap kebutuhan pengguna untuk meneruskan argumen, atau kebutuhan apa pun yang kita miliki di luar pembanding di masa depan sudah ditangani.
  10. Tidak bertentangan dengan kode yang ada (menggunakan https://grep.app/ untuk mencari).

Filsafat Desain

Bagian di bawah ini mencakup diskusi filsafat desain sebelumnya. Termasuk mengapa formulir tertentu ditolak.

Ada dua arah utama yang dapat kita masuk untuk menyediakan data yang ditentukan pengguna ini. Yang pertama adalah untuk kasus khusus hanya nilai dalam ruang pembanding (yang kita tentukan sebagai jenis yang mewarisi dari BCL IComparer<T> atau IEqualityComparer<T> jenis). Yang kedua adalah menyediakan mekanisme umum untuk menyediakan argumen arbitrer ke API akhir yang dipanggil saat membuat ekspresi koleksi. Spesifikasi ekspresi kamus utama menunjukkan bagaimana kita dapat melakukan yang pertama, sementara spesifikasi ini berusaha melakukan yang terakhir.

Pemeriksaan solusi untuk hanya melewati pembanding telah mengungkapkan kelemahan dalam pendekatan mereka jika kita ingin memperluasnya ke argumen sewenang-wenang. Contohnya:

  1. Menggunakan kembali sintaks elemen , seperti yang kita lakukan dengan formulir: [StringComparer.OrdinalIgnoreCase, "mads": 21]. Ini bekerja dengan baik di ruang di mana KeyValuePair<,> dan pembanding tidak mewarisi dari jenis umum. Tetapi rusak di dunia di mana seseorang mungkin melakukan: HashSet<object> h = [StringComparer.OrdinalIgnoreCase, "v"]. Apakah ini melewati perbandingan? Atau mencoba memasukkan dua nilai objek ke dalam set?

  2. Memisahkan argumen versus elemen dengan sintaks yang halus (seperti menggunakan titik koma alih-alih koma untuk memisahkannya dalam [comparer; v1]). Ini berisiko situasi yang sangat membingungkan di mana pengguna secara tidak sengaja menulis [1; 2] (dan mendapatkan koleksi yang melewati '1' karena, katakanlah, argumen 'kapasitas' untuk List<>, dan hanya berisi nilai tunggal '2'), ketika mereka dimaksudkan [1, 2] (koleksi dengan dua elemen).

Karena itu, untuk mendukung argumen arbitrer, kami percaya sintaks yang lebih jelas diperlukan untuk lebih jelas mendemarcate nilai-nilai ini. Beberapa kekhawatiran desain lainnya juga muncul di ruang ini. Dalam urutan tertentu, ini adalah:

  1. Bahwa solusinya tidak ambigu dan menyebabkan jeda dengan kode yang kemungkinan digunakan orang dengan ekspresi koleksi saat ini. Contohnya:

    List<Widget> c = [new(...), w1, w2, w3];
    

    Ini legal saat ini, dengan new(...) ekspresi menjadi 'pembuatan objek implisit' yang membuat widget baru. Kami tidak dapat menggunakan kembali ini untuk meneruskan argumen ke List<>konstruktor karena tentu akan memutus kode yang ada.

  2. Bahwa sintaks tidak meluas ke luar [...] konstruksi. Contohnya:

    HashSet<string> s = [...] with ...;
    

    Sintaks ini dapat ditafsirkan untuk berarti bahwa koleksi dibuat terlebih dahulu, dan kemudian dibuat ulang menjadi bentuk yang berbeda, menyiratkan beberapa transformasi data, dan biaya yang berpotensi lebih tinggi yang tidak diinginkan (bahkan jika itu bukan yang dipancarkan).

  3. Bahwa new sebagai kata kunci potensial untuk digunakan sama sekali di ruang ini tidak diinginkan membingungkan. Keduanya karena [...]sudah menunjukkan bahwa objek baru dibuat, dan karena terjemahan ekspresi koleksi dapat melalui API non-konstruktor (misalnya, pola Buat metode ).

  4. Bahwa solusinya tidak terlalu verbose. Proposisi nilai inti dari ekspresi koleksi adalah brevity. Jadi jika formulir menambahkan sejumlah besar perancah sintik, itu akan terasa seperti langkah mundur, dan akan mendasar proposisi nilai menggunakan ekspresi koleksi, versus memanggil ke API yang ada untuk membuat koleksi.

Perhatikan bahwa sintaks seperti new([...], ...) menjalankan afoul dari '2' dan '3' di atas. Ini membuatnya tampak seolah-olah kita memanggil ke konstruktor (ketika kita mungkin tidak) dan menyiratkan bahwa ekspresi koleksi yang dibuat diteruskan ke konstruktor itu, yang pasti tidak.

Berdasarkan semua hal di atas, beberapa opsi kecil telah muncul yang dirasakan untuk menyelesaikan kebutuhan melewati argumen, tanpa melangkah keluar dari batas-batas tujuan ekspresi koleksi.

[with(...arguments...)] Desain

Syntax:

collection_element
   : expression_element
   | spread_element
+  | with_element
   ;

+with_element
+  : 'with' argument_list
+  ;

Ada ambiguitas sinaks yang segera diperkenalkan dengan produksi tata bahasa ini. Mirip dengan ambiguitas antara spread_element dan expression_element (dijelaskan di sini, ada ambiguitas sindikat langsung antara with_element dan expression_element. Secara khusus with(<arguments>) keduanya persis tubuh produksi untuk with_element, dan juga dapat dijangkau melalui expression_element -> expression -> ... -> invocation_expression. Ada aturan menyeluruh sederhana untuk collection_elements. Secara khusus, jika elemen secara leksikal dimulai dengan urutan with( token maka selalu diperlakukan sebagai with_element.

Ini bermanfaat dalam dua cara. Pertama, implementasi kompilator hanya perlu melihat token berikut yang dilihatnya untuk menentukan jenis elemen apa yang akan diurai. Kedua, secara sesuai, pengguna dapat secara sepele memahami elemen seperti apa yang mereka miliki tanpa harus mencoba mengurai apa yang berikut untuk melihat apakah mereka harus menganggapnya sebagai with_element atau expression_element.

Contoh

Contoh tampilannya adalah:

// With an existing type:

// Initialize to twice the capacity since we'll have to add
// more values later.
List<string> names = [with(capacity: values.Count * 2), .. values];

Bentuk-bentuk ini tampaknya "dibaca" dengan cukup baik. Dalam semua kasus tersebut, kodenya adalah "membuat ekspresi koleksi, 'dengan' argumen berikut untuk diteruskan untuk mengontrol instans akhir, lalu elemen berikutnya yang digunakan untuk mengisinya. Misalnya, baris pertama "membuat daftar string 'dengan' kapasitas dua kali jumlah nilai yang akan disebarkan ke dalamnya"

Yang penting, kode ini memiliki sedikit kemungkinan untuk diabaikan seperti dengan formulir seperti: [arg; element], sambil juga menambahkan verbositas minimal, dengan sejumlah besar fleksibilitas untuk melewati argumen yang diinginkan bersama.

Ini secara teknis akan menjadi perubahan yang melanggar karena with(...)bisa saja panggilan ke metode yang sudah ada sebelumnya yang disebut with. Namun, tidak seperti new(...) yang merupakan cara yang diketahui dan direkomendasikan untuk membuat nilai yang diketik secara implisit, with(...) jauh lebih kecil kemungkinannya sebagai nama metode, menjalankan banyak penamaan .Net untuk metode. Dalam hal yang tidak mungkin bahwa pengguna memang memiliki metode seperti itu, mereka pasti akan dapat terus memanggil ke metode yang ada dengan menggunakan @with(...).

Kami akan menerjemahkan elemen ini with(...) seperti:

List<string> names = [with(/*capacity*/10), ...]; // translates to:

// argument_list *becomes* the argument list for the
// constructor call. 
__result = new List<string>(10); // followed by normal initialization

// or

IList<string> names2 = [with(capacity: 20), ...]; // translates to:

__result = new List<string>(20);

Dengan kata lain, argumen argument_list akan diteruskan ke konstruktor yang sesuai jika kita memanggil konstruktor, atau ke 'buat metode' yang sesuai jika kita memanggil metode seperti itu. Kami juga akan mengizinkan satu argumen yang mewarisi dari jenis pembanding BCL untuk disediakan saat membuat instans salah satu jenis antarmuka kamus tujuan untuk mengontrol perilakunya.

Conversions

Bagian konversi untuk koleksi-ekspresi diperbarui dengan cara berikut:

> A struct or class type that implements System.Collections.IEnumerable where:

-  * The type has an applicable constructor that can be invoked with no arguments, and the constructor is accessible at the location of the collection expression.
+  a. the collection expression has no `with_element` and the type has an applicable constructor
+     that can be invoked with no arguments, accessible at the location of the collection expression. or
+  b. the collection expression has a `with_element` and the type has at least one constructor
+     accessible at the location of the collection expression. 

Perhatikan argumen aktual dalam argument_list tidak with_element mempengaruhi apakah konversi ada atau tidak. Hanya kehadiran atau tidak adanya itu with_element sendiri. Intuisi di sini hanyalah bahwa jika ekspresi koleksi ditulis tanpa satu (seperti [x, y, z]) harus dapat memanggil konstruktor tanpa args. Sementara jika memilikinya [with(...), x, y, z] maka dapat memanggil konstruktor yang sesuai. Ini juga berarti bahwa jenis yang tidak dapat dipanggil dengan konstruktor tanpa argumen dapat digunakan dengan ekspresi koleksi, tetapi hanya jika ekspresi koleksi yang berisi with_element.

Penentuan aktual tentang bagaimana with_element suatu akan memengaruhi konstruksi diberikan di bawah ini.

Pembangunan

Konstruksi diperbarui sebagai berikut.

Elemen ekspresi koleksi dievaluasi secara berurutan, kiri ke kanan. Dalam argumen koleksi, argumen dievaluasi secara berurutan, kiri ke kanan. Setiap elemen atau argumen dievaluasi tepat sekali, dan referensi lebih lanjut mengacu pada hasil evaluasi awal ini.

Jika collection_arguments disertakan dan bukan elemen pertama dalam ekspresi koleksi, kesalahan waktu kompilasi akan dilaporkan.

Jika daftar argumen berisi nilai apa pun dengan jenis dinamis , kesalahan waktu kompilasi dilaporkan (LDM-2025-01-22).

Konstruktor

Jika jenis target adalah jenis struct atau class yang mengimplementasikan System.Collections.IEnumerable, dan jenis target tidak memiliki metode create, dan jenis target bukan jenis parameter generik maka:

  • Resolusi kelebihan beban digunakan untuk menentukan konstruktor instans terbaik dari kandidat.
  • Kumpulan konstruktor kandidat adalah semua konstruktor instans yang dapat diakses yang dideklarasikan pada jenis target yang berlaku sehubungan dengan daftar argumen sebagaimana didefinisikan dalam anggota fungsi yang berlaku.
  • Jika konstruktor instans terbaik ditemukan, konstruktor dipanggil dengan daftar argumen.
    • Jika konstruktor memiliki params parameter, pemanggilan mungkin dalam bentuk yang diperluas.
  • Jika tidak, kesalahan pengikatan dilaporkan.
// List<T> candidates:
//   List<T>()
//   List<T>(IEnumerable<T> collection)
//   List<T>(int capacity)
List<int> l;
l = [with(capacity: 3), 1, 2]; // new List<int>(capacity: 3)
l = [with([1, 2]), 3];         // new List<int>(IEnumerable<int> collection)
l = [with(default)];           // error: ambiguous constructor

Metode CollectionBuilderAttribute

Jika jenis target adalah jenis dengan metode buat, maka:

  • Resolusi kelebihan beban digunakan untuk menentukan metode pembuatan terbaik dari kandidat.
  • Untuk setiap metode buat untuk jenis target, kami menentukan metode proyeksi dengan tanda tangan yang identik dengan metode buat tetapi tanpa parameter terakhir.
  • Kumpulan metode proyeksi kandidat adalah metode proyeksi yang berlaku sehubungan dengan daftar argumen seperti yang didefinisikan dalam anggota fungsi yang berlaku.
  • Jika metode proyeksi terbaik ditemukan, metode buat yang sesuai dipanggil dengan daftar argumen ditambahkan dengan elemen yang ReadOnlySpan<T> berisi.
  • Jika tidak, kesalahan pengikatan dilaporkan.
[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> elements);
    public static MyCollection<T> Create<T>(IEqualityComparer<T> comparer, ReadOnlySpan<T> elements);
}
MyCollection<string> c1 = [with(GetComparer()), "1", "2"];
// IEqualityComparer<string> _tmp1 = GetComparer();
// ReadOnlySpan<string> _tmp2 = ["1", "2"];
// c1 = MyBuilder.Create<string>(_tmp1, _tmp2);

MyCollection<string> c2 = [with(), "1", "2"];
// ReadOnlySpan<string> _tmp3 = ["1", "2"];
// c2 = MyBuilder.Create<string>(_tmp3);

CollectionBuilderAttribute: Membuat metode

Untuk ekspresi koleksi di mana definisi jenis target memiliki [CollectionBuilder] atribut, metode buat adalah sebagai berikut, diperbarui dari ekspresi koleksi: buat metode.

[CollectionBuilder(...)] Atribut menentukan jenis penyusun dan nama metode metode yang akan dipanggil untuk membuat instans jenis koleksi.

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 terakhir jenis System.ReadOnlySpan<E>, diteruskan oleh 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.

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 .

Perbedaan utama dari algoritma sebelumnya adalah:

  • Metode buat mungkin memiliki parameter tambahan sebelumReadOnlySpan<E> parameter .
  • Beberapa metode buat didukung.

Jenis target antarmuka

Jika jenis target adalah jenis antarmuka, maka:

  • Resolusi kelebihan beban digunakan untuk menentukan tanda tangan metode kandidat terbaik.

  • Kumpulan tanda tangan kandidat adalah tanda tangan di bawah ini untuk antarmuka target yang berlaku sehubungan dengan daftar argumen seperti yang didefinisikan dalam anggota fungsi yang berlaku.

    Interfaces Tanda tangan kandidat
    IEnumerable<E>
    IReadOnlyCollection<E>
    IReadOnlyList<E>
    () (tidak ada parameter)
    ICollection<E>
    IList<E>
    List<E>()
    List<E>(int)

Jika tanda tangan metode terbaik ditemukan, semantiknya adalah sebagai berikut:

  • Tanda tangan kandidat untuk IEnumerable<E>, IReadOnlyCollection<E> dan IReadOnlyList<E> sederhana () dan memiliki arti yang sama seperti tidak memiliki with() elemen sama sekali.
  • Tanda tangan kandidat untuk IList<T> dan ICollection<T> merupakan tanda tangan dan List<T>()List<T>(int) konstruktor. Saat membuat nilai (lihat Terjemahan Antarmuka yang Dapat Diubah), konstruktor masing-masing List<T> akan dipanggil.
  • Jika tidak, kesalahan pengikatan dilaporkan.

jenis target Dictionary-Interface

Ini ditentukan di sini sebagai bagian dari fitur yang ditentukan dalam https://github.com/dotnet/csharplang/blob/main/proposals/dictionary-expressions.md.

Daftar di atas ditambat untuk memiliki item berikut:

Interfaces Tanda tangan kandidat
IReadOnlyDictionary<K, V> () (tidak ada parameter)
(IEqualityComparer<K>? comparer)
IDictionary<K, V> Dictionary<K, V>()
Dictionary<K, V>(int)
Dictionary<K, V>(IEqualityComparer<K>)
Dictionary<K, V>(int, IEqualityComparer<K>)

Jika tanda tangan metode terbaik ditemukan, semantiknya adalah sebagai berikut:

  • Tanda tangan kandidat untuk IReadOnlyDictionary<K, V> adalah () (yang memiliki arti yang sama seperti tidak memiliki with() elemen sama sekali), dan (IEqualityComparer<K>). Perbandingan ini akan digunakan untuk hash dengan tepat dan membandingkan kunci dalam kamus tujuan yang dipilih pengkompilasi untuk dibuat (lihat Terjemahan Antarmuka Tidak Dapat Diubah).
  • Tanda tangan kandidat untuk IDictionary<T> adalah tanda tangan Dictionary<K, V>(), , Dictionary<K, V>(int)Dictionary<K, V>(IEqualityComparer<K>) dan Dictionary<K, V>(int, IEqualityComparer<K>)konstruktor. Saat membuat nilai (lihat Terjemahan Antarmuka yang Dapat Diubah), konstruktor masing-masing Dictionary<K, V> akan dipanggil.
  • Jika tidak, kesalahan pengikatan dilaporkan.
IDictionary<string, int> d;
IReadOnlyDictionary<string, int> r;

d = [with(StringComparer.Ordinal)]; // new Dictionary<string, int>(StringComparer.Ordinal)
r = [with(StringComparer.Ordinal)]; // new $PrivateImpl<string, int>(StringComparer.Ordinal)

d = [with(capacity: 2)]; // new Dictionary<string, int>(capacity: 2)
r = [with(capacity: 2)]; // error: 'capacity' parameter not recognized
d = [with()];            // Legal: empty arguments supported for interfaces

Jenis target lainnya

Jika jenis target adalah jenis lain, maka kesalahan pengikatan dilaporkan untuk daftar argumen, bahkan jika kosong.

Span<int> a = [with(), 1, 2, 3]; // error: arguments not supported
Span<int> b = [with([1, 2]), 3]; // error: arguments not supported

int[] a = [with(), 1, 2, 3]; // error: arguments not supported
int[] b = [with(length: 1), 3]; // error: arguments not supported

Keamanan ref

Kami menyesuaikan aturan collection-expressions.md#ref-safety untuk memperhitungkan with() elemen .

Lihat juga batasan konteks Aman §16.4.15.

Membuat metode

Bagian ini berlaku untuk ekspresi koleksi yang jenis targetnya memenuhi batasan yang ditentukan dalam metode CollectionBuilderAttribute.

Konteks aman ditentukan dengan memodifikasi klausul dari collection-expressions.md#ref-safety (perubahan tebal):

  • Jika jenis target adalah jenis struct ref dengan metode buat, konteks aman ekspresi koleksi adalah konteks aman dari pemanggilan metode buat di mana argumen adalah with() argumen elemen diikuti oleh ekspresi koleksi sebagai argumen untuk parameter terakhir ( ReadOnlySpan<E> parameter).

Argumen metode harus cocok dengan batasan yang berlaku untuk ekspresi koleksi. Demikian pula dengan penentuan konteks aman di atas, argumen metode harus cocok dengan batasan diterapkan dengan memperlakukan ekspresi koleksi sebagai pemanggilan metode buat, di mana argumen adalah with() argumen elemen diikuti oleh ekspresi koleksi sebagai argumen untuk parameter terakhir.

Panggilan konstruktor

Bagian ini berlaku untuk ekspresi koleksi yang jenis targetnya memenuhi batasan yang ditentukan dalam Konstruktor.

Untuk ekspresi koleksi jenis struktur ref dari formulir berikut:
[with(a₁, a₂, ..., aₙ), e₁, e₂, ..., eₙ]

Konteks aman ekspresi koleksi adalah yang tersempit dari konteks aman ekspresi berikut:

  • Ekspresi new C(a₁, a₂, ..., aₙ)pembuatan objek , di mana C adalah jenis target
  • Ekspresi e₁, e₂, ..., eₙ elemen (baik ekspresi itu sendiri, atau nilai spread dalam kasus elemen spread).

Argumen metode harus cocok dengan batasan yang berlaku untuk ekspresi koleksi. Batasan diterapkan dengan memperlakukan ekspresi koleksi sebagai pembuatan objek formulir new C(a₁, a₂, ..., aₙ) { e₁, e₂, ..., eₙ } per low-level-struct-improvements.md#rules-for-object-initializeers.

  • Elemen ekspresi diperlakukan seolah-olah mereka adalah penginisialisasi elemen koleksi.
  • Elemen spread diperlakukan sama, dengan mengasumsikan sementara yang C memiliki Add(SpreadType spread) metode, di mana SpreadType adalah jenis nilai spread.

Pertanyaan yang dijawab

Argumen dynamic

Haruskah argumen dengan dynamic jenis diizinkan? Itu mungkin mengharuskan penggunaan pengikat runtime untuk resolusi kelebihan beban, yang akan menyulitkan untuk membatasi kumpulan kandidat, misalnya untuk kasus penyusun koleksi.

Resolusi: Batasan. LDM-2025-01-22

with() melanggar perubahan

Elemen yang diusulkan with() adalah perubahan yang melanggar.

object x, y, z = ...;
object[] items = [with(x, y), z]; // C#13: ok; C#14: error args not supported for object[]

object with(object x, object y) { ... }

Konfirmasikan bahwa perubahan yang melanggar dapat diterima, dan apakah perubahan yang melanggar harus terkait dengan versi bahasa.

Resolusi: Pertahankan perilaku sebelumnya (tidak ada perubahan yang melanggar) saat mengkompilasi dengan versi bahasa yang lebih lama. LDM-2025-03-17

Haruskah argumen memengaruhi konversi ekspresi koleksi?

Haruskah argumen pengumpulan dan metode yang berlaku memengaruhi konvertibilitas ekspresi koleksi?

Print([with(comparer: null), 1, 2, 3]); // ambiguous or Print<int>(HashSet<int>)?

static void Print<T>(List<T> list) { ... }
static void Print<T>(HashSet<T> set) { ... }

Jika argumen memengaruhi konvertibilitas berdasarkan metode yang berlaku, argumen mungkin juga akan memengaruhi inferensi jenis.

Print([with(comparer: StringComparer.Ordinal)]); // Print<string>(HashSet<string>)?

Sebagai referensi, kasus serupa dengan jenis target mengakibatkan new() kesalahan.

Print<int>(new(comparer: null));              // error: ambiguous
Print(new(comparer: StringComparer.Ordinal)); // error: type arguments cannot be inferred

Resolusi: Argumen koleksi harus diabaikan dalam konversi dan mengetik inferensi. LDM-2025-03-17

Urutan parameter metode penyusun koleksi

Untuk metode penyusun koleksi , haruskah parameter rentang sebelum atau sesudah parameter apa pun untuk argumen koleksi?

Elemen pertama-tama akan memungkinkan argumen dideklarasikan sebagai opsional.

class MySetBuilder
{
    public static MySet<T> Create<T>(ReadOnlySpan<T> items, IEqualityComparer<T> comparer = null) { ... }
}

Argumen terlebih dahulu akan memungkinkan rentang menjadi params parameter, untuk mendukung panggilan langsung dalam bentuk yang diperluas.

var s = MySetBuilder.Create(StringComparer.Ordinal, x, y, z);

class MySetBuilder
{
    public static MySet<T> Create<T>(IEqualityComparer<T> comparer, params ReadOnlySpan<T> items) { ... }
}

Resolusi: Parameter rentang untuk elemen harus menjadi parameter terakhir. LDM-2025-03-12

Argumen dengan versi bahasa yang lebih lama

Apakah kesalahan dilaporkan with() saat mengkompilasi dengan versi bahasa yang lebih lama, atau apakah with mengikat ke simbol lain dalam cakupan?

Resolusi: Tidak ada perubahan mencolok untuk with di dalam ekspresi koleksi saat mengkompilasi dengan versi bahasa yang lebih lama. LDM-2025-03-17

Jenis target tempat argumen diperlukan

Haruskah konversi ekspresi koleksi didukung ke jenis target di mana argumen harus disediakan karena semua konstruktor atau metode pabrik memerlukan setidaknya satu argumen?

Jenis tersebut dapat digunakan dengan ekspresi koleksi yang menyertakan argumen eksplisit with() tetapi jenisnya tidak dapat digunakan untuk params parameter.

Misalnya, pertimbangkan jenis berikut yang dibangun dari metode pabrik:

MyCollection<object> c;
c = [];                  // error: no arguments
c = [with(capacity: 1)]; // ok

[CollectionBuilder(typeof(MyBuilder), "Create")]
class MyCollection<T> : IEnumerable<T> { ... }

class MyBuilder
{
    public static MyCollection<T> Create<T>(ReadOnlySpan<T> items, int capacity) { ... }
}

Pertanyaan yang sama berlaku ketika konstruktor dipanggil langsung seperti pada contoh di bawah ini.

Namun, untuk jenis target tempat konstruktor dipanggil secara langsung, konversi ekspresi koleksi saat ini memerlukan konstruktor yang dapat dipanggil tanpa argumen, tetapi argumen koleksi diabaikan saat menentukan konvertibilitas.

c = [];                  // error: no arguments
c = [with(capacity: 1)]; // error: no constructor callable with no arguments?

class MyCollection<T> : IEnumerable<T>
{
    public MyCollection(int capacity) { ... }
    public void Add(T t) { ... }
    // ...
}

Resolusi: Mendukung konversi ke jenis target di mana semua konstruktor atau metode pabrik memerlukan argumen, dan memerlukan with() konversi. LDM-2025-03-05

__arglist

Harus __arglist didukung dalam with() elemen?

class MyCollection : IEnumerable
{
    public MyCollection(__arglist) { ... }
    public void Add(object o) { }
}

MyCollection c;
c = [with(__arglist())];    // ok
c = [with(__arglist(x, y)]; // ok

Resolusi: Tidak ada dukungan untuk __arglist dalam argumen koleksi kecuali gratis. LDM-2025-03-05

Argumen untuk jenis antarmuka

Haruskah argumen didukung untuk jenis target antarmuka?

ICollection<int> c = [with(capacity: 4)];
IReadOnlyDictionary<string, int> d = [with(comparer: StringComparer.Ordinal), ..values];
Jika demikian, tanda tangan metode mana yang digunakan saat mengikat argumen?

Untuk jenis antarmuka yang dapat diubah , opsinya adalah:

  1. Gunakan konstruktor yang dapat diakses dari jenis terkenal yang diperlukan untuk instans: List<T> atau Dictionary<K, V>.
  2. Gunakan tanda tangan independen dari jenis tertentu, misalnya menggunakan new() dan new(int capacity) untuk ICollection<T> dan IList<T> (lihat Konstruksi untuk tanda tangan potensial untuk setiap antarmuka).

Menggunakan konstruktor yang dapat diakses dari jenis terkenal memiliki implikasi berikut:

  • Nama parameter, opsional-ness, params, diambil dari parameter secara langsung.
  • Semua konstruktor yang dapat diakses disertakan, meskipun mungkin tidak berguna untuk ekspresi koleksi, seperti List(IEnumerable<T>) yang akan memungkinkan IList<int> list = [with(1, 2, 3)];.
  • Set konstruktor dapat bergantung pada versi BCL.

Rekomendasi: Gunakan konstruktor yang dapat diakses dari jenis terkenal. Kami telah menjamin kami akan menggunakan jenis ini, jadi ini hanya 'jatuh' dan merupakan jalur terjelas dan paling sederhana untuk membangun nilai-nilai ini.

Untuk jenis antarmuka yang tidak dapat diubah , opsinya serupa:

  1. Jangan lakukan apa pun. Ini
  2. Gunakan tanda tangan independen dari jenis tertentu, meskipun satu-satunya skenario mungkin new(IEqualityComparer<K> comparer) untuk IReadOnlyDictionary<K, V> C#14..

Menggunakan konstruktor yang dapat diakses dari beberapa jenis terkenal (strategi untuk jenis antarmuka yang dapat diubah) tidak layak karena tidak ada hubungannya dengan jenis tertentu yang ada, dan jenis akhir yang dapat kami gunakan dan/atau sintesis. Dengan demikian, harus ada persyaratan baru yang aneh bahwa kompilator dapat memetakan konstruktor jenis tersebut yang ada (bahkan saat berkembang) ke instans yang tidak dapat diubah yang benar-benar dihasilkannya.

Rekomendasi: Gunakan tanda tangan independen dari jenis tertentu. Dan, untuk C# 14, hanya dukungan new(IEqualityComparer<K> comparer) karena IReadOnlyDictionary<K, V> itu adalah satu-satunya antarmuka yang tidak dapat diubah di mana kami merasa sangat penting untuk kegunaan/semantik untuk memungkinkan pengguna menyediakan ini. Rilis C# di masa mendatang dapat mempertimbangkan untuk memperluas set ini berdasarkan pertimbangan solid yang disediakan.

Resolusi:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-04-23.md

Argumen didukung untuk jenis target antarmuka. Untuk antarmuka yang dapat diubah dan tidak dapat diubah, kumpulan argumen akan dikurasi.

Daftar yang diharapkan (yang masih perlu diratifikasi LDM) adalah Jenis target antarmuka

Daftar argumen kosong

Haruskah kita mengizinkan daftar argumen kosong untuk beberapa atau semua jenis target?

with() Kosong akan setara dengan tidak ada with(). Ini mungkin memberikan beberapa konsistensi dengan kasus yang tidak kosong, tetapi tidak akan menambahkan kemampuan baru.

Arti dari 'with()' kosong mungkin lebih jelas untuk beberapa jenis target daripada yang lain: - Untuk jenis di mana **konstruktor** digunakan, panggil konstruktor yang berlaku tanpa argumen. - Untuk jenis dengan **'CollectionBuilderAttribute'**, panggil metode pabrik yang berlaku hanya dengan elemen. - Untuk **jenis antarmuka**, buat jenis yang terkenal atau ditentukan implementasi tanpa argumen. - Namun, untuk **array** dan **spans**, di mana argumen koleksi tidak didukung, 'with()' mungkin membingungkan.
List<int>           l = [with()]; // ok? new List<int>()
ImmutableArray<int> m = [with()]; // ok? ImmutableArray.Create<int>()

IList<int>       i = [with()]; // ok? new List<int>() or equivalent
IEnumerable<int> e = [with()]; // ok?

int[]     a = [with()]; // ok?
Span<int> s = [with()]; // ok?

Resolusi:https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-05-12.md#empty-argument-lists

Kami akan mengizinkan dengan() untuk jenis konstruktor dan jenis penyusun yang dapat dipanggil tanpa argumen sama sekali, dan kami akan menambahkan tanda tangan konstruktor kosong untuk jenis antarmuka (dapat diubah dan dibaca). Array dan rentang tidak akan memungkinkan dengan(), karena tidak ada tanda tangan yang cocok dengannya.

Buka pertanyaan

Menyelesaikan kekhawatiran terbuka dari https://github.com/dotnet/csharplang/blob/main/meetings/2025/LDM-2025-03-17.md#conclusion

with(...) adalah perubahan yang melanggar dalam bahasa dengan [with(...)]. Sebelum fitur ini, ini berarti ekspresi koleksi dengan satu elemen, yang merupakan hasil dari memanggil with-invocation-expression. Setelah fitur ini, ini adalah koleksi, yang memiliki argumen yang diteruskan ke dalamnya.

Apakah kita ingin pemisah ini terjadi hanya ketika pengguna memilih versi bahasa tertentu (seperti C#-14/15?). Dengan kata lain, jika mereka berada di langversion yang lebih lama, mereka mendapatkan logika penguraian sebelumnya, tetapi pada versi yang lebih baru mereka mendapatkan logika penguraian yang lebih baru. Di Atau apakah kita selalu ingin memiliki logika penguraian yang lebih baru, bahkan pada langversi yang lebih tua?

Kami memiliki seni sebelumnya untuk kedua strategi. required, misalnya, selalu diurai dengan logika baru, terlepas dari langversi. Sedangkan, record/field dan yang lain mengubah logika penguraiannya tergantung pada versi bahasa.

Akhirnya, ini memiliki tumpang tindih dan berdampak dengan Dictionary Expressions, yang memperkenalkan key:value sintaks untuk elemen KVP. Kami ingin menetapkan perilaku yang kami inginkan untuk versi bahasa lang apa pun, dan untuk [with(...)] sendirinya, dan hal-hal seperti [with(...) : expr] atau [expr : with(...)].