Aracılığıyla paylaş


Eklenti üyeleri

Uyarı

Bu makale bir özellik belirtimidir. Belirtim, özelliğin tasarım belgesi olarak görev alır. Önerilen belirtim değişikliklerini ve özelliğin tasarımı ve geliştirilmesi sırasında gereken bilgileri içerir. Bu makaleler, önerilen belirtim değişiklikleri son haline getirilene ve geçerli ECMA belirtimine dahil edilene kadar yayımlanır.

Özellik belirtimi ile tamamlanan uygulama arasında bazı tutarsızlıklar olabilir. Bu farklılıklar ilgili dil tasarım toplantısı (LDM) notlarında yakalanır.

Özellik belirtimlerini C# dil standardına benimseme işlemi hakkında daha fazla bilgi edinmek içinbelirtimleri makalesinde bulabilirsiniz.

Şampiyonluk sorunu: https://github.com/dotnet/csharplang/issues/8697

Beyanname

Sözdizimi

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?
    ;

Uzantı bildirimleri yalnızca genel olmayan, iç içe olmayan statik sınıflarda bildirilmelidir.
Bir türün extension olarak adlandırılması hatadır.

Kapsam belirleme kuralları

Bir uzantı bildiriminin tür parametreleri ve alıcı parametresi, uzantı bildiriminin gövdesi kapsamındadır. Statik bir üyenin içinden, bir nameof ifadesi dışında, alıcı parametresine başvurmak bir hatadır. Üyelerin, bir uzantı bildiriminin tür parametresi veya alıcı parametresiyle aynı ada sahip tür parametrelerini veya diğer parametrelerini (yerel değişkenler ve yerel işlevler de dahil olmak üzere doğrudan üye gövdesi içinde) bildirmesi bir hatadır.

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`
    }
}

Üyelerin, iç uzantı bildiriminin tür parametreleri veya alıcı parametresiyle aynı ismi taşımaları bir hata değildir. Üye adları, uzantı bildiriminin içinden basit bir ad aramasında doğrudan bulunmaz; arama, bu nedenle üye yerine bu adın tür parametresini veya alıcı parametresini bulur.

Üyeler, doğrudan kapsayan statik sınıfta bildirilen statik yöntemleri ortaya çıkar ve bunlar basit ad arama yoluyla bulunabilir; ancak, ilk olarak aynı ada sahip bir uzantı bildirim türü parametresi veya alıcı parametresi bulunur.

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
    }
}

Uzantı kapsayıcıları olarak statik sınıflar

Uzantılar, günümüzdeki uzantı yöntemleri gibi üst düzey genel olmayan statik sınıflar içinde bildirilir ve bu nedenle klasik uzantı yöntemleri ve uzantı olmayan statik üyeler ile birlikte bulunabilir:

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) { ... } 
}

Uzantı bildirimleri

Uzantı bildirimi anonimdir ve ilişkili tür parametreleri ve kısıtlamalarıyla birlikte bir alıcı belirtimi ve ardından bir uzantı üyesi bildirimleri kümesi sağlar. Alıcı belirtimi bir parametre biçiminde olabilir veya - yalnızca statik uzantı üyeleri bildirilirse - bir tür:

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) { ... }
    }
}

Alıcı belirtimindeki türe alıcı türü , varsa parametre adı ise alıcı parametresi olarak adlandırılır.

Alıcı parametresi adlandırılmışsa, alıcı türü statik olmayabilir.
Alıcı parametresinin adı belirtilmemişse değiştiricilere sahip olması yasaktır; aksi takdirde yalnızca aşağıdaki ve scoped belirtilen başvuru değiştiricilerine izin verilir.
Alıcı parametresi, klasik uzantı yönteminin ilk parametresiyle aynı kısıtlamaları taşır.
[EnumeratorCancellation] özniteliği alıcı parametresine yerleştirilirse yoksayılır.

Eklenti üyeleri

Uzantı üyesi bildirimleri, sınıf ve yapı bildirimlerinde (oluşturucular dışında) karşılık gelen örnek ve statik üyelerle aynı olur. Örnek üyeler, alıcı parametre adı ile alıcıyı belirtir:

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

Kapsayan uzantı bildirimi bir alıcı parametresi belirtmiyorsa örnek uzantısı üyesini belirtmek bir hatadır:

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

Uzantı bildiriminin bir üyesinde aşağıdaki değiştiricilerin belirtilmesi hatadır: abstract, virtual, override, new, sealed, , partialve protected (ve ilgili erişilebilirlik değiştiricileri).
Uzantı bildiriminin bir üyesinde readonly değiştiricinin belirtilmesi bir hatadır.
Uzantı bildirimlerindeki özelliklerin init erişimcileri olmayabilir.
Alıcı parametresi adlandırılmamışsa örnek üyelerine izin verilmez.

Tüm üyeler statik kapsayan sınıfın adından ve varsa genişletilmiş türün adından farklı adlara sahip olmalıdır.

Uzantı üyesini özniteliğiyle [ModuleInitializer] süslemek bir hatadır.

Referanslık

Varsayılan olarak, alıcı diğer parametrelerde olduğu gibi örnek genişletme üyelerine değer olarak iletilir. Ancak, parametre formundaki bir uzantı bildirimi alıcısının türünün bir değer türü olduğu bilindiği sürece, ref, ref readonly ve in belirtebilir.

Boş olabilirlik ve öznitelikler

Alıcı türleri, null atanabilir referans türleri olabilir veya içerebilir ve parametre olarak verilen alıcı belirtimleri öznitelikleri belirtebilir.

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);
    }
}

Klasik uzantı yöntemleriyle uyumluluk

Örnek uzantısı yöntemleri, klasik uzantı yöntemleri tarafından üretilenlerle eşleşen yapıtlar oluşturur.

Özellikle oluşturulan statik yöntem, bildirilen uzantı yönteminin özniteliklerine, değiştiricilerine ve adına ek olarak uzantı bildiriminden ve yöntem bildiriminden birleştirilmiş tür parametre listesine, parametre listesine ve kısıtlamalar listesine sahiptir:

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)  { ... }
    }
}

Oluşturur:

[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)  { ... }
}

Operatörler

Uzantı işleçlerinin açıkça belirtilmiş işlenen türleri olsa da, yine de bir uzantı bildirimi içinde tanıtılması gerekir.

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) { ... }
    }
}

Bu, tür parametrelerinin bildirilmesine ve çıkarılmasına olanak tanır ve normal kullanıcı tanımlı işlecin işlenen türlerinden birinde nasıl bildirilmesi gerektiğine benzerdir.

Denetleniyor

Çıkarılabilirlik: Yöntem dışı her uzantı üyesi için uzantı bloğunun tüm tür parametreleri, uzantıdan ve üyeden gelen birleştirilmiş parametre kümesinde kullanılmalıdır.

Benzersizlik: Belirli bir kapsayan statik sınıf içinde, aynı alıcı türüne (modül kimliği dönüştürme ve tür parametresi adı değiştirme) sahip uzantı üyesi bildirimleri kümesi, bir sınıf veya yapı bildirimi içindeki üyelere benzer tek bir bildirim alanı olarak değerlendirilir ve benzersizlik konusunda aynı kurallara tabidir.

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
    }
}

Bu benzersizlik kuralının uygulaması, aynı statik sınıf içindeki klasik uzantı yöntemlerini içerir. Uzantı bildirimleri içindeki yöntemlerle karşılaştırma amacıyla parametresi, this alıcı türünde belirtilen tüm tür parametreleriyle birlikte bir alıcı belirtimi olarak değerlendirilir ve yöntem imzası için kalan tür parametreleri ve yöntem parametreleri kullanılır:

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

Tüketim

Bir uzantı üyesi arandığında, -using içeri aktarılan statik sınıflar içindeki tüm uzantı bildirimleri, alıcı türünden bağımsız olarak üyelerini aday olarak sunar. Yalnızca çözümlemenin bir parçası olarak uyumsuz alıcı türlerine sahip adaylar elenir.
Bağımsız değişkenlerin türü (gerçek alıcı dahil) ile herhangi bir tür parametresi (uzantı bildirimindeki ve uzantı üyesi bildirimindeki parametreleri birleştirerek) arasında tam bir genel tür çıkarımı denenir.
Belirtilmiş tür argümanları sağlandığında, uzantı bildiriminin ve uzantı üyesi bildiriminin tür parametrelerini yerine koymak için kullanılırlar.

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) { ... }
    }
}

Klasik uzantı yöntemlerine benzer şekilde, yayılan uygulama yöntemleri statik olarak çağrılabilir.
Bu, derleyicinin aynı ada ve parametre sayısına sahip uzantı üyeleri arasında ayrım yapmasını sağlar.

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;
    }
}

Statik genişletme yöntemleri, örnek genişletme yöntemleri gibi çözümlenecek (alıcı türü için ek bir bağımsız değişken kullanmayı düşüneceğiz).
Uzantı özellikleri, tek bir parametre (alıcı parametresi) ve tek bir bağımsız değişken (gerçek alıcı değeri) ile uzantı yöntemleri gibi çözümlenir.

using static Yönerge -leri

using_static_directive, tür bildirimindeki uzantı bloklarının üyelerini uzantı erişimi için kullanılabilir hale getirir.

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 { } 

Daha önce olduğu gibi, verilen türün bildiriminde doğrudan yer alan erişilebilir statik üyelere (uzantı yöntemleri dışında) doğrudan başvurulabilir.
Bu, uygulama yöntemlerinin (uzantı yöntemleri dışındaki) doğrudan statik yöntemler olarak kullanılabileceğini gösterir:

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; }
    }
}

bir using_static_directive hala doğrudan statik yöntemler olarak uzantı yöntemlerini içeri aktarmaz, bu nedenle statik olmayan uzantı yöntemleri için uygulama yöntemi doğrudan statik yöntem olarak çağrılamaz.

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() { }
    }
}

AşırıYüklenmeÇözümÖnceliğiNiteliği

Kapsayan statik sınıf içindeki uzantı üyeleri ORPA değerlerine göre öncelik belirlemeye tabidir. Kapsayan statik sınıf, ORPA kurallarının dikkate aldığı "içeren tür" olarak kabul edilir.
Uzantı özelliğinde bulunan herhangi bir ORPA özniteliği, özelliğin erişimcilerinin uygulama yöntemlerine kopyalanır, böylece bu erişimciler kesinleştirme söz dizimi aracılığıyla kullanıldığında öncelik belirlemeye uyulur.

Giriş noktaları

Uzantı bloklarının yöntemleri giriş noktası adayı olarak nitelenmez (bkz. "7.1 Uygulama başlatma"). Not: Uygulama yöntemi hala bir aday olabilir.

Düşürme

Uzantı bildirimleri için indirgeme stratejisi, dil düzeyinde bir karar değildir. Ancak, dil semantiğini uygulamanın ötesinde belirli gereksinimleri karşılaması gerekir:

  • Oluşturulan türlerin, üyelerin ve meta verilerin biçimi, diğer derleyicilerin kullanabilmesi ve oluşturabilmesi için her durumda açıkça belirtilmelidir.
  • Oluşturulan yapıtlar kararlı olmalıdır; daha sonra yapılan makul değişiklikler, önceki sürümlerde derlenen tüketicileri bozmamalıdır.

Uygulama ilerledikçe bu gereksinimlerin daha fazla iyileştirilmesi gerekir ve makul bir uygulama yaklaşımına olanak sağlamak için istisnai durumlarda değiştirilmesi veya ödün verilmesi gerekebilir.

Bildirimler için meta veriler

Hedefler

Aşağıdaki tasarım şunları sağlar:

  • meta veriler aracılığıyla uzantı bildirimi simgelerini yuvarlama (tam ve başvuru derlemeleri),
  • uzantı üyelerine kararlı başvurular (xml belgeleri),
  • yayılan adların yerel olarak belirlenmesi (EnC için yararlıdır),
  • açık API izleme.

Xml belgeleri için, uzantı üyesinin docID değeri meta verilerdeki uzantı üyesinin docID değeridir. Örneğin, içinde cref="Extension.extension(object).M(int)" kullanılan docID değeridir M:Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) ve bu docID, uzantı bloklarının yeniden derlemeleri ve yeniden sıralamaları arasında kararlıdır. İdeal olarak, uzantı bloğu üzerindeki kısıtlamalar değiştiğinde de kararlı kalırdı, ancak üye çakışmaları için dil tasarımı üzerinde zarar verici bir etkisi olmadan bunu başarabilecek bir tasarım bulamadık.

EnC için, değiştirilmiş bir uzantı üyesine bakarak, güncellenmiş uzantı üyesinin meta verilerde nerede yer aldığını yerel olarak bilmek yararlıdır.

Genel API izleme için daha kararlı adlar gürültüyü azaltır. Ancak teknik olarak uzantı gruplandırma türü adları bu tür senaryolarda devreye girmemelidir. uzantı üyesine Mbakarken, uzantı gruplandırma türünün adının ne olduğu önemli değildir, önemli olan ait olduğu uzantı bloğunun imzasıdır. Genel API imzası Extension.<>E__ExtensionGroupingTypeNameForObject.M(System.Int32) olarak değil, Extension.extension(object).M(int) olarak görülmelidir. Başka bir deyişle, uzantı üyeleri iki tür parametresi kümesine ve iki parametre kümesine sahip olarak görülmelidir.

Genel Bakış

Uzantı blokları CLR düzeyi imzalarına göre gruplandırılır. Her CLR denklik grubu, içerik tabanlı bir ada sahip bir uzantı gruplandırma türü olarak yayılır. Bir CLR eşdeğerlik grubu içindeki uzantı blokları daha sonra C# eşdeğerliğine göre alt gruplanır. Her C# eşdeğerlik grubu, ilgili uzantı gruplandırma türüne iç içe yerleştirilmiş, içerik tabanlı bir ada sahip bir uzantı işaretçisi türü olarak yayılır. Uzantı işaretçisi türü, bir uzantı parametresini kodlayan tek bir uzantı işaretçisi yöntemi içerir. Uzantı işaretçisi türünü içeren uzantı işaretçisi yöntemi, bir uzantı bloğunun imzasını tam bir sadakatle kodlar. Her uzantı üyesinin bildirimi, doğru uzantı grubu türünde tanımlanır, bir öznitelik aracılığıyla adıyla bir uzantı işaretçisi türüne geri başvuru yapar ve değiştirilmiş metot imzasına sahip üst düzey statik uygulama yöntemiyle birlikte gelir.

Meta veri kodlamaya şemalı bir genel bakış aşağıdadır:

[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 ...
}

Kapsayan statik sınıf bir [Extension] öznitelikle birlikte gönderilir.

CLR düzeyi imza ile C#düzeyi imza karşılaştırması

Uzantı bloğunun CLR düzeyindeki imzası şu unsurların sonucudur:

  • Tür parametre adlarını T0, T1 gibi normalleştirin.
  • öznitelikleri kaldırma
  • parametre adını silme
  • parametre değiştiricilerini silme (, , ref, in...gibiscoped)
  • tuple (tanımlama grubu) adlarını silme
  • nullability açıklamalarını silme
  • kısıtlamaları silme notnull

Not: Diğer kısıtlamalar korunur, new(), struct, class, allows ref struct, unmanaged ve tür kısıtlamaları gibi.

Uzantı gruplandırma türleri

Aynı CLR düzeyinde imzaya sahip kaynaktaki her uzantı bloğu kümesi için meta veriye bir uzantı gruplandırma türü yayılır.

  • Adı telaffuz edilemez ve CLR düzeyi imzanın içeriğine göre belirlenir. Aşağıda daha fazla ayrıntı bulabilirsiniz.
  • Tür parametreleri normalleştirilmiş adlara (T0, T1, ...) ve özniteliklere sahip değildir.
  • Halka açık ve mühürlü.
  • specialname bayrağı ve [Extension] özniteliği ile işaretlenir.

Uzantı gruplandırma türünün içerik tabanlı adı CLR düzeyi imzayı temel alır ve aşağıdakileri içerir:

  • Uzantı parametresi türünün tam olarak nitelikli CLR adı.
    • Tür bildiriminde göründükleri sıraya göre, başvuruda bulunulan tür parametre adları T0, T1, vb. olarak normalleştirilir.
    • Tam nitelikli ad, içeren derlemeyi kapsamaz. Türlerin derlemeler arasında taşınması yaygındır ve bu, xml belge referanslarını bozmamalıdır.
  • Tür parametrelerine ilişkin kısıtlamalar, kaynak kodda yeniden sıralandığında ismin değişmeyeceği şekilde eklenip düzenlenir. Özellikle:
    • Tür parametresi kısıtlamaları bildirim sırasına göre listelenir. N. tür parametresinin kısıtlamaları Nth+1 tür parametresinden önce gerçekleşir.
    • Tür kısıtlamaları, tam adlar sıralı olarak karşılaştırılarak sıralanır.
    • Tür dışı kısıtlamalar belirlenimci olarak sıralanır ve herhangi bir belirsizlik veya tür kısıtlamalarıyla çakışmayı önlemek için işlenir.
  • Bu, öznitelikleri içermediğinden, tanımlama grubu adları, null değer alabilme vb. gibi C# diline özgü özellikleri kasıtlı olarak yoksayar...

Not: Adın yeniden derlemeler, yeniden sıralamalar ve C#'a özgü özelliklerin (yani CLR düzeyindeki imzayı etkilemeyen) değişikliklerinde kararlı kalacağı garanti edilir.

Uzantı işaretçi türleri

İşaret türü, uzantı bloklarının C# görünümünün eksiksiz uygunluğunu elde etmek amacıyla içerici gruplandırma türünün (bir uzantı gruplandırma türü) tür parametrelerini yeniden tanımlar.

Kaynaktaki her uzantı bloğu kümesinin meta verilerine aynı C#düzeyi imzaya sahip bir uzantı işaretçisi türü yayılır.

  • Adı okunamaz ve uzantı bloğunun C#düzeyi imzasının içeriğine göre belirlenir. Aşağıda daha fazla ayrıntı bulabilirsiniz.
  • İçeren gruplandırma türünün tür parametrelerini kaynakta bildirilenler (ad ve öznitelikler dahil) olacak şekilde yeniden işler.
  • Genel ve statiktir.
  • Bu specialname bayrak ile işaretlenmiştir.

Uzantı işaretçi türünün içerik tabanlı adı aşağıdakilere dayanır:

  • Tür parametrelerinin adları, uzantı bildiriminde göründükleri sırayla eklenir
  • Tür parametrelerine ait öznitelikler eklenecek ve sıralanacak, böylece bunların kaynak koddaki sıralamasını değiştirmek, adlarını etkilemeyecektir.
  • Tür parametrelerine ilişkin kısıtlamalar, kaynak kodda yeniden sıralandığında ismin değişmeyeceği şekilde eklenip düzenlenir.
  • Genişletilmiş türün tam nitelikli C# adı
    • Bu, boş değer atanabilir açıklamalar, demet adları, vb. gibi öğeleri içerir ...
    • Tam nitelikli ad, içeren derlemeyi kapsamaz.
  • Uzantı parametresinin adı
  • Uzantı parametresinin (ref, ref readonly, , scoped...) belirleyici bir sırada değiştiricileri
  • Uzantı parametresine belirleyici bir sırada uygulanan tüm öznitelikler için tam nitelikli ad ve öznitelik argümanları.

Not: Yeniden derleme ve yeniden sıralama işlemlerinde adın kararlı kalacağı garanti edilir.
Not: Uzantı işaretleyici türleri ve uzantı işaretçisi yöntemleri, başvuru derlemelerinin bir parçası olarak yayılır.

Uzantı işaretçisi yöntemi

İşaretçi yönteminin amacı, uzantı bloğunun uzantı parametresini kodlamaktır. Uzantı işaretçisi türünün bir üyesi olduğundan, uzantı işaretleyici türünün yeniden bildirilen tür parametrelerine başvurabilir.

Her uzantı işaretçisi türü, uzantı işaretçisi yöntemi olan tek bir yöntem içerir.

  • Statik, genel olmayan ve 'void' dönüşlüdür, <Extension>$ olarak adlandırılır.
  • Tek parametresi, uzantı parametresinden özniteliklere, başvuruya, türe ve ada sahiptir.
    Uzantı parametresi bir ad belirtmezse, parametre adı boş olur.
  • Bu specialname bayrak ile işaretlenmiştir.

İşaretleyici yönteminin erişilebilirliği, bildirilen uzantı üyeleri arasında en az kısıtlayıcı olan erişilebilirlik olacaktır; eğer hiçbir üye bildirilmemişse private kullanılır.

Eklenti üyeleri

Kaynaktaki bir uzantı bloğundaki yöntem/özellik bildirimleri, meta verilerdeki uzantı gruplandırma türünün üyeleri olarak temsil edilir.

  • Özgün yöntemlerin imzaları korunur (öznitelikler dahil), ancak gövdeleri throw NotImplementedException() ile değiştirilir.
  • Bunlar IL'de başvurulmamalıdır.
  • Yöntemler, özellikler ve erişimcileri, söz konusu üyenin uzantı bloğuna karşılık gelen uzantı göstergesi türünün adına [ExtensionMarkerName("...")] olarak işaretlenir.

Uygulama yöntemleri

Kaynaktaki bir uzantı bloğundaki yöntem/özellik bildirimleri için yöntem gövdeleri, en üst düzey statik sınıfta statik uygulama yöntemleri olarak yayılır.

  • Uygulanan yöntem, özgün yöntemle aynı isme sahiptir.
  • Özgün yöntemin tür parametrelerine (öznitelikler dahil) ekli uzantı bloğundan türetilmiş tür parametrelerine sahiptir.
  • Özgün yöntemle aynı erişilebilirliğe ve özniteliklere sahiptir.
  • Statik bir yöntem uygularsa, aynı parametrelere ve dönüş türüne sahiptir.
  • Bir örnek yöntemi uygularsa, özgün yöntemin imzasına önceden eklenmiş bir parametresi vardır. Bu parametrenin öznitelikleri, başvurusu, türü ve adı, ilgili uzantı bloğunda bildirilen uzantı parametresinden türetilir.
  • Uygulama yöntemlerindeki parametreler, uzantı bloğu yerine uygulama yöntemine ait tür parametrelerine başvurur.
  • Özgün üye sıradan bir örnek yöntemiyse, uygulama yöntemi bir [Extension] öznitelikle işaretlenir.

ExtensionMarkerName özniteliği

ExtensionMarkerNameAttribute türü yalnızca derleyici kullanımı içindir; kaynakta buna izin verilmez. Tür bildirimi, derlemeye henüz dahil değilse derleyici tarafından sentezlenmiştir.

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; }
}

Not: Geleceğe dönük uyumluluk sağlamak için bazı öznitelik hedefleri (uzantı iç içe türleri, uzantı alanları, uzantı olayları) dahil edilse de, uzantı oluşturucular oluşturucu olarak değerlendirilemeyeceğinden, AttributeTargets.Constructor dahil edilmez.

Örnek

Not: Okunabilirlik için örnek için basitleştirilmiş içerik tabanlı adlar kullanırız. Not: C# tür parametresi yeniden bildirimini temsil edemeyeceğinden, meta verileri temsil eden kod geçerli C# kodu değildir.

Aşağıda gruplamanın üye olmadan nasıl çalıştığını gösteren bir örnek verilmişti:

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>
    }
}

şu şekilde yayılır:

[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
}

Üyelerin nasıl yayılıyor olduğunu gösteren bir örnek aşağıda verilmişti:

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() { ... }
}

şu şekilde yayılır:

[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() { ... }
}

Uzantı üyeleri kaynakta her kullanıldığında, bunları uygulama yöntemlerine başvuru olarak yayacağız. Örneğin: bir enumerableOfInt.Method() çağrısı, IEnumerableExtensions.Method<int>(enumerableOfInt)'ye statik bir çağrı olarak iletilir.

XML belgeleri

Uzantı bloğundaki belge açıklamaları işaretçi türü için gösterilir (uzantı bloğunun DocID değeri aşağıdaki örnekte verilmiştir E.<>E__MarkerContentName_For_ExtensionOfT'1 ).
Uzantı parametresine <paramref> ve tür parametrelerine <typeparamref> kullanarak başvurmalarına izin verilir.
Not: Uzantı üyesi üzerinde uzantı parametresini veya tür parametrelerini (<param> ve <typeparam>) belgeleyemeyebilirsiniz.

İki uzantı bloğu tek bir işaretçi türü olarak yayılırsa, belge açıklamaları da birleştirilir.

XML belgelerini kullanan araçlar, uzantı bloğundaki <param> ve <typeparam> elemanlarını uzantı üyelerine gerektiği şekilde kopyalamaktan sorumludur (yani, parametre bilgileri yalnızca örnek üyeleri için kopyalanmalıdır).

<inheritdoc> uygulama yöntemlerinde yayılır ve cref ile ilgili uzantı üyesine atıfta bulunur. Örneğin, bir alıcı için uygulama yöntemi, uzantı özelliğinin belgelerine dayanır. Uzantı üyesi belge açıklamaları içermiyorsa, <inheritdoc> öğesi atlanır.

Uzantı blokları ve uzantı üyeleri için, şu anda aşağıdaki durumlarda uyarmıyoruz:

  • uzantı parametresi belgelenmiştir, ancak uzantı üyesindeki parametreler belgelenmez
  • veya tam tersi
  • veya belgelenmemiş tür parametreleriyle eşdeğer senaryolarda

Örneğin, aşağıdaki belge açıklamaları:

/// <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;
    }
}

aşağıdaki XML'i üretir:

<?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>

CREF referansları

uzantı bloklarını, imzalarıyla ele alınabilecek iç içe türler gibi (tek bir uzantı parametresine sahip bir yöntemmiş gibi) ele akleyebilirsiniz. Örnek: E.extension(ref int).M().

Ancak bir cref, uzantı bloğunun kendisini ele alamaz. E.extension(int) türünde E"extension" adlı bir yönteme başvurabilir.

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
  }
}

Arama, tüm eşleşen uzantı bloklarında arama yapmasıyla bilinir.
Uzantı üyelerine yönelik nitelenmemiş başvurulara izin vermediğimiz için cref de bunlara izin vermiyor.

Söz dizimi şöyle olacaktır:

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
  ;

extension_member_cref'ı en üst düzeyde (extension(int).M) veya başka bir uzantının içinde (E.extension(int).extension(string).M) iç içe kullanmak bir hatadır.

Kritik değişiklikler

Türler ve takma adlar "uzantı" olarak adlandırılmamalıdır.

Açık sorunlar

Belgenin, tamamlanmamış söz dizimi ve alternatif tasarımlar gibi açık sorunlarla ilgili geçici bölümü
  • Uzantı üyesine erişirken alıcı gereksinimlerini ayarlamalı mıydık? (açıklama)
  • extension ve extensions anahtar sözcük olarak onaylayın ( yanıt: extension, LDM 2025-03-24)
  • Reddetmek [ModuleInitializer]istediğimizi onaylayın (yanıt: evet, reddet, LDM 2025-06-11)
  • Uzantı bloklarını giriş noktası adayları olarak atmaya onay verildiğini teyit edin (yanıt: evet, at, LDM 2025-06-11)
  • LangVer mantığını onaylayın (yeni uzantıları atlayın, seçildiğinde bunları göz önünde bulundurun ve raporlayın) (yanıt: koşulsuz bağlama ve örnek uzantısı yöntemleri, LDM 2025-06-11 dışında LangVer hatasını bildirin)
  • Birleştirilen uzantı blokları ve belge açıklamalarının birleştirildiği durumlarda partial gerekli mi olmalıdır? (yanıt: Bloklar birleştirildiğinde, gerekli olmadığında partial , 2025-09-03 e-postasıyla onaylandığında belge açıklamaları sessizce birleştirilir)
  • Üyelerin, içerdikleri veya genişlettikleri türlerden sonra adlandırılmaması gerektiğinden emin olun. (yanıt: evet, e-postayla onaylandı 2025-09-03)

Taşınabilirlik sorunu ışığında gruplandırma/çakışma kurallarını yeniden ziyaret edin: https://github.com/dotnet/roslyn/issues/79043

(yanıt: Bu senaryo, içerik tabanlı tür adlarıyla yeni meta veri tasarımının bir parçası olarak çözümlendi, buna izin verildi)

Geçerli mantık, aynı alıcı türüne sahip uzantı bloklarını gruplandırmaktır. Bu, kısıtlamaları hesaba vermez. Bu, bu senaryoyla ilgili bir taşınabilirlik sorununa neden olur:

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

Öneri, uzantı gruplama türü tasarımı için planladığımız aynı gruplama mantığını kullanmaktır; yani CLR düzeyindeki kısıtlamaları hesaba katmak, fakat notnull, tuple adları ve null atanabilirlik açıklamalarını göz ardı etmektir.

Referanslık, gruplandırma türü adında kodlanmalı mı?

  • Uzantı gruplandırma türü adına dahil edilmeyen ref teklifi gözden geçirin (WG gruplandırma/çakışma kuralları, LDM 2025-06-23 yeniden ziyaret ettikten sonra daha fazla tartışma gerekiyor) (yanıt: 2025-09-03 e-postası ile onaylandı)
public static class E
{
  extension(ref int)
  {
    public static void M()
  }
}

Şu şekilde yayılır:

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

Üçüncü taraf CREF başvuruları E.extension(ref int).M için M:E.<>ExtensionGroupingTypeXYZ.M() olarak verilir. Eğer ref bir uzantı parametresine eklenir veya kaldırılırsa, CREF'nin bozulmasını ya da çalışmamasını istemeyiz.

Uzantı olarak herhangi bir kullanım bir belirsizlik olabileceği için bu senaryoyu pek önemsemiyoruz:

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

Ancak bu senaryoya önem verilmektedir (taşınabilirlik ve kullanışlılık için) ve çakışma kurallarını ayarladıktan sonra önerilen meta veri tasarımıyla bu çalışmalıdır:

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

Refness'i hesaba katmamanın dezavantajı vardır, çünkü bu durumda taşınabilirliği kaybediyoruz.

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

adının

  • Klasik ve yeni uzantı yöntemlerinde olduğu gibi nameof'da uzantı özelliklerine izin vermemeli miyiz? Cevap: `nameof(EnclosingStaticClass.ExtensionMember)` kullanmak istiyoruz. Tasarım gerekiyor, büyük olasılıkla .NET 10'dan geliyor. LDM 2025-06-11)

desen tabanlı yapılar

Yöntemler

  • Yeni uzantı yöntemleri nerede devreye girmelidir? (yanıt: Klasik uzantı yöntemlerinin devreye girdiği yerler, LDM 2025-05-05)

Buna aşağıdakiler dahildir:

  • GetEnumerator / GetAsyncEnumerator içinde foreach
  • Deconstruct yapısöküm, konumsal desen ve foreach içinde
  • Add koleksiyon başlatıcılarında
  • GetPinnableReference içinde fixed
  • GetAwaiter içinde await

Bu, şunu dışlar:

  • Dispose / DisposeAsync using ve foreach içinde
  • MoveNext / MoveNextAsync içinde foreach
  • Slice ve int örtük dizin oluşturuculardaki dizine koyucular (ve büyük olasılıkla liste örüntüleri?)
  • GetResult içinde await

Özellikler ve dizin oluşturucular

  • Uzantı özellikleri ve dizin oluşturucular nerede devreye girmelidir? (cevap: Dördü ile başlayalım, LDM 2025-05-05)

Şunları ekleyeceğiz:

  • nesne başlatıcı: new C() { ExtensionProperty = ... }
  • sözlük başlatıcısı: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • özellik desenleri: x is { ExtensionProperty: ... }

Şu öğeleri hariç tutardık:

  • Current içinde foreach
  • IsCompleted içinde await
  • Count / Length liste düzenindeki özellikler ve dizin oluşturucular
  • Count / Length örtük dizinleyicilerdeki özellikler ve dizinleyiciler
Temsilci döndüren özellikler
  • Bu şeklin uzantı özelliklerinin, örnek özelliklerinin ne yaptığını eşleştirmek için yalnızca LINQ sorgularında devreye alınması gerektiğini onaylayın. (yanıt: mantıklı, LDM 2025-04-06)
Liste ve dağıtma düzeni
  • Uzantı Index/Range dizin oluşturucularının liste desenlerinde oynatılması gerektiğini onaylayın (yanıt: C# 14 için uygun değil)
Uzantı özelliklerinin devreye girdiği yeri Count/Length yeniden ziyaret edin

Koleksiyon ifadeleri

  • Uzantı Add çalışır
  • Uzantı GetEnumerator, yayılma için çalışır.
  • Uzantı GetEnumerator , öğe türünün belirlenmesini etkilemez (örnek olmalıdır)
  • Statik Create uzantı yöntemleri kutsanmış bir oluşturma yöntemi olarak sayılmamalıdır
  • Ek sayılabilir özellikler koleksiyon ifadelerini etkilemeli mi?

params koleksiyonları

  • Uzantılar Add hangi türlere izin verildiğini etkilemez params

sözlük ifadeleri

  • Dizin oluşturucunun varlığı sözlük türünü tanımlayan şeyin ayrılmaz bir parçası olduğundan, uzantı dizin oluşturucularının sözlük ifadelerinde oynamadığını onaylayın. (yanıt: C# 14 ile ilgili değil)

extern

Uzantı türü için adlandırma/numaralandırma düzeni

Sorun
Geçerli numaralandırma sistemi , genel API'lerin doğrulanmasıyla ilgili sorunlara neden olur ve bu da genel API'lerin yalnızca başvuru derlemeleri ile uygulama derlemeleri arasında eşleşmesini sağlar.

Aşağıdaki değişikliklerden birini yapmalı mıydık? (yanıt: Genel API kararlılığını artırmak için içerik tabanlı bir adlandırma şemasını benimsiyoruz ve araçların işaretleyici yöntemleri hesaba eklemek için güncelleştirilmeye devam edilmesi gerekecek)

  1. aracı ayarla
  2. bazı içerik tabanlı adlandırma düzeni (TBD) kullanma
  3. adın belirli bir söz dizimi aracılığıyla kontrol edilmesine izin ver

Yeni genel uzantı Cast yöntemi LINQ içinde hala çalışamıyor

Sorun
Rollerin/uzantıların önceki tasarımlarında yalnızca yöntemin tür bağımsız değişkenlerini açıkça belirtmek mümkündü.
Ancak artık klasik uzantı yöntemlerinden kesintisiz geçişe odaklandığımıza göre, tüm tür bağımsız değişkenleri açıkça verilmelidir.
Bu, LINQ'te uzantı Atama yöntemi kullanımıyla ilgili bir sorunu gideremiyor.

Bu senaryoya uyum sağlamak için uzantılar özelliğinde bir değişiklik yapmamız gerekir mi? (yanıt: hayır, bu, uzantının çözümleme tasarımını yeniden gözden geçirmemize neden olmaz, LDM 2025-05-05)

Uzantı üyesinde uzantı parametresini kısıtlama

Aşağıdakilere izin vermeli miyiz? (yanıt: hayır, bu daha sonra eklenebilir)

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> { }

Null Olabilirlik

  • Geçerli tasarımı onaylayın, yani maksimum taşınabilirlik/uyumluluk (yanıt: evet, 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!;
    }

Meta veriler

  • İskelet yöntemleri NotSupportedException veya başka bir standart istisna mı fırlatmalı (şu anda biz bunu throw null; yapıyor muyuz)? (yanıt: evet, LDM 2025-04-17)
  • Meta verilerde işaretçi yönteminde birden fazla parametre kabul etmeli miyiz (yeni sürümlerin daha fazla bilgi eklemesi durumunda)? (yanıt: katı kalabiliriz, LDM 2025-04-17)
  • Uzantı işaretçisi veya konuşılabilir uygulama yöntemleri özel adla işaretlenmeli mi? (yanıt: İşaretleyici yöntemi özel adla işaretlenmeli ve bunu denetlemeliyiz, ancak uygulama yöntemleri değil, LDM 2025-04-17)
  • İçinde örnek uzantısı yöntemi olmasa bile statik sınıfa öznitelik eklemeli [Extension] miyiz? (yanıt: evet, LDM 2025-03-10)
  • Uygulama alıcılarına ve ayarlayıcılarına da öznitelik eklememiz [Extension] gerektiğini onaylayın. (yanıt: hayır, LDM 2025-03-10)
  • Uzantı türlerinin özel bir adla işaretlenmesi gerektiğini ve derleyicinin meta verilerde bu bayrağı zorunlu kılacağını onaylayın (bu, önizlemeden farklılık gösterebilecek önemli bir değişikliktir) (yanıt: onaylandı, LDM 2025-06-23)

statik fabrika senaryosu

  • Statik yöntemler için çakışma kuralları nelerdir? (yanıt: Kapsayan statik tür için geçerli C# kurallarını kullanın, hiçbir rahatlama yok, LDM 2025-03-17)

Arama Listesi

  • Artık söylenebilir uygulama adlarına sahip olduğumuza göre örnek yöntemi çağrıları nasıl çözülür? İskelet yöntemini ilgili uygulama yöntemine tercih ediyoruz.
  • Statik uzantı yöntemleri nasıl çözülür? (yanıt: Örnek uzantısı yöntemleri gibi, LDM 2025-03-03)
  • Özellikler nasıl çözülür? (genel hatlarıyla yanıtlandı LDM 2025-03-03, ancak iyileştirme için takip gerekiyor)
  • Uzantı parametresi ve tür parametreleri için kapsam belirleme ve gölgeleme kuralları (yanıt: uzantı bloğu kapsamında, gölgelendirmeye izin verilmiyor, LDM 2025-03-10)
  • ORPA yeni uzantı yöntemlerine nasıl uygulanmalıdır? (yanıt: uzantı bloklarını saydam olarak kabul edin, ORPA için "içeren tür", içinde yer alan statik sınıftır, LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • ORPA yeni uzantı özelliklerine uygulanmalı mı? (yanıt: evet ve ORPA uygulama yöntemlerine kopyalanmalıdır, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Klasik uzantı çözümleme kurallarını nasıl yeniden tutarsınız? Biz mi?
    1. klasik uzantı yöntemleri için standardı güncelleştirin ve bunu yeni uzantı yöntemlerini de açıklamak için kullanın,
    2. klasik uzantı yöntemleri için mevcut dili koruyun, bunu yeni uzantı yöntemlerini de açıklamak için kullanın, ancak her ikisi için de bilinen bir belirtim sapması varsa,
    3. klasik uzantı yöntemleri için mevcut dili koruyup yeni uzantı yöntemleri için farklı bir dil kullanmak mı, yoksa sadece klasik uzantı yöntemleri için bilinen bir belirtim sapmasına mı sahip olmak?
  • Bir özellik erişiminde açık tür bağımsız değişkenlerine izin vermek istemediğimizi onaylayın (yanıt: WG'de açıklanan açık tür bağımsız değişkenleriyle özellik erişimi yok)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Alıcı bir tür olduğunda bile iyilik kurallarının uygulanmasını istediğimizi onaylayın (yanıt: statik uzantı üyeleri çözümlendiğinde yalnızca tür uzantısı parametresi dikkate alınmalıdır, 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;
    }
}
  • Hem yöntemlerin hem de özelliklerin geçerli olduğu durumlarda belirsizliğe izin verme konusunda mutabık olduğumuzu onaylayın (yanıt: .NET 10'dan çıkararak ve LDM 2025-06-23'te görüşerek mevcut durumdan daha iyi bir teklif tasarlamamız gerekiyor)
  • Kazanan üye türünü belirlemeden önce tüm üyeler arasında biraz daha iyi olmak istemediğimiz onaylayın (yanıt: .NET 10, WG 2025-07-02'nin dışına doğru inin)
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;
    }
}
  • Uzantı bildirimleri içinde örtük bir alıcımız var mı? (yanıt: hayır, daha önce LDM'de tartışılmıştı)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Tür parametresinde aramaya izin vermeli miyiz? (tartışma) (yanıt: hayır, geri bildirimi bekleyeceğiz, LDM 2025-04-16)

Erişilebilirlik

  • Uzantı bildiriminde erişilebilirlik ne anlama gelir? (yanıt: uzantı bildirimleri erişilebilirlik kapsamı olarak sayılmaz, LDM 2025-03-17)
  • Statik üyeler için bile alıcı parametresinde "tutarsız erişilebilirlik" denetimini uygulamalı mıydık? (yanıt: evet, 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 { }
}

Uzantı bildirimi doğrulama

  • Yalnızca yöntemlerin mevcut olduğu durumlarda, tür parametreleri doğrulamasını (çıkarılabilirlik: tüm tür parametreleri uzantı parametresinin türünde görünmelidir) rahatlatmalı mıyız? (yanıt: evet, LDM 2025-04-06) Bu, klasik uzantı yöntemlerinin 100% taşımasına izin verir.
    Eğer TResult M<TResult, TSource>(this TSource source) varsa, onu extension<TResult, TSource>(TSource source) { TResult M() ... } olarak aktarabilirsiniz.

  • Uzantılarda yalnızca init erişimcilerine izin verilip verilmeyeceğini onaylayın (yanıt: şimdilik izin verme, LDM 2025-04-17)

  • Alıcı referans durumu arasındaki tek farka izin extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }verilmeli mi? (yanıt: hayır, belirlenen kuralı sürdür, LDM 2025-03-24)

  • Böyle extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }bir çatışmadan şikayet etmeli miyiz? (yanıt: evet, belirtim kuralını koru, LDM 2025-03-24)

  • Uygulama yöntemleri arasında bir çakışma olmadığı halde iskelet yöntemler arasındaki çakışmalardan şikayet etmeli miyiz? (yanıt: evet, belirtim kuralını koru, LDM 2025-03-24)

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

Geçerli çakışma kuralları şunlardır: 1. sınıf/yapı kurallarını kullanarak benzer uzantılarda çakışma olup olmadığını denetleyin, 2. çeşitli uzantı bildirimleri arasında uygulama yöntemleri arasında çakışma olup olmadığını denetleyin.

  • Kuralların ilk bölümüne ihtiyacımız var mı? (yanıt: evet, API'lerin tüketimine yardımcı olduğu için bu yapıyı tutuyoruz, LDM 2025-03-24)

XML belgeleri

  • Alıcı parametresi uzantı üyelerinde destekleniyor mu paramref ? Statikte bile mi? Çıkışta veri nasıl kodlanır? Büyük olasılıkla standart bir yol <paramref name="..."/> bir insan için işe yarayabilir, ancak bazı mevcut araçların bunun API parametreleri arasında bulunamamasından dolayı hoşnut olmayabileceği riski vardır. (yanıt: uzantı üyelerinde uzantı parametresine evet parametre başvurusuna izin verilir, LDM 2025-05-05)
  • Belge açıklamalarını, söylenebilir adlarla uygulama yöntemlerine kopyalamamız mı gerekiyor? (yanıt: kopyalama yok, LDM 2025-05-05)
  • Alıcı parametresine karşılık gelen öğe, örnek yöntemleri için uzantı kapsayıcısından kopyalanmalı mı <param> ? Kapsayıcıdan uygulama yöntemlerine (<typeparam> vb.) başka bir şey kopyalanmalıdır? (yanıt: kopyalama yok, LDM 2025-05-05)
  • Uzantı parametresi için <param>, uzantı üyelerinde bir geçersiz kılma olarak kullanılmasına izin verilmeli mi? (yanıt: hayır, şimdilik LDM 2025-05-05)
  • Uzantı bloklarının özeti herhangi bir yerde görünecek mi?

CREF

  • Söz dizimini onaylama (yanıt: teklif iyi, LDM 2025-06-09)
  • Uzantı bloğuna (E.extension(int)) başvurmak mümkün mü? (yanıt: hayır, LDM 2025-06-09)
  • Niteliksiz bir sözdizimi kullanarak bir üyeye başvurmak mümkün mü? extension(int).Member (yanıt: evet, LDM 2025-06-09)
  • XML'nin kaçmasını önlemek için söylenemeyen ad için farklı karakterler mi kullanmalıyız? (yanıt: WG'ye erteleme, LDM 2025-06-09)
  • hem iskelete hem de uygulama yöntemlerine yönelik başvuruların mümkün olduğunu onaylayın: E.M vs. E.extension(int).M. Her ikisi de gerekli görünüyor (uzantı özellikleri ve klasik uzantı yöntemlerinin taşınabilirliği). (yanıt: evet, LDM 2025-06-09)
  • Uzantı meta veri adları, sürüm oluşturma belgeleri için sorunlu mu? (yanıt: evet, sıra sayılarından uzaklaşacağız ve içerik tabanlı kararlı bir adlandırma şeması kullanacağız)

Daha fazla üye türü için destek ekleme

Bu tasarımın tümünü aynı anda uygulamamız gerekmez, ancak aynı anda bir veya birkaç üye türüne yaklaşabiliriz. Çekirdek kitaplıklarımızdaki bilinen senaryolara dayanarak aşağıdaki sırayla çalışmamız gerekir:

  1. Özellikler ve yöntemler (örnek ve statik)
  2. Operatörler
  3. İndeksleyiciler (örnek ve statik, daha önceki bir noktada fırsatçı bir şekilde yapılabilir)
  4. Başka bir şey

Diğer üye türleri için tasarımın ön yüklemesini ne kadar yapmak istiyoruz?

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
    ;

İç içe geçmiş türler

Uzantı iç içe türleriyle ilerlemeyi seçersek, önceki tartışmalardan bazı notlar aşağıdadır:

  • İki uzantı bildirimi aynı adlara ve kardinaliteye sahip iç içe uzantı türleri bildirirse bir çakışma ortaya çıkar. Bunu meta verilerde göstermek için bir çözüme sahip değiliz.
  • Meta veriler için ele aldığımız kaba yaklaşım:
    1. Özgün tür parametrelerine sahip, üye içermeyen iç içe bir iskelet yapı yayarız.
    2. uzantı bildiriminden ve tüm üye uygulamalarında kaynakta göründükleri şekilde önceden eklenmiş tür parametreleriyle bir uygulama iç içe türü yayarız (tür parametrelerine modül başvuruları)

Kurucular

C# dilinde oluşturucular genellikle örnek üye olarak tanımlanır, çünkü gövdeleri this anahtar sözcüğü aracılığıyla yeni oluşturulan değere erişebilir. Bu, örnek uzantısı üyelerine parametre tabanlı yaklaşım için iyi çalışmaz çünkü parametre olarak geçirilecek önceki bir değer yoktur.

Bunun yerine, uzantı oluşturucuları daha çok statik fabrika yöntemleri gibi çalışır. Alıcı parametre adına bağımlı olmadığı için statik üyeler olarak kabul edilirler. Vücutlarının açıkça oluşturması ve inşaat sonucunu döndürmesi gerekir. Üyenin kendisi hala oluşturucu söz dizimi ile bildirilir, ancak this veya base başlatıcılarına sahip olamaz ve alıcı türünün erişilebilir oluşturuculara sahip olması gerekmez.

Bu, uzantı oluşturucularının arabirimler ve sabit listesi türleri gibi kendi oluşturucuları olmayan türler için bildirilebileceği anlamına da gelir:

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) { ... } 
}

İzin verir:

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

Daha kısa formlar

Önerilen tasarım, alıcı özelliklerinin üye başına tekrarlanmasını önler, ancak uzantı üyelerinin bir statik sınıf ve uzantı bildiriminde iç içe yerleştirilmesiyle sonuçlanır. Statik sınıfların tek bir uzantı bildirimi içermesi veya uzantı bildirimlerinin yalnızca bir üye içermesi büyük olasılıkla yaygın olacaktır ve bu durumlar için söz dizimi kısaltmalarına izin vermemiz mantıklıdır.

Statik sınıf ve uzantı bildirimlerini birleştirin:

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

Bu, "tür tabanlı" bir yaklaşım olarak adlandırdığımız şeye daha çok benzemektedir; burada uzantı üyelerine yönelik kapsayıcı isimlendirilmiştir.

Uzantı bildirimini ve uzantı üyesini birleştir:

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) { ... }
}

Bu, her uzantı üyesinin kendi alıcı özelliğini içerdiği "üye tabanlı" bir yaklaşım olarak tanımladığımız şeye daha çok benzer olarak görünüyor.