Freigeben über


Erweiterungsmember

Hinweis

Dieser Artikel ist eine Featurespezifikation. Die Spezifikation dient als Designdokument für das Feature. Es enthält vorgeschlagene Spezifikationsänderungen sowie Informationen, die während des Entwurfs und der Entwicklung des Features erforderlich sind. Diese Artikel werden veröffentlicht, bis die vorgeschlagenen Spezifikationsänderungen abgeschlossen und in die aktuelle ECMA-Spezifikation aufgenommen werden.

Es kann einige Abweichungen zwischen der Featurespezifikation und der abgeschlossenen Implementierung geben. Diese Unterschiede werden in den relevanten Sprachentwurfsbesprechungen (LDM)-Notizen erfasst.

Weitere Informationen zum Einführen von Featurespezifikationen in den C#-Sprachstandard finden Sie im Artikel zu den Spezifikationen.

Champion Issue: https://github.com/dotnet/csharplang/issues/8697

Erklärung

Syntax

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

Erweiterungsdeklarationen dürfen nur in nicht generischen, nicht geschachtelten statischen Klassen deklariert werden.
Es ist ein Fehler für einen Typ, der benannt extensionwerden soll.

Bereichsdefinitionsregeln

Die Typparameter und der Empfängerparameter einer Erweiterungserklärung sind im Geltungsbereich des Körpers der Erweiterungserklärung vorhanden. Es ist ein Fehler, auf den Empfängerparameter innerhalb eines statischen Elements zu verweisen, mit Ausnahme eines Ausdrucks nameof . Es ist ein Fehler, wenn ein Mitglied Typparameter oder Parameter (sowie lokale Variablen und lokale Funktionen direkt im Mitgliedskörper) mit demselben Namen wie ein Typparameter oder ein Receiver-Parameter der Erweiterungsdeklaration deklariert.

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

Es ist kein Fehler für die Member selbst, denselben Namen wie die Typparameter oder den Empfängerparameter der eingeschlossenen Erweiterungsdeklaration zu haben. Mitgliedsnamen werden nicht direkt durch eine einfache Namenssuche innerhalb der Erweiterungsdeklaration gefunden; deshalb wird der Typparameter oder der Empfängerparameter dieses Namens anstelle des Mitglieds gefunden.

Mitglieder geben Anlass zu statischen Methoden, die direkt in der umgebenden statischen Klasse deklariert werden, und diese können durch einfache Namenssuche gefunden werden; aber ein Typparameter der Erweiterungsdeklaration oder ein Empfängerparameter mit demselben Namen wird zuerst gefunden.

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

Statische Klassen als Erweiterungscontainer

Erweiterungen werden innerhalb nicht generischer statischer Klassen auf oberster Ebene deklariert, genau wie erweiterungsmethoden heute und können somit mit klassischen Erweiterungsmethoden und nicht-erweiterungsstatischen Membern koexistieren:

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

Erweiterungsdeklarationen

Eine Erweiterungsdeklaration ist anonym und stellt eine Empfängerspezifikation mit allen zugeordneten Typparametern und Einschränkungen bereit, gefolgt von einer Reihe von Erweiterungsmememerdeklarationen. Die Empfängerspezifikation kann sich in Form eines Parameters befinden, oder - wenn nur statische Erweiterungsmember deklariert werden - einen Typ:

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

Der Typ in der Empfängerspezifikation wird als Empfängertyp und der Parametername (sofern vorhanden) als Empfängerparameter bezeichnet.

Wenn der Empfängerparameter benannt ist, ist der Empfängertyp möglicherweise nicht statisch.
Der Empfängerparameter darf keine Modifizierer haben, wenn er nicht benannt ist, und er darf nur die Abweichungsmodifizierer unten und scoped andernfalls enthalten.
Der Empfängerparameter trägt die gleichen Einschränkungen wie der erste Parameter einer klassischen Erweiterungsmethode.
Das [EnumeratorCancellation] Attribut wird ignoriert, wenn es im Empfängerparameter platziert wird.

Erweiterungsmember

Erweiterungsmemberdeklarationen sind syntaktisch identisch mit entsprechenden Instanzen und statischen Membern in Klassen- und Strukturdeklarationen (mit Ausnahme von Konstruktoren). Instanzmitglieder verweisen auf den Empfänger mit dem Namen des Empfängerparameters:

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

Fehler beim Angeben eines Instanzerweiterungsmitglieds, wenn die eingeschlossene Erweiterungsdeklaration keinen Empfängerparameter angibt:

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

Es ist ein Fehler, die folgenden Modifizierer für ein Element einer Erweiterungsdeklaration anzugeben: abstract, , virtual, , override, new, sealed, partialund protected (und verwandte Barrierefreiheitsmodifizierer).
Eigenschaften in Erweiterungsdeklarationen verfügen möglicherweise nicht über init Accessoren.
Die Instanzmitglieder sind unzulässig, wenn der Empfängerparameter unbenannt ist.

