Bagikan melalui


Anggota ekstensi

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 tertuangkan dalam catatan rapat terkait desain bahasa (LDM) .

Anda dapat mempelajari lebih lanjut tentang proses untuk mengadopsi speklet fitur ke dalam standar bahasa C# dalam artikel tentang spesifikasi .

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

Deklarasi

Sintaksis

class_body
    : '{' class_member_declaration* '}' ';'?
    | ';'
    ;

class_member_declaration
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    | extension_declaration // add
    ;

extension_declaration // add
    : 'extension' type_parameter_list? '(' receiver_parameter ')' type_parameter_constraints_clause* extension_body
    ;

extension_body // add
    : '{' extension_member_declaration* '}' ';'?
    ;

extension_member_declaration // add
    : method_declaration
    | property_declaration
    | operator_declaration
    ;

receiver_parameter // add
    : attributes? parameter_modifiers? type identifier?
    ;

Deklarasi ekstensi hanya boleh dideklarasikan dalam kelas statis non-generik dan tidak bersarang.
Ini adalah kesalahan untuk jenis yang dinamai extension.

Aturan cakupan

Parameter jenis dan parameter penerima dari deklarasi ekstensi berada dalam cakupan dalam isi deklarasi ekstensi. Ini adalah kesalahan untuk merujuk ke parameter penerima dari dalam anggota tetap, kecuali dalam ekspresi nameof. Ini adalah kesalahan bagi anggota untuk mendeklarasikan parameter jenis atau parameter lainnya (serta variabel lokal dan fungsi lokal langsung dalam isi anggota) dengan nama yang sama dengan parameter jenis atau parameter penerima pada deklarasi ekstensi.

public static class E
{
    extension<T>(T[] ts)
    {
        public bool M1(T t) => ts.Contains(t);        // `T` and `ts` are in scope
        public static bool M2(T t) => ts.Contains(t); // Error: Cannot refer to `ts` from static context
        public void M3(int T, string ts) { }          // Error: Cannot reuse names `T` and `ts`
        public void M4<T, ts>(string s) { }           // Error: Cannot reuse names `T` and `ts`
    }
}

Bukan kesalahan bagi anggota itu sendiri untuk memiliki nama yang sama dengan parameter jenis atau parameter penerima dari deklarasi ekstensi penutup. Nama anggota tidak secara langsung ditemukan dalam pencarian nama sederhana dari dalam deklarasi ekstensi; pencarian dengan demikian akan menemukan parameter jenis atau parameter penerima dari nama tersebut, bukan anggota.

Anggota memang memunculkan metode statis yang dideklarasikan langsung pada kelas statis penutup, dan dapat ditemukan melalui pencarian nama yang sederhana; namun demikian, parameter jenis atau parameter penerima dari deklarasi ekstensi dengan nama yang sama akan ditemukan terlebih dahulu.

public static class E
{
    extension<T>(T[] ts)
    {
        public void T() { M(ts); } // Generated static method M<T>(T[]) is found
        public void M() { T(ts); } // Error: T is a type parameter
    }
}

Kelas statis sebagai kontainer ekstensi

Ekstensi dinyatakan di dalam kelas statis non-generik tingkat atas, sama seperti metode ekstensi saat ini, dan dengan demikian dapat hidup berdampingan dengan metode ekstensi klasik dan anggota statis non-ekstensi:

public static class Enumerable
{
    // New extension declaration
    extension(IEnumerable source) { ... }
    
    // Classic extension method
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    // Non-extension member
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

Deklarasi ekstensi

Deklarasi ekstensi bersifat anonim, dan menyediakan spesifikasi penerima dengan parameter dan batasan jenis terkait, diikuti oleh serangkaian deklarasi anggota ekstensi. Spesifikasi penerima mungkin dalam bentuk parameter, atau - jika hanya anggota ekstensi statis yang dinyatakan - jenis:

public static class Enumerable
{
    extension(IEnumerable source) // extension members for IEnumerable
    {
        public bool IsEmpty { get { ... } }
    }
    extension<TSource>(IEnumerable<TSource> source) // extension members for IEnumerable<TSource>
    {
        public IEnumerable<T> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TResult> Select<TResult>(Func<TSource, TResult> selector) { ... }
    }
    extension<TElement>(IEnumerable<TElement>) // static extension members for IEnumerable<TElement>
        where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator +(IEnumerable<TElement> first, IEnumerable<TElement> second) { ... }
    }
}

Jenis dalam spesifikasi penerima disebut sebagai jenis penerima dan nama parameter, jika ada, disebut sebagai parameter penerima.

Jika parameter penerima diberi nama, jenis penerima mungkin tidak statis.
Parameter penerima tidak diizinkan untuk memiliki pengubah jika tidak diberi nama, dan hanya diizinkan untuk memiliki pengubah refness yang tercantum di bawah ini dan scoped sebaliknya.
Parameter penerima menanggung pembatasan yang sama dengan parameter pertama dari metode ekstensi klasik.
Atribut [EnumeratorCancellation] diabaikan jika ditempatkan pada parameter penerima.

Anggota ekstensi

Deklarasi anggota ekstensi secara sintetis identik dengan instans dan anggota statis yang sesuai di kelas dan deklarasi struktur (dengan pengecualian konstruktor). Anggota instans mengacu pada penerima dengan menggunakan nama parameter penerima.

public static class Enumerable
{
    extension(IEnumerable source)
    {
        // 'source' refers to receiver
        public bool IsEmpty => !source.GetEnumerator().MoveNext();
    }
}

Ini adalah kesalahan untuk menentukan anggota ekstensi instans jika deklarasi ekstensi terlampir tidak menentukan parameter penerima.

public static class Enumerable
{
    extension(IEnumerable) // No parameter name
    {
        public bool IsEmpty => true; // Error: instance extension member not allowed
    }
}

Ini adalah kesalahan untuk menentukan pengubah berikut pada anggota deklarasi ekstensi: abstract, virtual, override, new, sealed, partial, dan protected (dan pengubah aksesibilitas terkait).
Ini adalah kesalahan untuk menentukan pengubah readonly pada anggota deklarasi ekstensi.
Properti dalam deklarasi ekstensi mungkin tidak memiliki init aksesor.
Anggota instans tidak diizinkan jika parameter penerima tidak memiliki nama.

Semua anggota harus memiliki nama yang berbeda dari nama kelas pembungkus statis dan nama tipe turunan jika memilikinya.

Ini adalah kesalahan untuk menyematkan atribut [ModuleInitializer] pada anggota ekstensi.

Kesempurnaan

Secara default penerima diteruskan ke anggota ekstensi instans berdasarkan nilai, sama seperti parameter lainnya. Namun, penerima deklarasi ekstensi dalam formulir parameter dapat menentukan ref, ref readonly dan in, selama jenis penerima diketahui sebagai jenis nilai.

Nullability dan atribut

Jenis penerima dapat berupa atau berisi jenis referensi nullable, dan spesifikasi penerima yang dalam bentuk parameter dapat menentukan atribut:

public static class NullableExtensions
{
    extension(string? text)
    {
        public string AsNotNull => text is null ? "" : text;
    }
    extension([NotNullWhen(false)] string? text)
    {
        public bool IsNullOrEmpty => text is null or [];
    }
    extension<T> ([NotNull] T t) where T : class?
    {
        public void ThrowIfNull() => ArgumentNullException.ThrowIfNull(t);
    }
}

Kompatibilitas dengan metode ekstensi klasik

Metode ekstensi instans menghasilkan artefak yang cocok dengan yang diproduksi oleh metode ekstensi klasik.

