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, einen Typ extension
zu benennen.
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, wenn die Member denselben Namen wie die Typparameter oder der Empfängerparameter der eingeschlossenen Erweiterungsdeklaration 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.
Member führen dazu, dass statischen Methoden direkt in der einschließenden statischen Klasse deklariert werden, und diese können durch einfache Namenssuche gefunden werden. Allerdings wird zuerst ein gleichnamiger Typparameter der Erweiterungsdeklaration oder ein gleichnamiger Empfängerparameter 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 statischen Membern ohne Erweiterung 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. Bei der Empfängerspezifikation kann es sich um einen Parameter, oder – wenn nur statische Erweiterungsmember deklariert werden – einen Typ handeln:
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 unten aufgeführten Refness-Modifizierer und andernfalls scoped
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). Instanzmember 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.
Es ist ein Fehler, ein Erweiterungsmember mit dem [ModuleInitializer]
-Attribut zu ergänzen.
Refness
Standardmäßig wird der Empfänger wie andere Parameter an Instanzerweiterungsmember nach Wert übergeben.
Ein Erweiterungsdeklarationsempfänger im Parameterformular kann jedoch ref
, ref readonly
und in
angeben, solange der Empfängertyp als Werttyp bekannt ist.
Wenn ref
angegeben ist, kann ein Instanzmember oder einer seiner Accessoren als readonly
deklariert werden, wodurch verhindert wird, dass der Empfänger geändert werden 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;
}
NULL-Zulässigkeit und Attribute
Empfängertypen können Verweistypen sein oder enthalten, die Nullwerte zulassen, und Empfängerspezifikationen in Form von Parametern 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 zusammengesetzt sind:
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) { ... }
}
}
Dies ermöglicht die Deklaration und Ableitung von Typparametern und entspricht der Deklaration eines normalen benutzerdefinierten Operators innerhalb eines seiner Operandentypen.
Wird geprüft
Ableitbarkeit: Für jedes Nicht-Methoden-Erweiterungsmember müssen alle Typparameter des Erweiterungsblocks in der kombinierten Gruppe von Parametern aus der Erweiterung und dem Member 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 umfasst klassische Erweiterungsmethoden innerhalb derselben statischen Klasse.
Für den Vergleich mit Methoden innerhalb von Erweiterungsdeklarationen wird der this
-Parameter zusammen mit den in diesem Empfängertyp genannten Typparametern als Empfängerspezifikation behandelt, und die übrigen Typ- 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
Bei dem Versuch, eine Erweiterungsmembersuche durchzuführen, bringen alle Erweiterungsdeklarationen innerhalb statischer Klassen, die durch using
importiert werden, ihre Member als Kandidaten ein, unabhängig vom Empfängertyp. Nur im Rahmen der Auflösung werden 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.
Dies ermöglicht es dem Compiler, zwischen Erweiterungsmembern mit demselben Namen und derselben Stelligkeit zu 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 einschließende statische Klasse wird als „enthaltender Typ“ betrachtet, der von den ORPA-Regeln berücksichtigt wird.
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 kommen nicht als Einstiegspunkte in Frage (siehe „7.1 Anwendungsstart“). Hinweis: Die Implementierungsmethode kann immer noch ein Kandidat sein.
Reduzierung
Die Herabsetzungsstrategie 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 Consumer, die mit früheren Versionen kompiliert wurden, nicht unterbrechen sollten.
Diese Anforderungen müssen im Laufe der Umsetzung weiter verfeinert werden, und in einigen Fällen müssen möglicherweise Kompromisse eingegangen werden, um einen vernünftigen Implementierungsansatz zu ermöglichen.
Metadaten für Deklarationen
Jede Erweiterungsdeklaration wird als Erweiterungstyp mit einer Markierungsmethode und Erweiterungsmembern definiert.
Jedes Erweiterungselement 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.
Erweiterungstypen
Jede Erweiterungsdeklaration in der Quelle wird als Erweiterungsdeklaration in Metadaten (manchmal als Skeletttyp bezeichnet) ausgegeben.
- Der Name ist unaussprechlich und wird basierend auf der lexikalischen Reihenfolge im Programm bestimmt.
Es ist nicht garantiert, dass der Name bei einer Neukompilierung stabil bleibt. Nachfolgend wird<>E__
gefolgt von einem Index verwendet. Beispiel:<>E__2
. - Die Typparameter sind die in der Quelle deklarierten Parameter (einschließlich der Attribute).
- Der Zugriff darauf ist öffentlich.
- Sie ist mit der
specialname
Kennzeichnung gekennzeichnet.
Methoden-/Eigenschaftsdeklarationen innerhalb einer Erweiterungsdeklaration im Quelltext werden als Mitglieder des Erweiterungstyps in den Metadaten dargestellt.
Die Signaturen der ursprünglichen Methoden werden beibehalten (einschließlich der Attribute), aber ihre Texte werden durch throw null
ersetzt.
Auf diese sollte in IL nicht verwiesen werden.
Hinweis: Dies ist vergleichbar mit Referenzassemblys. Der Grund für die Verwendung von throw null
-Texten (im Gegensatz zu keinen Texten) ist, dass die IL-Verifizierung erfolgreich ausgeführt werden kann (und so wird die Vollständigkeit der Metadaten überprüft).
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 Roundtrips von Symbolen der Erweiterungsdeklaration über die Metadaten (vollständige Assemblys und Referenzassemblys).
Hinweis: Wir können nur einen Erweiterungstyp 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.
- Sie verfügt über die gleiche Zugänglichkeit und die gleichen Attribute wie die ursprüngliche Methode.
- Wenn sie eine statische Methode implementiert, weist sie dieselben Parameter und denselben Rückgabetyp auf.
- Wenn sie eine Instanzmethode implementiert, 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 wie folgt ausgegeben:
[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 Erweiterungsmember in der Quelle verwendet werden, geben wir diese als Verweis auf Implementierungsmethoden aus.
Beispiel: Ein Aufruf von enumerableOfInt.Method()
wird als statischer Aufruf von IEnumerableExtensions.Method<int>(enumerableOfInt)
ausgegeben.
XML-Dokumente
Die Dokumentkommentare zum Erweiterungsblock werden für den unaussprechlich benannten Typ ausgegeben (im folgenden Beispiel ist die DocID für den Erweiterungsblock <>E__0'1
).
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 <param>
und <typeparam>
aus dem Erweiterungsblock nach Bedarf in die Erweiterungsmember zu kopieren (d. h. die Parameterinformationen sollten nur für Instanzmember kopiert werden).
Ein <inheritdoc>
wird für Implementierungsmethoden ausgegeben und bezieht sich auf das relevante Erweiterungsmember mit einem cref
. Die Implementierungsmethode für einen Getter bezieht sich beispielsweise auf die Dokumentation der Erweiterungseigenschaft.
Wenn das Erweiterungsmember keine Dokumentkommentare enthält, wird das <inheritdoc>
weggelassen.
Bei Erweiterungsblöcken und Erweiterungsmembern werden gegenwärtig keine Warnungen ausgegeben, 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:
<?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)"
}
}
Der Lookup weiß, dass er in allen übereinstimmenden Erweiterungsblöcken suchen muss.
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: Dies ermöglicht keinen cref-Verweis auf den Erweiterungsblock, 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(Antwort: Ja, ablehnen, LDM 2025-06-11)[ModuleInitializer]
ablehnen möchtenVergewissern 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 (neue Erweiterungen überspringen oder sie in Betracht ziehen und melden, wenn sie ausgewählt werden)(Antwort: Bedingungslos binden und LangVer-Fehler melden, mit Ausnahme von Instanzerweiterungsmethoden, LDM 2025-06-11)- Sollten wir die Empfängeranforderungen beim Zugriff auf ein Erweiterungsmitglied anpassen? (Kommentar) Beispiel:
new Struct() { Property = 42 }
. - Revidieren von Gruppierungs-/Konfliktregeln im Lichte der Portabilitätsfrage: https://github.com/dotnet/roslyn/issues/79043
nameof
Sollten wir Erweiterungseigenschaften in nameof unterbinden, ebenso wie klassische und neue Erweiterungsmethoden?(Antwort: wir möchten 'nameof(EnclosingStaticClass.ExtensionMember) verwenden. Designbedingt, wahrscheinlich Punt von .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, Positionsmuster und foreach -
Add
in Auflistungsinitialisierern -
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:
- Objektinitialisierer:
new C() { ExtensionProperty = ... }
- Wörterbuchinitialisierer:
new C() { [0] = ... }
with
:x with { ExtensionProperty = ... }
- Eigenschaftsmuster:
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
Eigenschaften, die Delegaten zurückgeben
Stellen Sie sicher, dass die Erweiterungseigenschaften dieser Form nur in LINQ-Abfragen relevant sind, ebenso wie Instanzeigenschaften.(Antwort: sinnvoll, LDM 2025-04-06)
Listen- und Verteilungsmuster
- Bestätigen, dass Erweiterungs-
Index
/Range
-Indexer in Listenmustern relevant sind
Überarbeiten, wo Count
/Length
-Erweiterungseigenschaften ins Spiel kommen
Sammlungsausdrücke
- Erweiterung
Add
funktioniert - Erweiterung
GetEnumerator
funktioniert für die Verteilung - Die Erweiterung
GetEnumerator
wirkt sich nicht auf die Bestimmung des Elementtyps aus (muss Instanz sein) - Statische
Create
-Erweiterungsmethoden sollten nicht als empfohlene Erstellungsmethode zählen. - Sollten zählbare Erweiterungseigenschaften Sammlungsausdrücke beeinflussen?
params
Collections
- Die Erweiterung
Add
wirkt sich nicht darauf aus, welche Typen fürparams
zulässig sind.
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, die Portabilität zu ermöglichenhttps://github.com/dotnet/roslyn/issues/78572:(Antwort: genehmigt, LDM 2025-06-23)extern
Benennungs-/Nummerierungsschema für Erweiterungstyp
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)
- Tool anpassen
- 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.
Dadurch wird ein Problem mit der Verwendung der Cast-Erweiterungsmethode in LINQ nicht behoben.
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 Gerüstmethoden(Antwort: Ja, LDM 2025-04-17)NotSupportedException
oder eine andere Standardausnahme auslösen (momentan wirdthrow null;
verwendet)?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.-
Bestätigen Sie, dass die Erweiterungstypen mit einem besonderen Namen gekennzeichnet werden sollen und dass der Compiler dieses Flag in Metadaten erfordert (dies ist ein Breaking Change im Vergleich zur Vorschau)(Antwort: genehmigt, LDM 2025-06-23) - Bestätigen Sie, dass
ref
nicht im Namen des Erweiterungstyps enthalten sein sollte (bedarf weiterer Erläuterung nach der erneuten Prüfung der Gruppierungs-/Konfliktregeln durch die WG, LDM 2025-06-23)
public static class E
{
extension(ref int)
{
public static void M()
}
}
Es wird ausgegeben als:
public static class E
{
public static class <>ExtensionTypeXYZ
{
.. marker method ...
void M()
}
}
Und der Drittanbieter-CREF-Verweis für E.extension(ref int).M
wird als M:E.<>ExtensionTypeXYZ.M()
ausgegeben.
Wenn ref
bei einem Erweiterungsparameter entfernt oder hinzugefügt wird, sollte der CREF nicht unterbrochen werden.
Szenario für statische Factorymethode
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)
Nachschlagen
Wie können Sie Instanzmethodenaufrufe jetzt auflösen, da wir über sprechende Implementierungsnamen verfügen?Wir bevorzugen die Gerüstmethode gegenüber der entsprechenden Implementierungsmethode.Wie behebt man statische Erweiterungsmethoden?(Antwort: genau wie Instanzerweiterungsmethoden, LDM 2025-03-03)Wie werden Eigenschaften aufgelöst?(in LDM 2025-03-03 grob beantwortet, aber bedarf der Nachverfolgung zur Verbesserung)-
Gültigkeitsbereichs- und Shadowing-Regeln für Erweiterungsparameter und Typparameter(Antwort: im Gültigkeitsbereich des Erweiterungsblocks, Shadowing 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 sollte in 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 lassen sich die klassischen Regeln für die Auflösung von Erweiterungen ändern? Gehen wir folgendermaßen vor?
- 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;
}
}
-
Bestätigen Sie, dass Verbesserungsregeln auch dann angewendet werden sollen, wenn der Empfänger ein Typ ist(Antwort: Ein reiner Typ-Erweiterungsparameter sollte bei der Auflösung statischer Erweiterungsmember berücksichtigt werden, 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;
}
}
- Vergewissern Sie sich, dass wir mit einer Zweideutigkeit einverstanden sind, wenn sowohl Methoden als auch Eigenschaften anwendbar sind (Antwort: Wir sollten einen Vorschlag entwerfen, um über den Status quo hinauszugehen, ohne für .NET 10 zu blockieren, LDM 2025-06-23)
- Bestätigen Sie, dass wir nicht ein gewisses Maß an Verbesserung für alle Member wünschen, bevor wir den gewinnenden Membertyp 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 die Prüfung des „inkonsistenten Zugriffs“ auf den Empfängerparameter auch für statische Member angewendet werden?(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
Sollte die Überprüfung des Typparameters weniger streng sein (Ableitbarkeit: alle Typparameter müssen im Typ des Erweiterungsparameters angezeigt werden), wenn 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: können derzeit unterbunden werden, LDM 2025-04-17)Sollte der einzige Unterschied in Bezug auf Refness 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, spezifizierte 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, spezifizierte 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(Antwort: Ja, paramref auf den Erweiterungsparameter ist bei Erweiterungsmembern erlaubt, LDM 2025-05-05)paramref
auf den Empfängerparameter für Erweiterungsmember unterstützt? Selbst bei statischen? 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 Dokumentkommentare in die Implementierungsmethoden mit aussprechbaren 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>
für Erweiterungsparameter bei Erweiterungsmembern als Überschreibung 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 Zeichen für unaussprechliche Namen verwenden, um XML-Escaping 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?
Support für weitere Memberarten hinzufügen
Wir müssen nicht alles auf einmal umsetzen, sondern können jeweils eine oder einige wenige Memberarten hinzufügen. Basierend auf bekannten Szenarien in unseren Kernbibliotheken ist folgende Reihenfolge zu berücksichtigen:
- Eigenschaften und Methoden (Instanz und statisch)
- Betriebspersonal
- Indexer (Instanz und statisch, können opportunistisch zu einem früheren Zeitpunkt durchgeführt werden)
- Alles andere
Inwieweit sollte der Entwurf für andere Arten von Membern vorverlagert werden?
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 verschachtelten Erweiterungstypen fortzufahren, finden Sie hier einige Anmerkungen aus früheren Diskussionen:
- Es würde einen Konflikt geben, wenn zwei Erweiterungsdeklarationen verschachtelte Erweiterungstypen mit demselben Namen und derselben Stelligkeit deklarieren. Wir haben keine Lösung, um dies in den Metadaten darzustellen.
- Der grobe Ansatz, den wir für Metadaten diskutiert haben:
- Wir würden einen geschachtelten Gerüsttyp mit ursprünglichen Typparametern und ohne 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 (Modulo-Verweise auf Typparameter).
Erbauer
Konstruktoren werden im Allgemeinen als Instanzmember 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 Mitglieder in dem Sinne, dass sie nicht von einem Empfängerparameternamen abhängen.
Ihre Texte müssen das Konstruktionsergebnis explizit erstellen und zurückgeben.
Das Member selbst wird weiterhin mit der Konstruktorsyntax deklariert, kann aber nicht über this
- oder base
-Initialisierer verfügen und ist nicht davon abhängig, ob der Empfängertyp zugängliche Konstruktoren hat.
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) { ... }
}
Zulässig:
var range = new IEnumerable<int>(1, 100);
Kürzere Formen
Der vorgeschlagene Entwurf vermeidet die Wiederholung von Empfängerspezifikationen pro Member, führt aber dazu, dass Erweiterungsmember in einer statischen Klassen- und Erweiterungsdeklaration verschachtelt sind. Es wird wahrscheinlich vorkommen, dass statische Klassen nur eine Erweiterungsdeklaration enthalten oder dass Erweiterungsdeklarationen nur ein Member 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 ähnelt eher einem sogenannten „typbasierten“ Ansatz, 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 ähnelt eher einem sogenannten „Member-basierten“ Ansatz, bei dem jedes Erweiterungsmitglied seine eigene Empfängerspezifikation enthält.
C# feature specifications