Fehler beim Dekorieren eines Erweiterungselements mit dem [ModuleInitializer] Attribut.

Refness

Standardmäßig wird der Empfänger wie andere Parameter an Instanzenerweiterungsmber nach Wert übergeben. Ein Erweiterungsdeklarationsempfänger im Parameterformular kann jedoch angeben refref readonly und in, solange der Empfängertyp als Werttyp bekannt ist.

Wenn ref angegeben, kann ein Instanzmitglied oder eine seiner Accessoren deklariert readonlywerden, wodurch verhindert wird, dass er den Empfänger stummschalten kann:

public static class Bits
{
    extension(ref ulong bits) // receiver is passed by ref
    {
        public bool this[int index]
        {
            set => bits = value ? bits | Mask(index) : bits & ~Mask(index); // mutates receiver
            readonly get => (bits & Mask(index)) != 0;                // cannot mutate receiver
        }
    }
    static ulong Mask(int index) => 1ul << index;
}

Nullierbarkeit und Attribute

Empfängertypen können nullable Verweistypen sein oder enthalten, und Empfängerspezifikationen, die sich in Form von Parametern befinden, können Attribute angeben:

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

Kompatibilität mit klassischen Erweiterungsmethoden

Instanzenerweiterungsmethoden generieren Artefakte, die mit den klassischen Erweiterungsmethoden übereinstimmen.

Insbesondere die generierte statische Methode verfügt über die Attribute, Modifizierer und den Namen der deklarierten Erweiterungsmethode sowie typparameterliste, Parameterliste und Einschränkungsliste, die aus der Erweiterungsdeklaration und der Methodendeklaration in dieser Reihenfolge verkettet ist:

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

Generiert:

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

Betriebspersonal

Erweiterungsoperatoren verfügen zwar über explizite Operandentypen, müssen aber dennoch innerhalb einer Erweiterungsdeklaration deklariert werden:

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

Auf diese Weise können Typparameter deklariert und abgeleitet werden und analog dazu verwendet werden, wie ein regulärer benutzerdefinierter Operator innerhalb eines seiner Operandentypen deklariert werden muss.

Wird geprüft

Ableitbarkeit: Für jedes Nicht-Methoden-Erweiterungsmitglied müssen alle Typparameter des Erweiterungsblocks in der kombinierten Menge von Parametern aus der Erweiterung und dem Mitglied verwendet werden.

Eindeutigkeit: Innerhalb einer bestimmten eingeschlossenen statischen Klasse werden die Deklarationen von Erweiterungsmembern mit demselben Empfängertyp (Modulo Identity Conversion and Type Parameter Name Substitution) als ein einzelnes Deklarationsraum behandelt, der den Membern innerhalb einer Klasse oder Struct-Deklaration ähnelt, und unterliegen den gleichen Regeln zur Eindeutigkeit.

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

Die Anwendung dieser Eindeutigkeitsregel enthält klassische Erweiterungsmethoden innerhalb derselben statischen Klasse. Für den Vergleich mit Methoden innerhalb von Erweiterungsdeklarationen wird der this Parameter zusammen mit allen Typparametern behandelt, die in diesem Empfängertyp erwähnt werden, und die verbleibenden Typparameter und Methodenparameter werden für die Methodensignatur verwendet:

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

Verbrauch

Wenn eine Erweiterungsmembersuche versucht wird, tragen alle Erweiterungsdeklarationen innerhalb statischer Klassen, die importiert werden using, ihre Member als Kandidaten bei, unabhängig vom Empfängertyp. Nur als Teil der Auflösung sind Kandidaten mit inkompatiblen Empfängertypen verworfen.
Es wird versucht, eine vollständige generische Typinferenz zwischen den Typen der Argumente (einschließlich des tatsächlichen Empfängers) und allen Typparametern (zusammengefasst in der Erweiterungsdeklaration und in der Erweiterungsmemberdeklaration) durchzuführen.
Wenn explizite Typargumente angegeben werden, werden sie verwendet, um die Typparameter der Erweiterungsdeklaration und die Erweiterungsmemmdeklaration zu ersetzen.

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

Ähnlich wie bei klassischen Erweiterungsmethoden können die ausgegebenen Implementierungsmethoden statisch aufgerufen werden.
Auf diese Weise kann der Compiler zwischen Erweiterungsmitgliedern mit demselben Namen und derselben Arität unterscheiden.

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

Statische Erweiterungsmethoden werden wie Instanzenerweiterungsmethoden aufgelöst (wir betrachten ein zusätzliches Argument des Empfängertyps).
Erweiterungseigenschaften werden wie Erweiterungsmethoden mit einem einzelnen Parameter (dem Empfängerparameter) und einem einzelnen Argument (dem tatsächlichen Empfängerwert) aufgelöst.

OverloadResolutionPriorityAttribute