Secara khusus metode statis yang dihasilkan memiliki atribut, pengubah, dan nama metode ekstensi yang dideklarasikan, serta daftar parameter jenis, daftar parameter dan daftar batasan yang digabungkan dari deklarasi ekstensi dan deklarasi metode dalam urutan tersebut:

public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) // Generate compatible extension methods
    {
        public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
        public IEnumerable<TSource> Select<TResult>(Func<TSource, TResult> selector)  { ... }
    }
}

Menghasilkan:

[Extension]
public static class Enumerable
{
    [Extension]
    public static IEnumerable<TSource> Where<TSource>(IEnumerable<TSource> source, Func<TSource, bool> predicate) { ... }

    [Extension]
    public static IEnumerable<TSource> Select<TSource, TResult>(IEnumerable<TSource> source, Func<TSource, TResult> selector)  { ... }
}

Para Operator

Meskipun operator ekstensi memiliki jenis operand eksplisit, operator tersebut masih perlu dideklarasikan dalam deklarasi ekstensi:

public static class Enumerable
{
    extension<TElement>(IEnumerable<TElement>) where TElement : INumber<TElement>
    {
        public static IEnumerable<TElement> operator *(IEnumerable<TElement> vector, TElement scalar) { ... }
        public static IEnumerable<TElement> operator *(TElement scalar, IEnumerable<TElement> vector) { ... }
    }
}

Ini memungkinkan parameter jenis dideklarasikan dan disimpulkan, dan dianalogikan dengan bagaimana operator biasa yang ditentukan pengguna harus dideklarasikan dalam salah satu jenis operand-nya.

Memeriksa

Inferabilitas: Untuk setiap anggota ekstensi non-metode, semua parameter tipe dari blok ekstensinya harus digunakan dalam kumpulan parameter gabungan dari ekstensi dan anggota.

Keunikan: Dalam kelas statis penutup tertentu, sekumpulan deklarasi anggota ekstensi dengan jenis penerima yang sama (konversi identitas modulo dan penggantian nama parameter jenis) diperlakukan sebagai ruang deklarasi tunggal yang mirip dengan anggota dalam kelas atau deklarasi struktur, dan tunduk pada aturan yang sama tentang keunikan.

public static class MyExtensions
{
    extension<T1>(IEnumerable<int>) // Error! T1 not inferrable
    {
        ...
    }
    extension<T2>(IEnumerable<T2>)
    {
        public bool IsEmpty { get ... }
    }
    extension<T3>(IEnumerable<T3>?)
    {
        public bool IsEmpty { get ... } // Error! Duplicate declaration
    }
}

Penerapan aturan keunikan ini mencakup metode ekstensi klasik dalam kelas statis yang sama. Untuk tujuan perbandingan dengan metode dalam deklarasi ekstensi, this parameter diperlakukan sebagai spesifikasi penerima bersama dengan parameter jenis apa pun yang disebutkan dalam jenis penerima tersebut, dan parameter jenis dan parameter metode yang tersisa digunakan untuk tanda tangan metode:

public static class Enumerable
{
    public static IEnumerable<TResult> Cast<TResult>(this IEnumerable source) { ... }
    
    extension(IEnumerable source) 
    {
        IEnumerable<TResult> Cast<TResult>() { ... } // Error! Duplicate declaration
    }
}

Konsumsi

Ketika pencarian anggota ekstensi dilakukan, semua deklarasi ekstensi dalam kelas statis yang usingdiimpor menyertakan anggota mereka sebagai kandidat, apapun jenis penerima. Hanya sebagai bagian dari resolusi, kandidat dengan tipe penerima yang tidak kompatibel akan dibuang.
Inferensi tipe generik secara penuh dicoba antara tipe argumen (termasuk penerima sebenarnya) dan parameter tipe apa pun (menggabungkannya dalam deklarasi ekstensi dan dalam deklarasi anggota ekstensi).
Ketika argumen jenis eksplisit disediakan, argumen tersebut digunakan untuk mengganti parameter jenis deklarasi ekstensi dan deklarasi anggota ekstensi.

string[] strings = ...;

var query = strings.Select(s => s.Length); // extension invocation
var query2 = strings.Select<string, int>(s => s.Length); // ... with explicit full set of type arguments

var query3 = Enumerable.Select(strings, s => s.Length); // static method invocation
var query4 = Enumerable.Where<string, int>(strings, s => s.Length); // ... with explicit full set of type arguments
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source)
    {
        public IEnumerable<TResult> Select<TResult>(Func<T, TResult> predicate) { ... }
    }
}

Demikian pula dengan metode ekstensi klasik, metode implementasi yang dipancarkan dapat dipanggil secara statis.
Ini memungkinkan pengompilasi untuk membedakan antara anggota ekstensi dengan nama dan aritas yang sama.

object.M(); // ambiguous
E1.M();

new object().M2(); // ambiguous
E1.M2(new object());

_ = _new object().P; // ambiguous
_ = E1.get_P(new object());

static class E1
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

static class E2
{
    extension(object)
    {
        public static void M() { }
        public void M2() { }
        public int P => 42;
    }
}

Metode ekstensi statis akan diselesaikan sama seperti metode ekstensi instans (kami akan mempertimbangkan argumen tambahan dari tipe penerima).
Properti ekstensi akan diselesaikan seperti metode ekstensi, dengan parameter tunggal (parameter penerima) dan satu argumen (nilai penerima aktual).

using static arahan

using_static_directive membuat anggota blok ekstensi dalam deklarasi jenis tersedia untuk akses ekstensi.

using static N.E;

new object().M();
object.M2();

_ = new object().Property;
_ = object.Property2;

C c = null;
_ = c + c;
c += 1;

namespace N
{
    static class E
    {
        extension(object o)
        {
            public void M() { }
            public static void M2() { }
            public int Property => 0;
            public static int Property2 => 0;
        }

        extension(C c)
        {
            public static C operator +(C c1, C c2) => throw null;
            public void operator +=(int i) => throw null;
        }
    }
}

class C { } 

Seperti sebelumnya, anggota statis yang dapat diakses (kecuali metode ekstensi) yang terkandung langsung dalam deklarasi jenis yang diberikan dapat direferensikan secara langsung.
Ini berarti bahwa metode implementasi (kecuali yang merupakan metode ekstensi) dapat digunakan langsung sebagai metode statis:

using static E;

M();
System.Console.Write(get_P());
set_P(43);
_ = op_Addition(0, 0);
_ = new object() + new object();

static class E
{
    extension(object)
    {
        public static void M() { }
        public static int P { get => 42; set { } }
        public static object operator +(object o1, object o2) { return o1; }
    }
}

Using_static_directive masih tidak mengimpor metode ekstensi secara langsung sebagai metode statis, sehingga metode implementasi untuk metode ekstensi non-statis tidak dapat dipanggil langsung sebagai metode statis.

using static E;

M(1); // error: The name 'M' does not exist in the current context

static class E
{
    extension(int i)
    {
        public void M() { }
    }
}

AtributPrioritasResolusiKelebihan

Anggota ekstensi dalam kelas statis yang tertutup tunduk pada prioritas sesuai dengan nilai ORPA. Kelas statik penutup dianggap sebagai "jenis yang berisi" yang dipertimbangkan oleh aturan ORPA.
Setiap atribut ORPA yang ada pada properti ekstensi disalin ke metode implementasi untuk aksesor properti, sehingga prioritas dihormati ketika aksesor tersebut digunakan melalui sintaks disambiguasi.

Titik masuk

Metode blok ekstensi tidak memenuhi syarat sebagai kandidat titik awal (lihat "7.1 Permulaan Aplikasi"). Catatan: metode implementasi mungkin masih menjadi kandidat.

Menurunkan

