Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
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 extension
werden 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
, partial
und 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 ref
ref readonly
und in
, solange der Empfängertyp als Werttyp bekannt ist.
Wenn ref
angegeben, kann ein Instanzmitglied oder eine seiner Accessoren deklariert readonly
werden, 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 null
ersetzt.
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.<>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.<>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.<>E__0`1.P">
<summary>Summary for P</summary>
</member>
<member name="M:E.M``2(``0,``1)">
<inheritdoc cref="M:E.<>E__0`1.M``1(``0)"/>
</member>
<member name="M:E.get_P``1(``0)">
<inheritdoc cref="P:E.<>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:(Antwort:extension
oderextensions
als Schlüsselwortextension
, LDM 2025-03-24) -
Bestätigen, dass wir nicht zulassen(Antwort: Ja, nicht zulassen, LDM 2025-06-11)[ModuleInitializer]
möchten -
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
inforeach
-
Deconstruct
in Dekonstruktion, in Positionsmustern und Vorgabe -
Add
in Sammlungsinitialisierern -
GetPinnableReference
infixed
-
GetAwaiter
inawait
Dies schließt Folgendes aus:
-
Dispose
/DisposeAsync
inusing
undforeach
-
MoveNext
/MoveNextAsync
inforeach
-
Slice
undint
Indexer in impliziten Indexern (und eventuell Listenmustern?) -
GetResult
inawait
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
inforeach
-
IsCompleted
inawait
-
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 ausparams
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
- wir planen,
extern
im Rahmen der Portabilität zuzulassen: https://github.com/dotnet/roslyn/issues/78572
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)
- Anpassen des Tools
- Verwenden eines inhaltsbasierten Benennungsschemas (TBD)
- 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(Antwort: Ja, LDM 2025-04-17)NotSupportedException
oder eine andere Standardausnahme werfen (momentanthrow null;
)?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(Antwort: Ja, LDM 2025-03-10)[Extension]
hinzufügen, auch wenn keine Instanzerweiterungsmethode darin vorhanden ist?Bestätigen Sie, dass wir das Attribut(Antwort: nein, LDM 2025-03-10)[Extension]
auch zu den Implementierungs-Gettern und -Settern hinzufügen sollten.- 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?
- aktualisieren Sie den Standard für klassische Erweiterungsmethoden, und verwenden Sie diese, um auch neue Erweiterungsmethoden zu beschreiben,
- 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.
- 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 SieTResult M<TResult, TSource>(this TSource source)
haben, könnten Sie es alsextension<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(Antwort: Nein, angegebene Regel beibehalten, LDM 2025-03-24)extension(int receiver) { public void M2() {} }
extension(ref int receiver) { public void M2() {} }
?Sollten wir uns über einen Konflikt wie diesen(Antwort: Ja, Regel beibehalten, LDM 2025-03-24)extension(object receiver) { public int P1 => 1; }
extension(object receiver) { public int P1 {set{}} }
beschweren?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(Antwort: Ja, parameterreferenz zum Erweiterungsparameter ist bei Erweiterungselementen erlaubt, LDM 2025-05-05)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.Sollen wir Doc-Kommentare in die Implementierungsmethoden mit sprechenden Namen kopieren?(Antwort: kein Kopieren, LDM 2025-05-05)Soll das(Antwort: kein Kopieren, LDM 2025-05-05)<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?Sollte(Antwort: Nein, vorerst, LDM 2025-05-05)<param>
als Überschreibung bei Erweiterungsmitgliedern für Erweiterungsparameter zulässig sein?- 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 ((Antwort: nein, LDM 2025-06-09)E.extension(int)
)zu verweisen?Sollte es möglich sein, auf ein Mitglied mit einer nicht qualifizierten Syntax zu verweisen:(Antwort: Ja, LDM 2025-06-09)extension(int).Member
?- 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:(Antwort: Ja, LDM 2025-06-09)E.M
vs.E.extension(int).M
Beide scheinen notwendig (Erweiterungseigenschaften und Portabilität klassischer Erweiterungsmethoden).- 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:
- Eigenschaften und Methoden (Instanz und statisch)
- Betriebspersonal
- Indexer (Instanz und statisch, können opportunistisch zu einem früheren Zeitpunkt durchgeführt werden)
- 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:
- wir würden einen geschachtelten Skeletttyp mit ursprünglichen Typparametern und keine Member ausgeben.
- 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.
C# feature specifications