Erweiterungsmitglieder innerhalb einer umgebenden statischen Klasse unterliegen der Priorisierung gemäß ORPA-Werten. Die eingeschlossene statische Klasse wird als "enthaltender Typ" betrachtet, den ORPA-Regeln berücksichtigen.
Jedes ORPA-Attribut, das für eine Erweiterungseigenschaft vorhanden ist, wird in die Implementierungsmethoden für die Accessoren der Eigenschaft kopiert, sodass die Priorisierung berücksichtigt wird, wenn diese Accessoren über die Mehrdeutigkeitssyntax verwendet werden.

Einstiegspunkte

Methoden von Erweiterungsblöcken gelten nicht als Kandidaten für Einstiegspunkte (siehe "7.1 Anwendungsstart"). Hinweis: Die Implementierungsmethode kann immer noch ein Kandidat sein.

Reduzierung

Die Senkungsstrategie für Erweiterungsdeklarationen ist keine Entscheidung auf Sprachebene. Über die Implementierung der Sprachsemantik hinaus muss sie jedoch bestimmte Anforderungen erfüllen:

  • Das Format der generierten Typen, Member und Metadaten sollte in allen Fällen eindeutig angegeben werden, damit andere Compiler sie nutzen und generieren können.
  • Die generierten Artefakte sollten stabil sein, in dem Sinne, dass vernünftige spätere Änderungen die Verbraucher, die gegen frühere Versionen kompiliert wurden, nicht unterbrechen sollten.

Diese Anforderungen benötigen mehr Verfeinerung, wenn die Implementierung voranschreitet, und kann in Eckfällen kompromittiert werden, um einen angemessenen Implementierungsansatz zu ermöglichen.

Metadaten für Deklarationen

Jede Erweiterungsdeklaration wird als Skeletttyp mit einer Markermethode und -membern ausgegeben.
Jedes Skelettelement wird von einer statischen Implementierungsmethode auf oberster Ebene mit einer geänderten Signatur begleitet.
Die enthaltende statische Klasse für eine Erweiterungsdeklaration wird mit einem [Extension] Attribut gekennzeichnet.

Skelette

Jede Erweiterungsdeklaration in der Quelle wird als Erweiterungsdeklaration in Metadaten ausgegeben.

  • Sein Name ist unsäglich und bestimmt basierend auf der lexikalischen Reihenfolge im Programm.
    Der Name ist nicht garantiert, dass er bei der erneuten Kompilierung stabil bleibt. Nachfolgend verwenden <>E__ wir einen Index. Beispiel: <>E__2.
  • Die Typparameter sind die in der Quelle deklarierten Parameter (einschließlich Attributen).
  • Der Zugriff darauf ist öffentlich.
  • Sie ist mit der specialname Kennzeichnung gekennzeichnet.

Methoden- und Eigenschaftsdeklarationen in einer Erweiterungserklärung im Quellcode werden in den Metadaten als Skelettmitglieder dargestellt.
Die Signaturen der ursprünglichen Methoden werden beibehalten (einschließlich Attributen), aber ihre Körper werden durch throw nullersetzt.
Auf diese sollten in IL nicht verwiesen werden.

Hinweis: Dies ist vergleichbar mit Verweisassemblys. Der Grund für die Verwendung von throw null Stellen (im Gegensatz zu keinen Stellen) besteht darin, dass die IL-Überprüfung ausgeführt und übergeben werden kann (wodurch die Vollständigkeit der Metadaten überprüft wird).

Die Erweiterungsmarkierungsmethode codiert den Empfängerparameter.

  • Es ist privat und statisch und wird genannt <Extension>$.
  • Sie verfügt über die Attribute, refness, den Typ und den Namen des Empfängerparameters in der Erweiterungsdeklaration.
  • Wenn der Empfängerparameter keinen Namen angibt, ist der Parametername leer.

Hinweis: Dies ermöglicht den Hin- und Rückaustausch von Symbolen der Erweiterungsdeklarationen über die Metadaten (vollständige Assemblys und Referenzassemblys).

Hinweis: Wir können nur einen Erweiterungsskeletttyp in Metadaten ausgeben, wenn doppelte Erweiterungsdeklarationen in der Quelle gefunden werden.

Ausführungen

Die Methodentexte für Methoden-/Eigenschaftsdeklarationen in einer Erweiterungsdeklaration in der Quelle werden als statische Implementierungsmethoden in der statischen Klasse der obersten Ebene ausgegeben.

  • Eine Implementierungsmethode hat denselben Namen wie die ursprüngliche Methode.
  • Es verfügt über Typparameter, die von der Erweiterungsdeklaration abgeleitet sind, die den Typparametern der ursprünglichen Methode (einschließlich Attributen) vorangestellt ist.
  • Es verfügt über die gleiche Barrierefreiheit und Attribute wie die ursprüngliche Methode.
  • Wenn sie eine statische Methode implementiert, weist sie dieselben Parameter und rückgabetyp auf.
  • Wenn eine Instanzmethode implementiert wird, verfügt sie über einen vordefinierten Parameter zur Signatur der ursprünglichen Methode. Die Attribute dieses Parameters – Refness, Typ und Name – werden von dem Empfängerparameter abgeleitet, der in der relevanten Erweiterungsdeklaration deklariert ist.
  • Die Parameter in Implementierungsmethoden beziehen sich auf Typparameter, die sich im Besitz der Implementierungsmethode befinden, statt auf die Parameter einer Erweiterungsdeklaration.
  • Wenn es sich bei dem ursprünglichen Element um eine instanz gewöhnliche Methode handelt, wird die Implementierungsmethode mit einem [Extension] Attribut gekennzeichnet.