Strategi penurunan untuk deklarasi ekstensi bukanlah keputusan pada tingkat bahasa. Namun, selain menerapkan semantik bahasa, itu harus memenuhi persyaratan tertentu:

  • Format jenis, anggota, dan metadata yang dihasilkan harus ditentukan dengan jelas dalam semua kasus sehingga pengkompilasi lain dapat mengonsumsi dan menghasilkannya.
  • Artefak yang dihasilkan harus stabil, artinya modifikasi wajar di kemudian hari tidak boleh merusak pengguna yang menggunakan versi sebelumnya.

Persyaratan ini membutuhkan lebih banyak penyempurnaan seiring dengan berjalannya implementasi, dan mungkin perlu dikompromikan dalam kasus tertentu untuk memungkinkan pendekatan implementasi yang wajar.

Metadata untuk deklarasi

Tujuan

Desain di bawah ini memungkinkan:

  • Proses memutar balik simbol deklarasi ekstensi melalui metadata (rakitan penuh dan rakitan referensi),
  • referensi stabil ke anggota ekstensi (dokumen xml),
  • penentuan lokal nama yang dihasilkan (berguna untuk EnC),
  • pelacakan API publik.

Untuk dokumen xml, docID dari anggota ekstensi merupakan docID dari anggota ekstensi dalam metadata. Misalnya, docID yang digunakan cref="Extension.extension(object).M(int)" adalah M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) dan docID tersebut stabil di seluruh kompilasi ulang dan pengurutan ulang blok ekstensi. Idealnya, itu juga akan tetap stabil ketika batasan pada blok ekstensi berubah, tetapi kami tidak menemukan desain yang dapat mencapainya tanpa memberikan dampak negatif pada desain bahasa terkait konflik anggota.

Untuk EnC, berguna untuk mengetahui secara lokal (hanya dengan melihat anggota ekstensi yang dimodifikasi) di mana anggota ekstensi yang diperbarui dipancarkan dalam metadata.

Untuk pelacakan API publik, nama yang lebih stabil mengurangi kebisingan. Tetapi secara teknis, nama jenis pengelompokan ekstensi tidak seharusnya berperan dalam skenario seperti itu. Saat melihat anggota ekstensi M, tidak masalah apa nama jenis pengelompokan ekstensi, yang penting adalah signature dari blok ekstensi tersebut. Tanda tangan API publik tidak boleh dilihat sebagai Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) tapi melainkan sebagai Extension.extension(object).M(int). Dengan kata lain, anggota ekstensi harus dilihat memiliki dua set parameter jenis dan dua set parameter.

Gambaran Umum

Blok ekstensi dikelompokkan menurut tanda tangan tingkat CLR mereka. Setiap grup kesetaraan CLR dipancarkan sebagai jenis pengelompokan ekstensi dengan nama berbasis konten. Blok ekstensi dalam grup kesetaraan CLR kemudian dikelompokkan berdasarkan ekuivalensi C#. Setiap grup kesetaraan C# dihasilkan sebagai jenis penanda ekstensi dengan nama berbasis konten, yang ditempatkan dalam jenis kelompok ekstensi yang sesuai. Jenis penanda ekstensi berisi metode penanda ekstensi tunggal yang mengodekan parameter ekstensi. Metode penanda ekstensi bersama tipe penanda ekstensi yang berisi mengodekan tanda tangan blok ekstensi dengan keakuratan penuh. Deklarasi setiap anggota ekstensi dikeluarkan dalam jenis pengelompokan ekstensi yang sesuai, mengacu kembali ke jenis penanda ekstensi berdasarkan namanya melalui atribut, dan disertai dengan metode implementasi statis tingkat atas dengan signature yang dimodifikasi.

Berikut adalah gambaran umum skema pengodean metadata:

[Extension]
static class EnclosingStaticClass
{
    [Extension]
    public sealed class ExtensionGroupingType1 // has type parameters with minimal constraints sufficient to keep extension member declarations below valid
    {
        public static class ExtensionMarkerType1 // has re-declared type parameters with full fidelity of C# constraints
        {
            public static void <Extension>$(... extension parameter ...) // extension marker method
        }
        ... ExtensionMarkerType2, etc ...

        ... extension members for ExtensionGroupingType1, each points to its corresponding extension marker type ...
    }

    ... ExtensionGroupingType2, etc ...

    ... implementation methods ...
}

Kelas statis penutup dipancarkan dengan atribut [Extension].

Tanda tangan tingkat CLR vs. tanda tangan tingkat C#

Tanda tangan tingkat CLR dari suatu blok ekstensi dihasilkan dari:

  • menormalkan nama parameter jenis ke T0, T1, dll ...
  • menghapus atribut
  • menghapus nama parameter
  • menghapus pengubah parameter (seperti ref, , inscoped, ...)
  • menghapus nama tuple
  • menghapus anotasi nullability
  • menghapus batasan notnull

Catatan: batasan lain dipertahankan, seperti new(), , structclass, allows ref struct, unmanaged, dan batasan jenis.

Jenis pengelompokan ekstensi

Jenis pengelompokan ekstensi dipancarkan ke metadata untuk setiap set blok ekstensi di sumber dengan tanda tangan tingkat CLR yang sama.

  • Namanya tak terkatakan dan ditentukan berdasarkan isi tanda tangan tingkat CLR. Detail selengkapnya di bawah ini.
  • Parameter jenisnya memiliki nama yang dinormalisasi (T0, , T1...) dan tidak memiliki atribut.
  • Ini publik dan disegel.
  • Ini ditandai dengan specialname bendera dan [Extension] atribut.

Nama berbasis konten dari jenis pengelompokan ekstensi didasarkan pada tanda tangan tingkat CLR dan mencakup yang berikut ini:

  • Nama CLR yang sepenuhnya memenuhi syarat dari jenis parameter ekstensi.
    • Nama parameter jenis yang dirujuk akan dinormalisasi ke T0, T1, dll ... berdasarkan urutan munculnya dalam deklarasi jenis.
    • Nama yang sepenuhnya memenuhi syarat tidak akan menyertakan rakitan yang berisi. Jenis tipe data atau objek biasa dipindahkan di antara assembly dan ini seharusnya tidak merusak referensi dokumen XML.
  • Batasan parameter jenis akan disertakan dan diurutkan sedemikian rupa sehingga menyusun ulang parameter dalam kode sumber tidak mengubah nama. Khusus:
    • Batasan parameter jenis akan tercantum dalam urutan deklarasi. Batasan untuk parameter jenis Nth akan terjadi sebelum parameter jenis Nth+1.
    • Batasan jenis akan diurutkan dengan membandingkan nama lengkap secara urutan.
    • Batasan non-jenis diurutkan secara deterministik dan ditangani sehingga menghindari ambiguitas atau tabrakan dengan batasan jenis.
  • Karena ini tidak termasuk atribut, ini sengaja mengabaikan karakteristik khusus C# seperti nama tuple, ketidaknullan, dan lain sebagainya.

Catatan: Nama dijamin tetap stabil di seluruh kompilasi ulang, pengurutan ulang, dan perubahan karakteristik khusus C# (yaitu, yang tidak memengaruhi tanda tangan tingkat CLR).

Jenis penanda ekstensi

Jenis penanda mendeklarasikan ulang parameter jenis dari jenis pengelompokan induknya (jenis pengelompokan ekstensi) untuk mendapatkan kesetiaan penuh dari pandangan C# terhadap blok ekstensi.

Jenis penanda ekstensi dipancarkan ke metadata untuk setiap set blok ekstensi di kode sumber dengan tanda tangan tingkat C# yang sama.

  • Namanya tidak dapat diucapkan dan ditentukan berdasarkan isi dari tanda tangan level C# blok ekstensi. Detail selengkapnya di bawah ini.
  • Ini mendefinisikan ulang parameter jenis untuk jenis pengelompokan yang mengandungnya sebagaimana didefinisikan dalam sumber (termasuk nama dan atribut).
  • Ini publik dan statis.
  • Ini ditandai dengan tanda specialname.

