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.
Erweiterungsmember ermöglichen es Ihnen, vorhandenen Typen Methoden hinzuzufügen, ohne einen neuen abgeleiteten Typ zu erstellen, neu zu kompilieren oder anderweitig den ursprünglichen Typ zu ändern.
Ab C# 14 gibt es zwei Syntaxen, die Sie zum Definieren von Erweiterungsmethoden verwenden. C# 14 fügt extension
Container hinzu, bei denen Sie mehrere Erweiterungsmember für einen Typ oder eine Instanz eines Typs definieren. Vor C# 14 fügen Sie den this
Modifizierer zum ersten Parameter einer statischen Methode hinzu, um anzugeben, dass die Methode als Element einer Instanz des Parametertyps angezeigt wird.
Erweiterungsmethoden sind statische Methoden, werden jedoch aufgerufen, als wären sie Instanzmethoden für den erweiterten Typ. Für Clientcode, der in C#, F# und Visual Basic geschrieben wurde, besteht kein offensichtlicher Unterschied zwischen dem Aufrufen einer Erweiterungsmethode und der in einem Typ definierten Methoden. Beide Formen von Erweiterungsmethoden werden in derselben IL (Zwischensprache) kompiliert. Anwender von Erweiterungsmitgliedern müssen nicht wissen, welche Syntax zur Definition von Erweiterungsmethoden verwendet wurde.
Die gängigsten Erweiterungsmber sind die LINQ-Standardabfrageoperatoren, die den vorhandenen System.Collections.IEnumerable Und System.Collections.Generic.IEnumerable<T> Typen Abfragefunktionen hinzufügen. Um die Standardabfrageoperatoren zu verwenden, müssen Sie sie zuerst mit einer using System.Linq
-Direktive einbinden. Dann scheint jeder Typ, der IEnumerable<T> implementiert, Instanzmethoden wie GroupBy, OrderBy, Averageusw. enthalten. Sie können diese zusätzlichen Methoden in der IntelliSense-Anweisungsvervollständigung sehen, wenn Sie nach einer Instanz eines IEnumerable<T>-Typs, z. B. List<T> oder Array, „dot“ eingeben.
OrderBy-Beispiel
Das folgende Beispiel zeigt, wie Sie den Standardabfrageoperator OrderBy
Methode für ein Array mit ganzen Zahlen aufrufen. Der Ausdruck in Klammern ist ein Lambda-Ausdruck. Viele Standardabfrageoperatoren verwenden Lambda-Ausdrücke als Parameter. Weitere Informationen finden Sie unter Lambda-Ausdrücke.
int[] numbers = [10, 45, 15, 39, 21, 26];
IOrderedEnumerable<int> result = numbers.OrderBy(g => g);
foreach (int i in result)
{
Console.Write(i + " ");
}
//Output: 10 15 21 26 39 45
Erweiterungsmethoden werden als statische Methoden definiert, werden jedoch mithilfe der Instanzmethodensyntax aufgerufen. Der erste Parameter gibt an, auf welchen Typ die Methode angewendet wird. Der Parameter folgt diesem Modifizierer. Erweiterungsmethoden liegen nur im Gültigkeitsbereich, wenn Sie den Namespace explizit mit einer using
-Direktive in Ihren Quellcode importieren.
Deklarieren von Erweiterungsmembern
Ab C# 14 können Sie Erweiterungsblöcke deklarieren. Ein Erweiterungsblock ist ein Block in einer nicht geschachtelten, nichtgenerischen, statischen Klasse, die Erweiterungsmember für einen Typ oder eine Instanz dieses Typs enthält. Im folgenden Codebeispiel wird ein Erweiterungsblock für den string
Typ definiert. Der Erweiterungsblock enthält ein Element: eine Methode, die die Wörter in der Zeichenfolge zählt:
namespace CustomExtensionMembers;
public static class MyExtensions
{
extension(string str)
{
public int WordCount() =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
}
Vor C# 14 deklarieren Sie eine Erweiterungsmethode, indem Sie den this
Modifizierer zum ersten Parameter hinzufügen:
namespace CustomExtensionMethods;
public static class MyExtensions
{
public static int WordCount(this string str) =>
str.Split([' ', '.', '?'], StringSplitOptions.RemoveEmptyEntries).Length;
}
Beide Formen von Erweiterungen müssen in einer nicht geschachtelten, nicht generierten statischen Klasse definiert werden.
Und sie kann über eine Anwendung mithilfe der Syntax für den Zugriff auf Instanzmitglieder aufgerufen werden.
string s = "Hello Extension Methods";
int i = s.WordCount();
Erweiterungsmitglieder fügen einem vorhandenen Typ zwar neue Funktionen hinzu, aber Erweiterungsmitglieder verstoßen nicht gegen das Prinzip der Kapselung. Die Zugriffsdeklarationen für alle Mitglieder des erweiterten Typs gelten auch für Erweiterungsmitglieder.
Sowohl die MyExtensions
-Klasse als auch die WordCount
-Methode sind static
, und auf sie kann wie auf alle anderen static
-Member zugegriffen werden. Die WordCount
-Methode kann wie folgt aufgerufen werden wie andere static
Methoden:
string s = "Hello Extension Methods";
int i = MyExtensions.WordCount(s);
Der vorangehende C#-Code gilt sowohl für den Erweiterungsblock this
als auch für die Syntax für Erweiterungsmitglieder. Der vorherige Code:
- Er deklariert ein neues
string
-Element namenss
mit dem Wert"Hello Extension Methods"
und weist es zu. - Das angegebene Argument
MyExtensions.WordCount
fürs
wird aufgerufen.
Weitere Informationen finden Sie unter Implementieren und Aufrufen einer benutzerdefinierten Erweiterungsmethode.
Im Allgemeinen rufen Sie wahrscheinlich Erweiterungsmitglieder viel häufiger auf, als Sie sie implementieren. Da Erweiterungsmitglieder aufgerufen werden, als wären sie Mitglieder der erweiterten Klasse deklariert, ist kein spezielles Wissen erforderlich, um sie aus Client-Code zu verwenden. Um Erweiterungsmember für einen bestimmten Typ zu aktivieren, fügen Sie einfach eine using
Direktive für den Namespace hinzu, in dem die Methoden definiert sind. Wenn Sie z. B. die Standardabfrageoperatoren verwenden möchten, fügen Sie diese using
Direktive zu Ihrem Code hinzu:
using System.Linq;
Binden von Erweiterungsmembern während der Kompilierung
Sie können Erweiterungsmember verwenden, um eine Klasse oder Schnittstelle zu erweitern, aber nicht, um das in einer Klasse definierte Verhalten außer Kraft zu setzen. Ein Erweiterungsmitglied mit demselben Namen und derselben Signatur wie die Mitglieder einer Schnittstelle oder Klasse wird nie aufgerufen. Zur Kompilierungszeit haben Erweiterungsmember immer eine niedrigere Priorität als Instanzmember (oder statische) Elemente, die im Typ selbst definiert sind. Anders ausgedrückt: Wenn ein Typ eine Methode mit dem Namen Process(int i)
hat und Sie über eine Erweiterungsmethode mit derselben Signatur verfügen, bindet der Compiler immer an die Membermethode. Wenn der Compiler einen Memberaufruf erkennt, sucht er zuerst nach einer Entsprechung unter den Membern des Typs. Wenn keine Übereinstimmung gefunden wird, sucht sie nach Erweiterungsmitgliedern, die für den Typ definiert sind. Es wird an das erste gefundene Erweiterungselement gebunden. Im folgenden Beispiel werden die Regeln veranschaulicht, die der C#-Compiler folgt, um zu bestimmen, ob eine Bindung an ein Instanzmememm für den Typ oder an ein Erweiterungselement erfolgt. Die statische Klasse Extensions
enthält Erweiterungsmmber, die für jeden Typ definiert sind, der folgendes implementiert IMyInterface
:
public interface IMyInterface
{
void MethodB();
}
// Define extension methods for IMyInterface.
// The following extension methods can be accessed by instances of any
// class that implements IMyInterface.
public static class Extension
{
public static void MethodA(this IMyInterface myInterface, int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public static void MethodA(this IMyInterface myInterface, string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public static void MethodB(this IMyInterface myInterface) =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
Die entsprechenden Erweiterungen können mit der C# 14-Erweiterungssyntax deklariert werden.
public static class Extension
{
extension(IMyInterface myInterface)
{
public void MethodA(int i) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, int i)");
public void MethodA(string s) =>
Console.WriteLine("Extension.MethodA(this IMyInterface myInterface, string s)");
// This method is never called in ExtensionMethodsDemo1, because each
// of the three classes A, B, and C implements a method named MethodB
// that has a matching signature.
public void MethodB() =>
Console.WriteLine("Extension.MethodB(this IMyInterface myInterface)");
}
}
Klassen A
, B
und C
alle implementieren die Schnittstelle:
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
class A : IMyInterface
{
public void MethodB() { Console.WriteLine("A.MethodB()"); }
}
class B : IMyInterface
{
public void MethodB() { Console.WriteLine("B.MethodB()"); }
public void MethodA(int i) { Console.WriteLine("B.MethodA(int i)"); }
}
class C : IMyInterface
{
public void MethodB() { Console.WriteLine("C.MethodB()"); }
public void MethodA(object obj)
{
Console.WriteLine("C.MethodA(object obj)");
}
}
Die MethodB
Erweiterungsmethode wird nie aufgerufen, da ihr Name und die Signatur genau mit methoden übereinstimmen, die bereits von den Klassen implementiert wurden. Wenn der Compiler eine Instanzmethode mit einer übereinstimmenden Signatur nicht finden kann, wird sie an eine übereinstimmende Erweiterungsmethode gebunden, falls vorhanden.
// Declare an instance of class A, class B, and class C.
A a = new A();
B b = new B();
C c = new C();
// For a, b, and c, call the following methods:
// -- MethodA with an int argument
// -- MethodA with a string argument
// -- MethodB with no argument.
// A contains no MethodA, so each call to MethodA resolves to
// the extension method that has a matching signature.
a.MethodA(1); // Extension.MethodA(IMyInterface, int)
a.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// A has a method that matches the signature of the following call
// to MethodB.
a.MethodB(); // A.MethodB()
// B has methods that match the signatures of the following
// method calls.
b.MethodA(1); // B.MethodA(int)
b.MethodB(); // B.MethodB()
// B has no matching method for the following call, but
// class Extension does.
b.MethodA("hello"); // Extension.MethodA(IMyInterface, string)
// C contains an instance method that matches each of the following
// method calls.
c.MethodA(1); // C.MethodA(object)
c.MethodA("hello"); // C.MethodA(object)
c.MethodB(); // C.MethodB()
/* Output:
Extension.MethodA(this IMyInterface myInterface, int i)
Extension.MethodA(this IMyInterface myInterface, string s)
A.MethodB()
B.MethodA(int i)
B.MethodB()
Extension.MethodA(this IMyInterface myInterface, string s)
C.MethodA(object obj)
C.MethodA(object obj)
C.MethodB()
*/
Allgemeine Verwendungsmuster
Sammlungsfunktionalität
In der Vergangenheit war es üblich, "Sammlungsklassen" zu erstellen, die die System.Collections.Generic.IEnumerable<T>-Schnittstelle für einen bestimmten Typ implementierten und Funktionen enthielten, die auf Sammlungen dieses Typs wirkten. Obwohl beim Erstellen dieses Auflistungsobjekts nichts falsch ist, kann die gleiche Funktionalität mithilfe einer Erweiterung für die System.Collections.Generic.IEnumerable<T>erreicht werden. Erweiterungen haben den Vorteil, dass die Funktionalität aus einer beliebigen Sammlung – z. B. System.Array oder System.Collections.Generic.List<T> – aufgerufen werden kann, die System.Collections.Generic.IEnumerable<T> für diesen Typ implementiert. Ein Beispiel hierfür mithilfe eines Arrays von Int32 finden Sie weiter oben in diesem Artikel.
Ebenenspezifische Funktionalität
Bei Verwendung einer Onion-Architektur oder eines anderen Mehrschichtanwendungsdesigns ist es üblich, über eine Reihe von Domänenentitäten oder Datenübertragungsobjekten zu verfügen, die für die Kommunikation über Anwendungsgrenzen hinweg verwendet werden können. Diese Objekte enthalten in der Regel keine Funktionalität oder nur minimale Funktionen, die für alle Ebenen der Anwendung gelten. Erweiterungsmethoden können verwendet werden, um Funktionen hinzuzufügen, die für jede Anwendungsebene spezifisch sind.
public class DomainEntity
{
public int Id { get; set; }
public required string FirstName { get; set; }
public required string LastName { get; set; }
}
static class DomainEntityExtensions
{
static string FullName(this DomainEntity value)
=> $"{value.FirstName} {value.LastName}";
}
Sie können eine entsprechende FullName
Eigenschaft in C# 14 und höher mit der neuen Erweiterungsblocksyntax deklarieren:
static class DomainEntityExtensions
{
extension(DomainEntity value)
{
string FullName => $"{value.FirstName} {value.LastName}";
}
}
Erweitern vordefinierter Typen
Anstatt neue Objekte zu erstellen, wenn wiederverwendbare Funktionen erstellt werden müssen, können Sie häufig einen vorhandenen Typ erweitern, z. B. einen .NET- oder CLR-Typ. Wenn Sie beispielsweise keine Erweiterungsmethoden verwenden, können Sie eine Engine
oder Query
Klasse erstellen, um eine Abfrage auf einem SQL Server auszuführen, der von mehreren Stellen in unserem Code aufgerufen werden kann. Sie können stattdessen die System.Data.SqlClient.SqlConnection Klasse mithilfe von Erweiterungsmethoden erweitern, um die Abfrage von überall dort auszuführen, wo Sie eine Verbindung zu einem SQL Server haben. Andere Beispiele sind möglicherweise das Hinzufügen allgemeiner Funktionen zur System.String Klasse, das Erweitern der Datenverarbeitungsfunktionen des System.IO.Stream-Objekts und System.Exception Objekte für bestimmte Fehlerbehandlungsfunktionen. Diese Arten von Anwendungsfällen sind nur durch Ihre Fantasie und ihren guten Sinn begrenzt.
Das Erweitern vordefinierter Typen kann bei struct
-Typen schwierig sein, da sie als Wert an Methoden übergeben werden. Dies bedeutet, dass alle Änderungen an der Struktur an einer Kopie der Struktur vorgenommen werden. Diese Änderungen sind nicht sichtbar, nachdem die Erweiterungsmethode beendet wurde. Sie können den ref
Modifizierer zum ersten Argument hinzufügen, das sie zu einer ref
Erweiterungsmethode macht. Das schlüsselwort ref
kann vor oder nach dem schlüsselwort this
ohne semantische Unterschiede angezeigt werden. Durch Das Hinzufügen des ref
Modifizierers wird angegeben, dass das erste Argument per Verweis übergeben wird. Mit dieser Technik können Sie Erweiterungsmethoden schreiben, die den Status der erweiterten Struktur ändern (beachten Sie, dass auf private Member nicht zugegriffen werden kann). Nur Werttypen oder generische Typen, die auf die Struktur beschränkt sind (Weitere Informationen zu diesen Regeln finden Sie unter struct
Einschränkung für weitere Informationen) sind als erster Parameter einer ref
Erweiterungsmethode oder als Empfänger eines Erweiterungsblocks zulässig. Das folgende Beispiel zeigt, wie Sie eine ref
-Erweiterungsmethode verwenden, um einen integrierten Typ direkt zu ändern, ohne das Ergebnis neu zuweisen oder es über eine Funktion mit dem Schlüsselwort ref
übergeben zu müssen:
public static class IntExtensions
{
public static void Increment(this int number)
=> number++;
// Take note of the extra ref keyword here
public static void RefIncrement(this ref int number)
=> number++;
}
Die entsprechenden Erweiterungsblöcke werden im folgenden Code angezeigt:
public static class IntExtensions
{
extension(int number)
{
public void Increment()
=> number++;
}
// Take note of the extra ref keyword here
extension(ref int number)
{
public void RefIncrement()
=> number++;
}
}
Unterschiedliche Erweiterungsblöcke sind erforderlich, um die Parametermodi „by-value“ und „by-ref“ für den Empfänger zu unterscheiden.
Sie können den Unterschied sehen, den das Anwenden von ref
auf den Empfänger hat, im folgenden Beispiel:
int x = 1;
// Takes x by value leading to the extension method
// Increment modifying its own copy, leaving x unchanged
x.Increment();
Console.WriteLine($"x is now {x}"); // x is now 1
// Takes x by reference leading to the extension method
// RefIncrement changing the value of x directly
x.RefIncrement();
Console.WriteLine($"x is now {x}"); // x is now 2
Sie können dieselbe Technik anwenden, indem Sie ref
Erweiterungsmitglieder zu benutzerdefinierten Strukturtypen hinzufügen.
public struct Account
{
public uint id;
public float balance;
private int secret;
}
public static class AccountExtensions
{
// ref keyword can also appear before the this keyword
public static void Deposit(ref this Account account, float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
Das vorherige Beispiel kann auch mithilfe von Erweiterungsblöcken in C# 14 erstellt werden:
public static class AccountExtensions
{
extension(ref Account account)
{
// ref keyword can also appear before the this keyword
public void Deposit(float amount)
{
account.balance += amount;
// The following line results in an error as an extension
// method is not allowed to access private members
// account.secret = 1; // CS0122
}
}
}
Sie können wie folgt auf diese Erweiterungsmethoden zugreifen:
Account account = new()
{
id = 1,
balance = 100f
};
Console.WriteLine($"I have ${account.balance}"); // I have $100
account.Deposit(50f);
Console.WriteLine($"I have ${account.balance}"); // I have $150
Allgemeine Richtlinien
Es ist vorzuziehen, Funktionen hinzuzufügen, indem sie den Code eines Objekts ändern oder einen neuen Typ ableiten, wenn dies sinnvoll und möglich ist. Erweiterungsmethoden sind eine wichtige Option zum Erstellen wiederverwendbarer Funktionen im gesamten .NET-Ökosystem. Erweiterungsmember sind vorzuziehen, wenn die ursprüngliche Quelle nicht unter Ihrer Kontrolle ist, wenn ein abgeleitetes Objekt unangemessen oder unmöglich ist oder wenn die Funktionalität eingeschränkten Umfang hat.
Weitere Informationen zu abgeleiteten Typen finden Sie unter Vererbung.
Wenn Sie Erweiterungsmethoden für einen bestimmten Typ implementieren, denken Sie an die folgenden Punkte:
- Eine Erweiterungsmethode wird nicht aufgerufen, wenn sie dieselbe Signatur wie eine im Typ definierte Methode aufweist.
- Erweiterungsmethoden werden auf Namespaceebene in den Gültigkeitsbereich aufgenommen. Wenn Sie beispielsweise über mehrere statische Klassen verfügen, die Erweiterungsmethoden in einem einzigen Namespace mit dem Namen
Extensions
enthalten, werden alle von der Direktive in denusing Extensions;
Gültigkeitsbereich gebracht.
Für eine von Ihnen implementierte Klassenbibliothek sollten Sie keine Erweiterungsmethoden verwenden, um zu vermeiden, dass die Versionsnummer einer Assembly erhöht wird. Wenn Sie einer Bibliothek, für die Sie den Quellcode besitzen, erhebliche Funktionen hinzufügen möchten, befolgen Sie die .NET-Richtlinien für die Assemblyversionsverwaltung. Weitere Informationen finden Sie unter Assemblyversionsverwaltung.
Siehe auch
- Beispiele für parallele Programmierung (viele Beispiele veranschaulichen Erweiterungsmethoden)
- Lambda-Ausdrücke
- Übersicht über Standardabfrageoperatoren
- Konvertierungsregeln für Instanzparameter und deren Auswirkungen
- Erweiterungsmethoden Interoperabilität zwischen Sprachen
- Erweiterungsmethoden und Curried Delegates
- Erweiterungsmethodenbindung und Fehlerbericht