Beispiel:

static class IEnumerableExtensions
{
    extension<T>(IEnumerable<T> source)
    {
        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() { ... }
}

wird als

[Extension]
static class IEnumerableExtensions
{
    public class <>E__1<T>
    {
        private static <Extension>$(IEnumerable<T> source) => throw null;
        public void Method() => throw null;
        internal static int Property { get => throw null; set => throw null; }
        public int Property2 { get => throw null; set => throw null; }
    }

    public class <>E__2
    {
        private static <Extension>$(IAsyncEnumerable<int> values) => throw null;
        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() { ... }
}

Immer wenn Erweiterungsmmber in der Quelle verwendet werden, geben wir diese als Verweis auf Implementierungsmethoden aus.
Beispiel: Ein Aufruf von enumerableOfInt.Method() wird als statischer Aufruf IEnumerableExtensions.Method<int>(enumerableOfInt)an .

XML-Dokumente

Die Dokumentkommentare für den Erweiterungsblock werden für den nicht darstellbaren benannten Typ erstellt (die DocID für den Erweiterungsblock ist <>E__0'1 im folgenden Beispiel).
Die Dokumentkommentare zu den Erweiterungsmitgliedern werden für die Skelettmitglieder ausgegeben. Sie dürfen auf den Erweiterungsparameter und die Typparameter unter Verwendung von <paramref> bzw. <typeparamref> verweisen.
Hinweis: Sie können den Erweiterungsparameter oder Typparameter (mit <param> und <typeparam>) nicht für ein Erweiterungselement dokumentieren.

Tools, die die XML-Dokumente verwenden, sind dafür verantwortlich, den Erweiterungsblock <param> und <typeparam> nach Bedarf in die Erweiterungsmember zu kopieren (d.h. die Parameterinformationen sollten nur für Instanzmember kopiert werden).

Eine <inheritdoc> wird bei Implementierungsmethoden emittiert und bezieht sich auf das relevante Skelettmitglied mit einem cref. Die Implementierungsmethode für einen Getter bezieht sich beispielsweise auf die Dokumentation der Skeletteigenschaft. Wenn das Skelettmitglied keine Dokumentkommentare hat, wird das <inheritdoc> weggelassen.

Bei Erweiterungsblöcken und Erweiterungsmitgliedern erhalten wir gegenwärtig keine Warnungen, wenn:

  • Der Erweiterungsparameter ist dokumentiert, aber die Parameter für das Erweiterungselement sind nicht vorhanden.
  • oder umgekehrt
  • oder in den entsprechenden Szenarien mit nicht dokumentierten Typparametern

Beispielsweise die folgenden Dokumentkommentare:

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

den folgenden XML-Code liefern:

<?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__0`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__0`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__0`1.P">
            <summary>Summary for P</summary>
        </member>
        <member name="M:E.M``2(``0,``1)">
            <inheritdoc cref="M:E.&lt;&gt;E__0`1.M``1(``0)"/>
        </member>
        <member name="M:E.get_P``1(``0)">
            <inheritdoc cref="P:E.&lt;&gt;E__0`1.P"/>
        </member>
    </members>
</doc>

CREF-Referenzen

Wir können Erweiterungsblöcke wie geschachtelte Typen behandeln, die anhand ihrer Signatur adressiert werden können (als wäre es eine Methode mit einem einzelnen Erweiterungsparameter). Beispiel: E.extension(ref int).M().

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

Die Suche weiß, in allen passenden Erweiterungsblöcken nachzuschlagen.
Da wir unqualifizierte Verweise auf Erweiterungsmitglieder verbieten, wird cref dies ebenfalls tun.

Die Syntax wäre:

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
  ;

Es ist ein Fehler, extension_member_cref auf oberster Ebene (extension(int).M) oder in einer anderen Erweiterung verschachtelt (E.extension(int).extension(string).M) zu verwenden.
Hinweis: Der cref zu dem Erweiterungsblock ist nicht möglich, da E.extension(int) auf eine Methode mit dem Namen "extension" im Typ E verweist.

Bahnbrechende Änderungen

Typen und Aliase werden möglicherweise nicht als "Erweiterung" bezeichnet.

Offene Probleme

  • Bestätigen: extension oder extensions als Schlüsselwort (Antwort: extension, LDM 2025-03-24)
  • Bestätigen, dass wir nicht zulassen [ModuleInitializer]möchten (Antwort: Ja, nicht zulassen, LDM 2025-06-11)
  • Vergewissern Sie sich, dass es in Ordnung ist, Erweiterungsblöcke als Einstiegspunktkandidaten zu verwerfen (Antwort: Ja, Verwerfen, LDM 2025-06-11)
  • Bestätigen Sie die LangVer-Logik (überspringen Sie neue Erweiterungen, und melden Sie sie bei auswahl) (Antwort: Bedingungslos binden und LangVer-Fehler melden, mit Ausnahme von Erweiterungsmethoden, LDM 2025-06-11)
  • Sollten wir die Empfängeranforderungen beim Zugriff auf ein Erweiterungsmitglied anpassen? (Kommentar) Beispiel: new Struct() { Property = 42 }.

Name Of

  • Sollten wir Erweiterungseigenschaften in nameof so wie klassische und neue Erweiterungsmethoden nicht zulassen? (Antwort: wir möchten 'nameof(EnclosingStaticClass.ExtensionMember) verwenden. Benötigt Design, wahrscheinlich Punt aus .NET 10. LDM 2025-06-11)

musterbasierte Konstrukte

Methodik

  • Wo sollen neue Erweiterungsmethoden ins Spiel kommen? (Antwort: dieselben Orte, an denen klassische Erweiterungsmethoden ins Spiel kommen, LDM 2025-05-05) Dazu gehören:
  • GetEnumerator / GetAsyncEnumerator in foreach
  • Deconstruct in Dekonstruktion, in Positionsmustern und Vorgabe
  • Add in Sammlungsinitialisierern
  • GetPinnableReference in fixed
  • GetAwaiter in await

Dies schließt Folgendes aus:

  • Dispose / DisposeAsync in using und foreach
  • MoveNext / MoveNextAsync in foreach
  • Slice und int Indexer in impliziten Indexern (und eventuell Listenmustern?)
  • GetResult in await

Eigenschaften und Indexer

  • Wo sollen Erweiterungseigenschaften und Indexer ins Spiel kommen? (Antwort: Beginnen wir mit den vier, LDM 2025-05-05)

Wir fügen Folgendes ein:

  • Object Initializer: new C() { ExtensionProperty = ... }
  • Wörterbuchinitialisierer: new C() { [0] = ... }
  • with: x with { ExtensionProperty = ... }
  • Eigenschaftenmuster: x is { ExtensionProperty: ... }

Wir schließen Folgendes aus:

  • Current in foreach
  • IsCompleted in await
  • Count / Length Eigenschaften und Indexer im Listenmuster
  • Count / Length Eigenschaften und Indexer in impliziten Indexern
Rückgabeeigenschaften für Delegaten
  • Stellen Sie sicher, dass die Erweiterungseigenschaften dieses Shapes nur in LINQ-Abfragen relevant sind, ähnlich wie die Eigenschaften einer Instanz. (Antwort: sinnvoll, LDM 2025-04-06)
Listen- und Verteilungsmuster
  • Bestätigen Sie, dass Erweiterungsindexer Index/Range in Listenmustern funktionieren sollen
Revidieren, wo Count/Length Erweiterungseigenschaften ins Spiel kommen

Sammlungsausdrücke

  • Erweiterung Add funktioniert
  • Erweiterungsarbeiten GetEnumerator für Spread
  • Die Erweiterung GetEnumerator wirkt sich nicht auf die Bestimmung des Elementtyps aus (muss Instanz sein)
  • Statische Create Erweiterungsmethoden sollten nicht als eine gesegnete Erstellungsmethode zählen
  • Sollten erweiterbare zählbare Eigenschaften Sammlungsausdrücke beeinflussen?

params Collections

  • Erweiterungen Add wirken sich nicht auf die zulässigen Typen aus params

Wörterbuchausdrücke

  • Vergewissern Sie sich, dass Erweiterungsindexer nicht in Wörterbuchausdrücken verwendet werden, da das Vorhandensein des Indexers ein integraler Bestandteil der Definition eines Wörterbuchtyps ist.

extern

Benennungs-/Nummerierungsschema für Skeletttyp

Problem
Das aktuelle Nummerierungssystem verursacht Probleme mit der Validierung öffentlicher APIs, wodurch sichergestellt wird, dass öffentliche APIs zwischen Referenzassemblys und Implementierungsassemblys übereinstimmen.

Sollten wir eine der folgenden Änderungen vornehmen? (Antwort: Wir passen das Tool an und optimieren die Implementierung der Nummerierung, LDM 2025-05-05)

  1. Anpassen des Tools
  2. Verwenden eines inhaltsbasierten Benennungsschemas (TBD)
  3. Lassen Sie den Namen über eine Syntax steuern

Die neue generische Erweiterungsmethode Cast funktioniert in LINQ immer noch nicht.

Problem
In früheren Entwürfen von Rollen/Erweiterungen war es möglich, nur die Typargumente der Methode explizit anzugeben.
Da wir uns jedoch auf den scheinbarlosen Übergang von klassischen Erweiterungsmethoden konzentrieren, müssen alle Typargumente explizit angegeben werden.
Dies behebt kein Problem bei der Verwendung der Cast-Methode in LINQ.

Sollten wir eine Änderung des Erweiterungsfeatures vornehmen, um dieses Szenario zu berücksichtigen? (Antwort: Nein, dies führt nicht dazu, dass wir den Entwurf zur Erweiterungsauflösung, LDM 2025-05-05, überarbeiten.)

Einschränken des Erweiterungsparameters für ein Erweiterungselement

Sollten wir Folgendes zulassen? (Antwort: Nein, dies könnte später hinzugefügt werden)

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-Zulässigkeit

  • Bestätigen Sie den aktuellen Entwurf, dh maximale Portabilität/Kompatibilität (Antwort: Ja, 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!;
    }

Metadaten

  • Sollten Skelettmethoden NotSupportedException oder eine andere Standardausnahme werfen (momentan throw null;)? (Antwort: Ja, LDM 2025-04-17)
  • Sollten wir mehr als einen Parameter in der Markermethode in Metadaten akzeptieren (falls neue Versionen weitere Informationen hinzufügen)? (Antwort: wir können streng bleiben, LDM 2025-04-17)
  • Soll die Erweiterungsmarkierung oder die sprechenden Implementierungsmethoden mit einem speziellen Namen gekennzeichnet werden? (Antwort: Die Markermethode sollte mit besonderem Namen gekennzeichnet werden, und wir sollten sie überprüfen, aber keine Implementierungsmethoden, LDM 2025-04-17)
  • Sollten wir ein Attribut zu der statischen Klasse [Extension] hinzufügen, auch wenn keine Instanzerweiterungsmethode darin vorhanden ist? (Antwort: Ja, LDM 2025-03-10)
  • Bestätigen Sie, dass wir das Attribut [Extension] auch zu den Implementierungs-Gettern und -Settern hinzufügen sollten. (Antwort: nein, LDM 2025-03-10)
  • Vergewissern Sie sich, dass die Skeletttypen mit einem speziellen Namen gekennzeichnet werden müssen, und der Compiler erfordert dieses Flag in den Metadaten (dies ist eine einschneidende Änderung im Vergleich zur Vorschau).

Szenario für statische Factory

  • Was sind die Konfliktregeln für statische Methoden? (Antwort: Verwenden vorhandener C#-Regeln für den eingeschlossenen statischen Typ, keine Lockerung, LDM 2025-03-17)

Suche

  • Wie können Sie Instanzmethodenaufrufe jetzt auflösen, da wir über sprechende Implementierungsnamen verfügen? Wir bevorzugen die Skelettmethode gegenüber der entsprechenden Implementierungsmethode.
  • Wie behebt man statische Erweiterungsmethoden? (Antwort: genau wie Instanzerweiterungsmethoden, LDM 2025-03-03)
  • Wie können Sie Eigenschaften auflösen? (antwortet in breiten Strichen LDM 2025-03-03, aber bedarf der Nachverfolgung zur Verbesserung)
  • Gültigkeitsbereichs- und Überschattungsregeln für Erweiterungsparameter und Typparameter (Antwort: im Gültigkeitsbereich des Erweiterungsblocks, Überschattung nicht zulässig, LDM 2025-03-10)
  • Wie sollte ORPA auf neue Erweiterungsmethoden angewendet werden? (Antwort: Erweiterungsblöcke sollten als transparent behandelt werden, der "enthaltende Typ" für ORPA ist die umschließende statische Klasse, LDM 2025-04-17)
public static class Extensions
{
    extension(Type1)
    {
        [OverloadResolutionPriority(1)]
        public void Overload(...)
    }
    extension(Type2)
    {
        public void Overload(...)
    }
}
  • Sollte ORPA auf neue Erweiterungseigenschaften angewendet werden? (Antwort: Ja, und ORPA sollen auf die Implementierungsmethoden kopiert werden, LDM 2025-04-23)
public static class Extensions
{
    extension(int[] i)
    {
        public P { get => }
    }
    extension(ReadOnlySpan<int> r)
    {
       [OverloadResolutionPriority(1)]
       public P { get => }
    }
}
  • Wie kann man die klassischen Regeln zur Erweiterungsauflösung retkonisieren? Machen wir?
    1. aktualisieren Sie den Standard für klassische Erweiterungsmethoden, und verwenden Sie diese, um auch neue Erweiterungsmethoden zu beschreiben,
    2. Behalten Sie die bestehende Sprache für klassische Erweiterungsmethoden bei und verwenden Sie diese auch, um neue Erweiterungsmethoden zu beschreiben, wobei es für beide eine bekannte Spezifikationsabweichung geben soll.
    3. die vorhandene Sprache für klassische Erweiterungsmethoden beibehalten, aber für neue Erweiterungsmethoden andere Sprache verwenden und nur eine bekannte Spezifikationsabweichung für klassische Erweiterungsmethoden haben?
  • Vergewissern Sie sich, dass explizite Typargumente für einen Eigenschaftszugriff nicht zulässig sein sollen (Antwort: kein Eigenschaftszugriff mit expliziten Typargumenten, die in der WG erläutert werden)
string s = "ran";
_ = s.P<object>; // error

static class E
{
    extension<T>(T t)
    {
        public int P => 0;
    }
}
  • Vergewissern Sie sich, dass Bessere Regeln angewendet werden sollen, auch wenn der Empfänger ein Typ ist.
int.M();

static class E1
{
    extension(int)
    {
        public static void M() { }
    }
}
static class E2
{
    extension(in int i)
    {
        public static void M() => throw null;
    }
}
  • Vergewissern Sie sich, dass wir uns nicht für eine Verbesserung über alle Mitglieder hinweg entscheiden, bevor wir die Art des Gewinnermitglieds bestimmen.
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;
    }
}
  • Haben wir einen impliziten Empfänger innerhalb von Erweiterungsdeklarationen? (Antwort: Nein, wurde zuvor in LDM diskutiert)
static class E
{
    extension(object o)
    {
        public void M() 
        {
            M2();
        }
        public void M2() { }
    }
}
  • Soll die Suche nach typparametern zugelassen werden? (Diskussion) (Antwort: Nein, wir warten auf Feedback, LDM 2025-04-16)

Zugänglichkeit

  • Was bedeutet die Barrierefreiheit in einer Erweiterungserklärung? (Antwort: Erweiterungsdeklarationen zählen nicht als Barrierefreiheitsbereich, LDM 2025-03-17)
  • Sollten wir die "uneinheitliche Zugänglichkeit" auf den Empfängerparameter anwenden, auch für statische Elemente? (Antwort: Ja, 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 { }
}

Überprüfung von Erweiterungsdeklarationen

  • Sollten wir die Typparameterüberprüfung entspannen (Schlussfolgerung: Alle Typparameter müssen im Typ des Erweiterungsparameters angezeigt werden), wo nur Methoden vorhanden sind? (Antwort: Ja, LDM 2025-04-06) Dies würde das Portieren von 100% klassischer Erweiterungsmethoden ermöglichen.
    Wenn Sie TResult M<TResult, TSource>(this TSource source) haben, könnten Sie es als extension<TResult, TSource>(TSource source) { TResult M() ... } übertragen.

  • Bestätigen Sie, ob init-only-Accessoren in Erweiterungen zulässig sein sollen (Antwort: okay, jetzt nicht zulassen, LDM 2025-04-17)

  • Sollte der einzige Unterschied im Bezug auf 'Ref-Ness' des Empfängers erlaubt sein extension(int receiver) { public void M2() {} }extension(ref int receiver) { public void M2() {} }? (Antwort: Nein, angegebene Regel beibehalten, LDM 2025-03-24)

  • Sollten wir uns über einen Konflikt wie diesen extension(object receiver) { public int P1 => 1; }extension(object receiver) { public int P1 {set{}} }beschweren? (Antwort: Ja, Regel beibehalten, LDM 2025-03-24)

  • Sollten wir uns über Konflikte zwischen Gerüstmethoden beschweren, die nicht als Konflikte zwischen Implementierungsmethoden gelten? (Antwort: Ja, Regel beibehalten, LDM 2025-03-24)

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

Die aktuellen Konfliktregeln sind: 1. Überprüfen Sie keinen Konflikt innerhalb ähnlicher Erweiterungen mithilfe von Klassen-/Strukturregeln, 2. Überprüfen Sie keinen Konflikt zwischen Implementierungsmethoden in verschiedenen Erweiterungsdeklarationen.

  • Brauchen wir den ersten Teil der Regeln? (Antwort: Ja, wir behalten diese Struktur, da sie die Nutzung der APIs erleichtert, LDM 2025-03-24)

XML-Dokumente

  • Wird der Empfängerparameter paramref für Erweiterungsmitglieder unterstützt? Sogar statisch? Wie wird sie in der Ausgabe codiert? Wahrscheinlich würde die Standardmethode <paramref name="..."/> für einen Menschen funktionieren, aber möglicherweise sind einige vorhandene Tools nicht zufrieden, wenn sie es nicht unter den Parametern der API finden. (Antwort: Ja, parameterreferenz zum Erweiterungsparameter ist bei Erweiterungselementen erlaubt, LDM 2025-05-05)
  • Sollen wir Doc-Kommentare in die Implementierungsmethoden mit sprechenden Namen kopieren? (Antwort: kein Kopieren, LDM 2025-05-05)
  • Soll das <param>-Element, das dem Empfängerparameter entspricht, aus dem Erweiterungscontainer für Instanzmethoden kopiert werden? Soll alles andere von den Containern in die Implementierungsmethoden (<typeparam> usw.) kopiert werden? (Antwort: kein Kopieren, LDM 2025-05-05)
  • Sollte <param> als Überschreibung bei Erweiterungsmitgliedern für Erweiterungsparameter zulässig sein? (Antwort: Nein, vorerst, LDM 2025-05-05)
  • Wird die Zusammenfassung von Erweiterungsblöcken irgendwo angezeigt werden?

CREF

  • Syntax bestätigen (Antwort: Vorschlag ist gut, LDM 2025-06-09)
  • Sollte es möglich sein, auf einen Erweiterungsblock (E.extension(int))zu verweisen? (Antwort: nein, LDM 2025-06-09)
  • Sollte es möglich sein, auf ein Mitglied mit einer nicht qualifizierten Syntax zu verweisen: extension(int).Member? (Antwort: Ja, LDM 2025-06-09)
  • Sollten wir unterschiedliche Zeichenfolgen für unaussprechliche Namen verwenden, um XML-Escapezeichen zu vermeiden? (Antwort: der WG überlassen, LDM 2025-06-09)
  • Vergewissern Sie sich, dass sowohl Verweise auf Skelett- als auch Implementierungsmethoden möglich sind: E.M vs. E.extension(int).M Beide scheinen notwendig (Erweiterungseigenschaften und Portabilität klassischer Erweiterungsmethoden). (Antwort: Ja, LDM 2025-06-09)
  • Sind Erweiterungsmetadatennamen für versionsverwaltungsdokumente problematisch?

Hinzufügen von Support für weitere Mitgliedstypen

Wir müssen nicht alle dieses Design gleichzeitig implementieren, sondern können es jeweils einer oder mehreren Mitgliedstypen nähern. Basierend auf bekannten Szenarien in unseren Kernbibliotheken sollten wir in der folgenden Reihenfolge arbeiten:

  1. Eigenschaften und Methoden (Instanz und statisch)
  2. Betriebspersonal
  3. Indexer (Instanz und statisch, können opportunistisch zu einem früheren Zeitpunkt durchgeführt werden)
  4. Alles andere

Wie viel möchten wir das Design für andere Arten von Mitgliedern vorab laden?

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
    ;

Geschachtelte Typen

Wenn wir uns entscheiden, mit geschachtelten Erweiterungstypen vorwärts zu gehen, finden Sie hier einige Hinweise aus früheren Diskussionen:

  • Es wäre ein Konflikt, wenn zwei Erweiterungsdeklarationen geschachtelte Erweiterungstypen mit denselben Namen und Zugehörigkeit deklariert haben. Wir haben keine Lösung für die Darstellung in Metadaten.
  • Der grobe Ansatz, den wir für Metadaten diskutiert haben:
    1. wir würden einen geschachtelten Skeletttyp mit ursprünglichen Typparametern und keine Member ausgeben.
    2. wir würden einen geschachtelten Implementierungstyp mit vordefinierten Typparametern aus der Erweiterungsdeklaration und allen Memberimplementierungen ausgeben, wie sie in der Quelle angezeigt werden (Moduloverweise auf Typparameter)

Erbauer

Konstruktoren werden im Allgemeinen als Instanzmemm in C# beschrieben, da ihr Text über das this Schlüsselwort Zugriff auf den neu erstellten Wert hat. Dies funktioniert jedoch nicht gut für den parameterbasierten Ansatz für Instanzerweiterungsmember, da es keinen vorherigen Wert gibt, der als Parameter übergeben werden soll.

Stattdessen funktionieren Erweiterungskonstruktoren eher wie statische Factorymethoden. Sie gelten als statische Member im Sinne, dass sie nicht von einem Empfängerparameternamen abhängen. Ihre Körper müssen explizit das Bauergebnis erstellen und zurückgeben. Das Element selbst wird weiterhin mit der Konstruktorsyntax deklariert, kann aber nicht über oder this Initialisierer verfügen base und setzt nicht auf den Empfängertyp mit barrierefreien Konstruktoren.

Dies bedeutet auch, dass Erweiterungskonstruktoren für Typen deklariert werden können, die keine eigenen Konstruktoren haben, z. B. Schnittstellen und Enumerationstypen:

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

Erlaubt:

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

Kürzere Formen

Der vorgeschlagene Entwurf vermeidet die Wiederholung der Empfängerspezifikationen pro Member, führt jedoch dazu, dass Erweiterungsmember zweimal in einer statischen Klasse und Erweiterungsdeklaration geschachtelt werden. Es wird wahrscheinlich vorkommen, dass statische Klassen nur eine Erweiterungsdeklaration enthalten oder erweiterungsdeklarationen nur ein Element enthalten, und es scheint plausibel, dass wir syntaktische Abkürzungen dieser Fälle zulassen.

Zusammenführen statischer Klassen- und Erweiterungsdeklarationen:

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

Dies sieht dann eher so aus, als hätten wir einen "typbasierten" Ansatz aufgerufen, bei dem der Container für Erweiterungsmember selbst benannt wird.

Erweiterungsdeklaration und Erweiterungselement zusammenführen:

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

Dies sieht dann eher so aus, als hätten wir einen "memberbasierten" Ansatz aufgerufen, bei dem jedes Erweiterungsmitglied eine eigene Empfängerspezifikation enthält.