Nama berbasis konten dari jenis penanda ekstensi didasarkan pada hal berikut:

  • Nama parameter jenis akan disertakan dalam urutan muncul dalam deklarasi ekstensi
  • Atribut dari parameter tipe akan disertakan dan diurutkan sedemikian rupa sehingga penyusunan ulang dalam kode sumber tidak menyebabkan perubahan nama.
  • Batasan parameter jenis akan disertakan dan diurutkan sedemikian rupa sehingga menyusun ulang parameter dalam kode sumber tidak mengubah nama.
  • Nama lengkap C# dari tipe yang diperluas
    • Ini akan mencakup item seperti anotasi yang dapat bernilai null, nama tuple, dll ...
    • Nama yang sepenuhnya memenuhi syarat tidak akan menyertakan rakitan yang berisi
  • Nama parameter ekstensi
  • Pengubah parameter ekstensi (ref, , ref readonly, scoped...) dalam urutan deterministik
  • Argumen nama dan atribut yang sepenuhnya memenuhi syarat untuk atribut apa pun yang diterapkan ke parameter ekstensi dalam urutan deterministik

Catatan: Nama dijamin tetap stabil di seluruh kompilasi ulang dan pengurutan ulang.
Catatan: jenis penanda ekstensi dan metode penanda ekstensi dipancarkan sebagai bagian dari rakitan referensi.

Metode penanda ekstensi

Tujuan dari metode penanda adalah untuk mengodekan parameter ekstensi dari blok perpanjangan. Karena merupakan anggota dari jenis penanda ekstensi, itu dapat merujuk ke parameter jenis yang dideklarasikan ulang dari jenis penanda ekstensi.

Setiap jenis penanda ekstensi berisi satu metode, metode penanda ekstensi.

  • Ini bersifat statis, tidak generik, tidak mengembalikan nilai, dan disebut <Extension>$.
  • Parameter tunggalnya memiliki atribut, penyempurnaan, jenis, dan nama dari parameter ekstensi.
    Jika parameter ekstensi tidak menentukan nama, maka nama parameter kosong.
  • Ini ditandai dengan tanda specialname.

Aksesibilitas metode penanda akan menjadi aksesibilitas paling tidak terbatas di antara anggota ekstensi terkait yang dideklarasikan, private digunakan jika tidak ada yang dideklarasikan.

Anggota ekstensi

Deklarasi metode/properti dalam blok ekstensi di sumber diwakili sebagai anggota jenis pengelompokan ekstensi dalam metadata.

  • Tanda tangan metode asli dipertahankan (termasuk atribut), tetapi tubuhnya diganti dengan throw NotImplementedException().
  • Hal-hal tersebut tidak boleh dirujuk dalam IL.
  • Metode, properti, dan aksesornya ditandai dengan [ExtensionMarkerName("...")] mengacu pada nama jenis penanda ekstensi yang sesuai dengan blok ekstensi untuk anggota tersebut.

Metode implementasi

Badan metode untuk deklarasi metode/properti dalam blok ekstensi dalam sumber dipancarkan sebagai metode implementasi statis di kelas statis tingkat atas.

  • Metode implementasi memiliki nama yang sama dengan metode asli.
  • Ini memiliki parameter jenis yang berasal dari blok ekstensi yang ditambahkan ke parameter jenis metode asli (termasuk atribut).
  • Ini memiliki aksesibilitas dan atribut yang sama dengan metode asli.
  • Jika menerapkan metode statis, metode tersebut memiliki parameter dan jenis pengembalian yang sama.
  • Jika menerapkan metode instans, ia memiliki parameter yang ditambahkan ke signature metode asli. Atribut, penyempurnaan, jenis, dan nama parameter ini berasal dari parameter ekstensi yang dideklarasikan dalam blok ekstensi yang relevan.
  • Parameter dalam metode implementasi mengacu pada parameter jenis yang dimiliki oleh metode implementasi, bukan parameter blok ekstensi.
  • Jika anggota asli adalah metode biasa dari instans, metode implementasi ditandai dengan atribut [Extension].

Atribut ExtensionMarkerName

Jenis ExtensionMarkerNameAttribute hanya untuk penggunaan pengkompilasi - tidak diizinkan dalam sumber. Deklarasi tipe data akan disintesis oleh kompiler jika belum disertakan dalam kompilasi.

namespace System.Runtime.CompilerServices;

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Enum | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Interface | AttributeTargets.Delegate, Inherited = false)]
public sealed class ExtensionMarkerNameAttribute : Attribute
{
    public ExtensionMarkerNameAttribute(string name)
        => Name = name;

    public string Name { get; }
}

Catatan: Meskipun beberapa target atribut disertakan untuk persiapan di masa depan (ekstensi tipe bersarang, bidang ekstensi, peristiwa ekstensi), AttributeTargets.Constructor tidak disertakan karena konstruktor ekstensi tidak akan menjadi konstruktor.

Contoh

Catatan: kami menggunakan nama berbasis konten yang disederhanakan untuk contoh, untuk keterbacaan. Catatan: karena C# tidak dapat mewakili deklarasi ulang parameter jenis, kode yang mewakili metadata bukan kode C# yang valid.

Berikut adalah contoh yang mengilustrasikan cara kerja pengelompokan, tanpa anggota:

class E
{
    extension<T>(IEnumerable<T> source)
    {
        ... member in extension<T>(IEnumerable<T> source)
    }

    extension<U>(ref IEnumerable<U?> p)
    {
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    extension<T>(IEnumerable<U> source)
        where T : IEquatable<U>
    {
        ... member in extension<T>(IEnumerable<U> source) where T : IEquatable<U>
    }
}

dipancarkan sebagai

[Extension]
class E
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        [SpecialName]
        public static class <>E__ContentName1 // note: re-declares type parameter T0 as T
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<T> source) { }
        }

        [SpecialName]
        public static class <>E__ContentName2 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(ref IEnumerable<U?> p) { }
        }

        [ExtensionMarkerName("<>E__ContentName1")]
        ... member in extension<T>(IEnumerable<T> source)

        [ExtensionMarkerName("<>E__ContentName2")]
        ... member in extension<U>(ref IEnumerable<U?> p)
    }

    [Extension, SpecialName]
    public sealed class <>ContentName_For_IEnumerable_T_With_Constraint<T0>
       where T0 : IEquatable<T0>
    {
        [SpecialName]
        public static class <>E__ContentName3 // note: re-declares type parameter T0 as U
        {
            [SpecialName]
            public static void <Extension>$(IEnumerable<U> source) { }
        }

        [ExtensionMarkerName("ContentName3")]
        public static bool IsPresent(U value) => throw null!;
    }

    ... implementation methods
}

Berikut adalah contoh yang mengilustrasikan bagaimana anggota dipancarkan:

static class IEnumerableExtensions
{
    extension<T>(IEnumerable<T> source) where T : notnull
    {
        public void Method() { ... }
        internal static int Property { get => ...; set => ...; }
        public int Property2 { get => ...; set => ...; }
    }

    extension(IAsyncEnumerable<int> values)
    {
        public async Task<int> SumAsync() { ... }
    }

    public static void Method2() { ... }
}

dipancarkan sebagai

[Extension]
static class IEnumerableExtensions
{
    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IEnumerable_T<T0>
    {
        // Extension marker type is emitted as a nested type and re-declares its type parameters to include C#-isms
        // In this example, the type parameter `T0` is re-declared as `T` with a `notnull` constraint:
        // .class <>E__IEnumerableOfT<T>.<>E__ContentName_For_IEnumerable_T_Source
        // .typeparam T
        //     .custom instance void NullableAttribute::.ctor(uint8) = (...)
        [SpecialName]
        public static class <>E__ContentName_For_IEnumerable_T_Source
        {
            [SpecialName]
            public static <Extension>$(IEnumerable<T> source) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public void Method() => throw null;

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        internal static int Property
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
        public int Property2
        {
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            get => throw null;
            [ExtensionMarkerName("<>E__ContentName_For_IEnumerable_T_Source")]
            set => throw null;
        }
    }

    [Extension, SpecialName]
    public sealed class <>E__ContentName_For_IAsyncEnumerable_Int
    {
        [SpecialName]
        public static class <>E__ContentName_For_IAsyncEnumerable_Int_Values
        {
            [SpecialName]
            public static <Extension>$(IAsyncEnumerable<int> values) => throw null;
        }

        [ExtensionMarkerName("<>E__ContentName_For_IAsyncEnumerable_Int_Values")]
        public Task<int> SumAsync() => throw null;
    }

    // Implementation for Method
    [Extension]
    public static void Method<T>(IEnumerable<T> source) { ... }

    // Implementation for Property
    internal static int get_Property<T>() { ... }
    internal static void set_Property<T>(int value) { ... }

    // Implementation for Property2
    public static int get_Property2<T>(IEnumerable<T> source) { ... }
    public static void set_Property2<T>(IEnumerable<T> source, int value) { ... }

    // Implementation for SumAsync
    [Extension]
    public static int SumAsync(IAsyncEnumerable<int> values) { ... }

    public static void Method2() { ... }
}

Setiap kali anggota ekstensi digunakan dalam sumber, kami akan memancarkan mereka sebagai referensi ke metode implementasi. Misalnya: pemanggilan enumerableOfInt.Method() akan dipancarkan sebagai panggilan statis ke IEnumerableExtensions.Method<int>(enumerableOfInt).

Dokumen XML

Komentar dokumen pada blok ekstensi dipancarkan untuk jenis penanda (DocID untuk blok ekstensi ada E.<>E__MarkerContentName_For_ExtensionOfT'1 dalam contoh di bawah).
Parameter diizinkan untuk mereferensikan parameter ekstensi menggunakan <paramref> dan parameter jenis menggunakan <typeparamref> masing-masing).
Catatan: Anda tidak boleh mendokumen parameter ekstensi atau mengetik parameter (dengan <param> dan <typeparam>) pada anggota ekstensi.

Jika dua blok ekstensi dipancarkan sebagai satu jenis penanda, komentar dokumen mereka juga digabungkan.

Alat yang menggunakan dokumen xml bertanggung jawab untuk menyalin <param> dan <typeparam> dari blok ekstensi ke anggota ekstensi sebagaimana mestinya (yaitu informasi parameter hanya boleh disalin untuk anggota instans).

Suatu <inheritdoc> dipancarkan pada metode implementasi dan mengacu pada anggota ekstensi yang relevan dengan cref. Misalnya, metode implementasi untuk getter mengacu pada dokumentasi properti ekstensi. Jika anggota ekstensi belum memiliki komentar dokumentasi, maka <inheritdoc> dihilangkan.

Untuk blok ekstensi dan anggota ekstensi, saat ini kami tidak memperingatkan jika:

  • parameter ekstensi telah terdokumentasi, tetapi parameter pada anggota ekstensi belum terdokumentasi
  • atau sebaliknya
  • atau dalam skenario yang setara dengan parameter tipe yang tidak terdokumentasi

Misalnya, komentar dokumen berikut:

/// <summary>Summary for E</summary>
static class E
{
    /// <summary>Summary for extension block</summary>
    /// <typeparam name="T">Description for T</typeparam>
    /// <param name="t">Description for t</param>
    extension<T>(T t)
    {
        /// <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
        /// <typeparam name="U">Description for U</typeparam>
        /// <param name="u">Description for u</param>
        public void M<U>(U u) => throw null!;

        /// <summary>Summary for P</summary>
        public int P => 0;
    }
}

menghasilkan xml berikut:

<?xml version="1.0"?>
<doc>
    <assembly>
        <name>Test</name>
    </assembly>
    <members>
        <member name="T:E">
            <summary>Summary for E</summary>
        </member>
        <member name="T:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1">
            <summary>Summary for extension block</summary>
            <typeparam name="T">Description for T</typeparam>
            <param name="t">Description for t</param>
        </member>
        <member name="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)">
            <summary>Summary for M, which may refer to <paramref name="t"/> and <typeparamref name="T"/></summary>
            <typeparam name="U">Description for U</typeparam>
            <param name="u">Description for u</param>
        </member>
        <member name="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P">
            <summary>Summary for P</summary>
        </member>
        <member name="M:E.M``2(``0,``1)">
            <inheritdoc cref="M:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.M``1(``0)"/>
        </member>
        <member name="M:E.get_P``1(``0)">
            <inheritdoc cref="P:E.&lt;&gt;E__MarkerContentName_For_ExtensionOfT`1.P"/>
        </member>
    </members>
</doc>

Referensi CREF

Kita dapat memperlakukan blok ekstensi seperti jenis berlapis, yang dapat diatasi oleh tanda tangan mereka (seolah-olah itu adalah metode dengan parameter ekstensi tunggal). Contoh: E.extension(ref int).M().

Tetapi cref tidak dapat mengatasi blok ekstensi itu sendiri. E.extension(int) dapat merujuk ke metode bernama "extension" dalam jenis E.

static class E
{
  extension(ref int i)
  {
    void M() { } // can be addressed by cref="E.extension(ref int).M()" or cref="extension(ref int).M()" within E, but not cref="M()"
  }
  extension(ref  int i)
  {
    void M(int i2) { } // can be addressed by cref="E.extension(ref int).M(int)" or cref="extension(ref int).M(int)" within E
  }
}

Pencarian diketahui untuk melihat semua blok ekstensi yang cocok.
Saat kami melarang referensi yang tidak memenuhi syarat untuk anggota ekstensi, cref juga akan melarangnya.

Sintaksnya adalah:

member_cref
  : conversion_operator_member_cref
  | extension_member_cref // added
  | indexer_member_cref
  | name_member_cref
  | operator_member_cref
  ;

extension_member_cref // added
 : 'extension' type_argument_list? cref_parameter_list '.' member_cref
 ;

qualified_cref
  : type '.' member_cref
  ;

cref
  : member_cref
  | qualified_cref
  | type_cref
  ;

Kesalahan terjadi saat menggunakan extension_member_cref pada tingkat atas (extension(int).M) atau tertanam di dalam ekstensi lain (E.extension(int).extension(string).M).

Perubahan mendasar

Jenis dan alias mungkin tidak diberi nama "ekstensi".

Masalah terbuka

Bagian sementara dokumen yang terkait dengan masalah terbuka, termasuk diskusi sintaksis yang tidak terfinalisasi dan desain alternatif
  • Haruskah kita menyesuaikan persyaratan penerima saat mengakses anggota ekstensi? (komentar)
  • Konfirmasikan extension vs. extensions sebagai kata kunci (jawaban: extension, LDM 2025-03-24)
  • Konfirmasikan bahwa kami ingin melarang [ModuleInitializer] (jawaban: ya, larang, LDM 2025-06-11)
  • Konfirmasi bahwa kita setuju untuk membuang blok ekstensi sebagai kandidat jalur masuk (jawaban: ya, buang, LDM 2025-06-11)
  • Konfirmasikan logika LangVer (lewati ekstensi baru, vs. pertimbangkan dan laporkan saat dipilih) (jawaban: ikat tanpa syarat dan laporkan kesalahan LangVer kecuali untuk metode ekstensi instans, LDM 2025-06-11)
  • Apakah partial diperlukan untuk blok ekstensi yang bergabung dan memiliki komentar dokumen yang digabungkan? (jawaban: komentar dokumen digabungkan secara diam-diam ketika blok digabungkan, tidak partial diperlukan, dikonfirmasi melalui email 2025-09-03)
  • Konfirmasikan bahwa anggota tidak boleh diberi nama sama dengan tipe yang mengandung atau tipe yang diperluas. (jawaban: ya, dikonfirmasi melalui email 2025-09-03)

Kunjungi kembali aturan pengelompokan/konflik sehubungan dengan masalah portabilitas: https://github.com/dotnet/roslyn/issues/79043

(jawaban: skenario ini diselesaikan sebagai bagian dari desain metadata baru dengan nama jenis berbasis konten, itu diizinkan)

Logika saat ini adalah mengelompokkan blok ekstensi yang memiliki jenis penerima yang sama. Ini tidak memperhitungkan batasan. Ini menyebabkan masalah portabilitas dengan skenario ini:

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

Proposal ini adalah menggunakan logika pengelompokan yang sama dengan yang direncanakan untuk desain tipe pengelompokan ekstensi, yaitu untuk memperhitungkan batasan pada tingkat CLR (yakni mengabaikan 'notnull', nama tupel, dan anotasi kenullanan).

Haruskah refness dikodekan dalam nama tipe pengelompokan?

  • Tinjau proposal yang ref tidak disertakan dalam nama jenis pengelompokan ekstensi (perlu diskusi lebih lanjut setelah WG mengunjungi kembali aturan pengelompokan/konflik, LDM 2025-06-23) (jawaban: dikonfirmasi melalui email 2025-09-03)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

Ini dipancarkan sebagai:

public static class E
{
  public static class <>ExtensionTypeXYZ
  {
    .. marker method ...
    void M()
  }
}

Dan referensi CREF pihak ketiga untuk E.extension(ref int).M dihasilkan sebagai M:E.<>ExtensionGroupingTypeXYZ.M() Jika ref dihapus atau ditambahkan ke parameter ekstensi, kemungkinan kita tidak ingin CREF rusak.

Kami tidak terlalu peduli dengan skenario ini, karena penggunaan apa pun sebagai ekstensi akan menjadi ambiguitas:

public static class E
{
  extension(ref int)
    static void M()
  extension(int)
    static void M()
}

Tetapi kami peduli dengan skenario ini (untuk portabilitas dan kegunaan), dan ini harus bekerja dengan desain metadata yang diusulkan setelah kami menyesuaikan aturan konflik:

static class E
{
   extension<T>(ref T) where T : struct
      void M()
   extension<T>(T) where T : class
      void M()
}

Tidak memperhitungkan referensialitas memiliki kelemahan, karena kita kehilangan portabilitas dalam skenario ini.

static class E
{
   extension<T>(ref T)
      void M()
   extension<T>(T)
      void M()
}
// portability issue: since we're grouping without accounting for refness, the emitted extension members conflict (not implementation members). Mitigation: keep as classic extensions or split to another static class

nama

  • Haruskah kita melarang properti ekstensi dalam nameof seperti yang kita lakukan metode ekstensi klasik dan baru? (jawaban: kami ingin menggunakan 'nameof(EnclosingStaticClass.ExtensionMember). Membutuhkan desain, kemungkinan keputusan untuk menunda dari .NET 10. LDM 2025-06-11)

konstruksi berbasis pola

Metode

  • Di mana metode ekstensi baru seharusnya diterapkan? (jawaban: tempat yang sama di mana metode ekstensi klasik mulai dimainkan, LDM 2025-05-05)

Ini termasuk:

  • GetEnumerator / GetAsyncEnumerator di foreach
  • Deconstruct dalam dekonstruksi, dalam pola posisional dan foreach
  • Add pada inisialisasi koleksi
  • GetPinnableReference di fixed
  • GetAwaiter di await

Ini mengecualikan:

  • Dispose / DisposeAsync di using dan foreach
  • MoveNext / MoveNextAsync di foreach
  • Slice dan int pengindeks dalam pengindeks implisit (dan mungkin pola daftar?)
  • GetResult di await

Properti dan pengindeks

  • Di mana properti ekstensi dan pengindeks seharusnya digunakan? (jawaban: mari kita mulai dengan empat, LDM 2025-05-05)

Kami akan menyertakan:

  • penginisialisasi objek: new C() { ExtensionProperty = ... }
  • intializer kamus: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • pola properti: x is { ExtensionProperty: ... }

Kami akan mengecualikan:

  • Current di foreach
  • IsCompleted di await
  • Count / Length properti dan pengindeks dalam pola daftar
  • Count / Length properti dan pengindeks dalam pengindeks implisit
Properti yang dikembalikan delegasi
  • Pastikan bahwa properti ekstensi dari bentuk ini hanya berfungsi dalam kueri LINQ, untuk mengikuti cara kerja properti instans. (jawaban: masuk akal, LDM 2025-04-06)
Daftar dan pola penyebaran
  • Konfirmasikan bahwa pengindeks ekstensi Index/Range harus bermain dalam pola daftar (jawaban: tidak relevan untuk C# 14)
Mencermati ulang di mana Count/Length properti ekstensi memiliki peran

Ekspresi Koleksi

  • Ekstensi Add berfungsi
  • Ekstensi GetEnumerator berfungsi untuk penyebaran
  • Ekstensi GetEnumerator tidak memengaruhi penentuan jenis elemen (harus berupa instans)
  • Metode ekstensi statis Create tidak boleh dihitung sebagai metode pembuatan yang diberkati
  • Haruskah sifat-sifat ekstensi yang dapat dihitung memengaruhi ekspresi kumpulan?

params Koleksi

  • Ekstensi Add tidak memengaruhi tipe yang diizinkan dengan params

ungkapan dalam kamus

  • Pastikan bahwa pengindeks ekstensi tidak digunakan dalam ekspresi kamus, karena kehadiran pengindeks adalah bagian integral yang mendefinisikan jenis kamus. (jawaban: tidak relevan untuk C# 14)

extern

Skema penamaan/penomoran untuk jenis ekstensi

Masalah
Sistem penomoran saat ini menyebabkan masalah dengan validasi API publik yang memastikan bahwa API publik cocok antara rakitan khusus referensi dan rakitan implementasi.

Haruskah kita membuat salah satu perubahan berikut? (jawaban: kami mengadopsi skema penamaan berbasis konten untuk meningkatkan stabilitas API publik, dan alat masih perlu diperbarui untuk memperhitungkan metode penanda)

  1. sesuaikan alat
  2. gunakan beberapa skema penamaan berbasis konten (TBD)
  3. biarkan nama dikontrol melalui beberapa sintaks

Metode Cast dari ekstensi generik baru masih belum bisa berfungsi di LINQ

Masalah
Dalam desain peran/ekstensi sebelumnya, dimungkinkan untuk hanya menentukan argumen jenis metode secara eksplisit.
Tetapi sekarang kita berfokus pada transisi mulus dari metode ekstensi klasik, semua argumen tipe harus diberikan secara eksplisit.
Ini gagal mengatasi masalah penggunaan metode Cast dari ekstensi di LINQ.

Haruskah kita membuat perubahan pada fitur ekstensi untuk mengakomodasi skenario ini? (jawaban: tidak, ini tidak menyebabkan kami meninjau ulang desain resolusi ekstensi, LDM 2025-05-05)

Membatasi parameter ekstensi pada anggota ekstensi

Haruskah kita mengizinkan hal berikut? (jawaban: tidak, ini dapat ditambahkan nanti)

static class E
{
    extension<T>(T t)
    {
        public void M<U>(U u) where T : C<U>  { } // error: 'E.extension<T>(T).M<U>(U)' does not define type parameter 'T'
    }
}

public class C<T> { }

Keterbatalan

  • Konfirmasi desain saat ini, yaitu portabilitas/kompatibilitas maksimal (jawaban: ya, LDM 2025-04-17)
    extension([System.Diagnostics.CodeAnalysis.DoesNotReturnIf(false)] bool b)
    {
        public void AssertTrue() => throw null!;
    }
    extension([System.Diagnostics.CodeAnalysis.NotNullIfNotNull("o")] ref int? i)
    {
        public void M(object? o)  => throw null!;
    }

Metainformasi

  • Haruskah metode skeleton melempar NotSupportedException atau pengecualian standar lainnya (saat ini kita menggunakan throw null;)? (jawaban: ya, LDM 2025-04-17)
  • Haruskah kita menerima lebih dari satu parameter dalam metode penanda dalam metadata (jika versi baru menambahkan lebih banyak info)? (jawaban: kita dapat tetap ketat, LDM 2025-04-17)
  • Haruskah penanda ekstensi atau metode implementasi yang dapat diucapkan ditandai dengan nama khusus? (jawaban: metode penanda harus ditandai dengan nama khusus dan kita harus memeriksanya, tetapi bukan metode implementasi, LDM 2025-04-17)
  • Haruskah kita menambahkan [Extension] atribut pada kelas statis bahkan ketika tidak ada metode ekstensi instans di dalamnya? (jawaban: ya, LDM 2025-03-10)
  • Konfirmasi bahwa kita perlu menambahkan [Extension] atribut ke getter dan setter implementasi juga. (jawaban: tidak, LDM 2025-03-10)
  • Konfirmasikan bahwa jenis ekstensi harus ditandai dengan nama khusus dan kompilator akan memerlukan bendera ini dalam metadata (ini adalah perubahan signifikan dari pratinjau) (jawaban: disetujui, LDM 2025-06-23)

skenario pabrik statis

  • Apa aturan konflik untuk metode statis? (jawaban: gunakan aturan C# yang ada untuk tipe statis yang melingkupi, tanpa relaksasi, LDM 2025-03-17)

Pemeriksaan

  • Bagaimana cara mengatasi pemanggilan metode instans sekarang setelah kita memiliki nama implementasi yang dapat diucapkan? Kami lebih suka metode kerangka dibandingkan dengan metode implementasinya.
  • Bagaimana cara mengatasi metode ekstensi statis? (jawaban: sama seperti metode ekstensi instance, LDM 2025-03-03)
  • Bagaimana cara menentukan sifat-sifat? (dijawab secara garis besar LDM 2025-03-03, tetapi perlu tindak lanjut untuk perbaikan lebih lanjut)
  • Aturan cakupan dan bayangan untuk parameter ekstensi dan parameter jenis (jawaban: dalam cakupan blok ekstensi, bayangan tidak diizinkan, LDM 2025-03-10)
  • Bagaimana ORPA dapat diterapkan pada metode ekstensi baru? (jawaban: perlakukan blok ekstensi sebagai transparan, "jenis yang mengandung" untuk ORPA adalah kelas statis yang melingkupi, LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • Haruskah ORPA berlaku untuk properti ekstensi baru? (jawaban: ya dan ORPA harus disalin ke metode implementasi, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Bagaimana cara mempertimbangkan kembali aturan resolusi ekstensi klasik? Apakah kita
    1. perbarui standar untuk metode ekstensi klasik, dan gunakan itu untuk juga menjelaskan metode ekstensi baru,
    2. pertahankan bahasa yang ada untuk metode ekstensi klasik, gunakan itu untuk juga menggambarkan metode ekstensi baru, tetapi memiliki penyimpangan spesifikasi yang diketahui untuk keduanya,
    3. pertahankan bahasa yang ada untuk metode ekstensi klasik, tetapi gunakan bahasa yang berbeda untuk metode ekstensi baru, dan hanya memiliki penyimpangan spesifikasi yang diketahui untuk metode ekstensi klasik?
  • Konfirmasikan bahwa kami ingin melarang argumen jenis eksplisit pada akses properti (jawaban: tidak ada akses properti dengan argumen jenis eksplisit, yang dibahas dalam WG)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Konfirmasikan bahwa kami ingin aturan penerapan yang lebih baik diterapkan bahkan ketika penerima adalah tipe (jawaban: parameter ekstensi yang hanya untuk tipe harus dipertimbangkan saat menyelesaikan anggota ekstensi statis, LDM 2025-06-23)
int.M();

static class E1
{
    extension(int)
    {
        public static void M() { }
    }
}
static class E2
{
    extension(in int i)
    {
        public static void M() => throw null;
    }
}
  • Pastikan bahwa kita menerima adanya ambiguitas ketika baik metode maupun properti berlaku (jawaban: kita harus merancang proposal untuk meningkatkan dari status quo, mengeluarkan .NET 10 dari rencana, LDM 2025-06-23)
  • Konfirmasikan bahwa kami tidak menginginkan beberapa kesempurnaan di semua anggota sebelum kami menentukan jenis anggota yang menang (jawaban: melepas .NET 10, WG 2025-07-02)
string s = null;
s.M(); // error

static class E
{
    extension(string s)
    {
        public System.Action M => throw null;
    }
    extension(object o)
    {
        public string M() => throw null;
    }
}
  • Apakah kita memiliki penerima implisit dalam deklarasi ekstensi? (jawaban: tidak, sebelumnya dibahas di LDM)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Haruskah kita mengizinkan pencarian pada parameter jenis? (diskusi) (jawaban: tidak, kita akan menunggu umpan balik, LDM 2025-04-16)

Aksesibilitas

  • Apa arti aksesibilitas dalam deklarasi ekstensi? (jawaban: deklarasi ekstensi tidak dihitung sebagai cakupan aksesibilitas, LDM 2025-03-17)
  • Haruskah kita menerapkan pemeriksaan "aksesibilitas yang tidak konsisten" pada parameter penerima bahkan untuk anggota statis? (jawaban: ya, LDM 2025-04-17)
public static class Extensions
{
    extension(PrivateType p)
    {
        // We report inconsistent accessibility error, 
        //   because we generate a `public static void M(PrivateType p)` implementation in enclosing type
        public void M() { } 

        public static void M2() { } // should we also report here, even though not technically necessary?
    }

    private class PrivateType { }
}

Validasi ekstensi deklarasi

  • Haruskah kita melonggarkan validasi parameter jenis (kemampuan inferensi: semua parameter jenis harus muncul dalam jenis parameter ekstensi) ketika hanya ada metode? (jawaban: ya, LDM 2025-04-06) Ini akan memungkinkan pemindahan 100% dari metode ekstensi klasik.
    Jika Anda memiliki TResult M<TResult, TSource>(this TSource source), Anda bisa mengalihkannya sebagai extension<TResult, TSource>(TSource source) { TResult M() ... }.

  • Konfirmasikan apakah aksesor yang hanya dapat diinisialisasi harus diizinkan dalam ekstensi (jawaban: boleh dilarang sementara waktu, LDM 2025-04-17)

  • Apakah perbedaan tunggal dalam hal ref-ness penerima diizinkan extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }? (jawaban: tidak, pertahankan aturan spesifikasi, LDM 2025-03-24)

  • Haruskah kita mengeluh tentang konflik seperti ini extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }? (jawaban: ya, pertahankan aturan spesifikasi, LDM 2025-03-24)

  • Haruskah kita mengeluh tentang konflik antara metode kerangka yang tidak bertentangan antara metode implementasi? (jawaban: ya, pertahankan aturan spesifikasi, LDM 2025-03-24)

static class E
{
    extension(object)
    {
        public void Method() {  }
        public static void Method() { }
    }
}

Aturan konflik saat ini adalah: 1. periksa tidak ada konflik dalam ekstensi serupa menggunakan aturan kelas/struktur, 2. periksa tidak ada konflik antara metode implementasi di berbagai deklarasi ekstensi.

  • Apakah kita perlu bagian pertama dari aturan? (jawaban: ya, kami menyimpan struktur ini karena membantu konsumsi API, LDM 2025-03-24)

Dokumen XML

  • Apakah parameter penerima paramref didukung pada anggota ekstensi? Bahkan ketika ada gangguan statis? Bagaimana dikodekan dalam output? Kemungkinan cara <paramref name="..."/> standar akan bekerja untuk manusia, tetapi ada risiko bahwa beberapa alat yang ada tidak akan senang jika tidak menemukannya di antara parameter pada API. (jawaban: ya, "paramref" untuk parameter ekstensi diizinkan pada anggota ekstensi, LDM 2025-05-05)
  • Apakah kita harus menyalin komentar dokumen ke metode implementasi dengan nama yang dapat diucapkan? (jawaban: tidak ada penyalinan, LDM 2025-05-05)
  • Haruskah <param> elemen yang sesuai dengan parameter penerima disalin dari kontainer ekstensi untuk metode instans? Apakah hal lainnya perlu disalin dari kontainer ke metode implementasi (<typeparam> dll.)? (jawaban: tidak ada penyalinan, LDM 2025-05-05)
  • Haruskah <param> parameter ekstensi diizinkan pada anggota ekstensi sebagai penggantian? (jawaban: tidak, untuk saat ini, LDM 2025-05-05)
  • Apakah ringkasan pada blok ekstensi akan muncul di mana saja?

CREF

  • Konfirmasi sintaks (dengan jawaban: proposal bagus, LDM 2025-06-09)
  • Apakah mungkin untuk merujuk ke blok ekstensi (E.extension(int))? (jawaban: tidak, LDM 2025-06-09)
  • Apakah mungkin untuk merujuk ke anggota menggunakan sintaksis yang tidak memenuhi syarat: extension(int).Member? (jawaban: ya, LDM 2025-06-09)
  • Haruskah kita menggunakan karakter yang berbeda untuk nama yang tidak dapat disebutkan, untuk menghindari penghindaran XML? (jawaban: tangguhkan ke WG, LDM 2025-06-09)
  • Konfirmasikan tidak apa-apa bahwa referensi ke kerangka dan metode implementasi dimungkinkan: E.M vs. E.extension(int).M. Keduanya tampaknya diperlukan (properti ekstensi dan portabilitas metode ekstensi klasik). (jawaban: ya, LDM 2025-06-09)
  • Apakah nama metadata ekstensi bermasalah untuk membuat versi dokumen? (jawaban: ya, kita akan menjauh dari ordinal dan menggunakan skema penamaan stabil berbasis konten)

Menambahkan dukungan untuk lebih banyak jenis anggota

Kami tidak perlu mengimplementasikan semua desain ini sekaligus; kita bisa mendekati satu atau beberapa jenis anggota pada satu waktu. Berdasarkan skenario yang diketahui di pustaka inti kami, kita harus bekerja dalam urutan berikut:

  1. Properti dan metode (instans dan statis)
  2. Para Operator
  3. Pengindeks (instans dan statis, dapat dilakukan secara oportunistik pada titik sebelumnya)
  4. Ada lagi

Seberapa banyak kita ingin mempersiapkan desain di awal untuk jenis anggota yang lain?

extension_member_declaration // add
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | operator_declaration
    | constructor_declaration
    | finalizer_declaration
    | static_constructor_declaration
    | type_declaration
    ;

Jenis berlapis

Jika kita memilih untuk maju dengan jenis berlapis ekstensi, berikut adalah beberapa catatan dari diskusi sebelumnya:

  • Akan ada konflik jika dua deklarasi ekstensi mendeklarasikan jenis ekstensi berlapis dengan nama dan aritas yang sama. Kami tidak memiliki solusi untuk mewakili ini dalam metadata.
  • Pendekatan kasar yang kami bahas untuk metadata:
    1. kami akan memancarkan jenis kerangka berlapis dengan parameter jenis asli dan tanpa anggota
    2. kami akan memancarkan jenis berlapis implementasi dengan parameter jenis yang telah ditentukan sebelumnya dari deklarasi ekstensi dan semua implementasi anggota saat muncul di sumber (referensi modulo ke parameter jenis)

Konstruktor

Konstruktor umumnya digambarkan sebagai anggota instans di C#, karena isinya memiliki akses ke nilai yang baru dibuat melalui this kata kunci. Namun, pendekatan berbasis parameter untuk anggota ekstensi instans tidak cocok, karena tidak ada nilai sebelumnya yang bisa diteruskan sebagai parameter.

Sebaliknya, konstruktor ekstensi bekerja lebih seperti metode pabrik statis. Mereka dianggap sebagai anggota statis dalam arti bahwa mereka tidak bergantung pada nama parameter penerima. Tubuh mereka perlu secara eksplisit membuat dan mengembalikan hasil konstruksi. Anggota masih dinyatakan dengan sintaksis konstruktor, tetapi tidak diperbolehkan memiliki inisialisasi this atau base dan tidak bergantung pada jenis penerima yang memiliki konstruktor yang dapat diakses.

Ini juga berarti bahwa konstruktor ekstensi dapat dideklarasikan untuk jenis yang tidak memiliki konstruktornya sendiri, seperti antarmuka dan jenis enum:

public static class Enumerable
{
    extension(IEnumerable<int>)
    {
        public static IEnumerable(int start, int count) => Range(start, count);
    }
    public static IEnumerable<int> Range(int start, int count) { ... } 
}

Memungkinkan:

var range = new IEnumerable<int>(1, 100);

Formulir yang lebih pendek

Desain yang diusulkan menghindari pengulangan spesifikasi penerima per anggota, tetapi malah anggota ekstensi disarangkan dua tingkat dalam kelas statis serta deklarasi ekstensi. Kemungkinan akan umum bagi kelas statis untuk hanya berisi satu deklarasi ekstensi atau bagi deklarasi ekstensi untuk hanya berisi satu anggota, dan tampaknya masuk akal bagi kami untuk memungkinkan pemendekan sintaksis dari kasus-kasus tersebut.

Gabungkan kelas statis dan deklarasi ekstensi:

public static class EmptyExtensions : extension(IEnumerable source)
{
    public bool IsEmpty => !source.GetEnumerator().MoveNext();
}

Ini akhirnya terlihat lebih seperti apa yang telah kami sebut pendekatan "berbasis jenis", di mana kontainer untuk anggota ekstensi itu sendiri diberi nama.

Gabungkan deklarasi ekstensi dan anggota ekstensi:

public static class Bits
{
    extension(ref ulong bits) public bool this[int index]
    {
        get => (bits & Mask(index)) != 0;
        set => bits = value ? bits | Mask(index) : bits & ~Mask(index);
    }
    static ulong Mask(int index) => 1ul << index;
}
 
public static class Enumerable
{
    extension<TSource>(IEnumerable<TSource> source) public IEnumerable<TSource> Where(Func<TSource, bool> predicate) { ... }
}

Ini akhirnya terlihat lebih seperti apa yang telah kami sebut pendekatan "berbasis anggota", di mana setiap anggota ekstensi berisi spesifikasi penerimanya sendiri.