Erweiterungsmethoden (C#-Programmierhandbuch)
Mit Erweiterungsmethoden können Sie vorhandenen Typen Methoden hinzufügen, ohne einen neuen abgeleiteten Typ zu erstellen und ohne den ursprünglichen Typ neu kompilieren oder auf andere Weise bearbeiten zu müssen. Erweiterungsmethoden sind eine besondere Art von statischen Methoden, die Sie jedoch wie Instanzmethoden für den erweiterten Typ aufrufen können. Für in C# und Visual Basic geschriebenen Clientcode gibt es keinen sichtbaren Unterschied zwischen dem Aufrufen einer Erweiterungsmethode und den Methoden, die in einem Typ tatsächlich definiert sind.
Die häufigsten Erweiterungsmethoden sind die LINQ-Standardabfrageoperatoren, die den vorhandenen Typen System.Collections.IEnumerable und System.Collections.Generic.IEnumerable<T> Abfragefunktionen hinzufügen. Um die Standardabfrageoperatoren zu verwenden, müssen Sie sie zuerst mit einer using System.Linq-Direktive einbinden. Jeder Typ, der IEnumerable<T> implementiert, scheint Instanzmethoden zu haben, wie z. B. GroupBy, OrderBy, Average. 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.
Das folgende Beispiel zeigt, wie Sie die Standardabfrageoperator-Methode OrderBy für ein Ganzzahlarray aufrufen können. Der Ausdruck in Klammern ist ein Lambda-Ausdruck. Viele Standardabfrageoperatoren verwenden Lambda-Ausdrücke als Parameter, dies ist jedoch keine Voraussetzung für Erweiterungsmethoden. Weitere Informationen finden Sie unter Lambda-Ausdrücke (C#-Programmierhandbuch).
class ExtensionMethods2
{
static void Main()
{
int[] ints = { 10, 45, 15, 39, 21, 26 };
var result = ints.OrderBy(g => g);
foreach (var i in result)
{
System.Console.Write(i + " ");
}
}
}
//Output: 10 15 21 26 39 45
Erweiterungsmethoden werden als statische Methoden definiert, jedoch mithilfe einer Instanzmethodensyntax aufgerufen. Der erste Parameter bestimmt, für welchen Typ die Methode gilt, und vor dem Parameter steht der this-Modifizierer. Erweiterungsmethoden befinden sich nur dann im Bereich, wenn Sie den Namespace explizit mit einer using-Direktive in Ihren Quellcode importieren.
Im folgenden Beispiel wird eine für die System.String-Klasse definierte Erweiterungsmethode veranschaulicht. Beachten Sie, dass sie in einer nicht geschachtelten, nicht generischen statischen Klasse definiert wird:
namespace ExtensionMethods
{
public static class MyExtensions
{
public static int WordCount(this String str)
{
return str.Split(new char[] { ' ', '.', '?' },
StringSplitOptions.RemoveEmptyEntries).Length;
}
}
}
Die WordCount-Erweiterungsmethode kann mit dieser using-Direktive eingebunden werden:
using ExtensionMethods;
Sie kann darüber hinaus mit dieser Syntax von einer Anwendung aufgerufen werden:
string s = "Hello Extension Methods";
int i = s.WordCount();
Im Code rufen Sie die Erweiterungsmethode mit Instanzmethodensyntax auf. Die vom Compiler erstellte Intermediate Language (IL) übersetzt jedoch Ihren Code in einen Aufruf der statischen Methode. Daher wird nicht wirklich gegen das Prinzip der Kapselung verstoßen. Erweiterungsmethoden können vielmehr nicht auf private Variablen im Typ zugreifen, den sie erweitern.
Weitere Informationen finden Sie unter Gewusst wie: Implementieren und Aufrufen einer benutzerdefinierten Erweiterungsmethode (C#-Programmierhandbuch).
Im Allgemeinen werden Sie vermutlich viel häufiger Erweiterungsmethoden aufrufen, anstatt Ihre eigenen zu implementieren. Da Erweiterungsmethoden mit der Instanzmethodensyntax aufgerufen werden, sind für ihren Einsatz aus dem Clientcode keine besonderen Kenntnisse erforderlich. Um Erweiterungsmethoden für einen bestimmten Typ zu aktivieren, fügen Sie eine using-Direktive für den Namespace, in dem die Methoden definiert werden, hinzu. Um beispielsweise die Standardabfrageoperatoren zu verwenden, fügen Sie diese using-Direktive dem Code hinzu:
using System.Linq;
(Möglicherweise müssen Sie auch einen Verweis auf System.Core.dll hinzufügen.) Wie Sie sehen, werden die Standardabfrageoperatoren jetzt in IntelliSense als zusätzliche Methoden, die für die meisten IEnumerable<T>-Typen verfügbar sind, angezeigt.
Tipp
Obwohl Standardabfrageoperatoren nicht in IntelliSense für String angezeigt werden, sind sie dennoch verfügbar.
Binden von Erweiterungsmethoden während der Kompilierung
Sie können Erweiterungsmethoden verwenden, um eine Klasse oder eine Schnittstelle zu erweitern, jedoch nicht, um sie zu überschreiben. Eine Erweiterungsmethode mit dem gleichen Namen und der gleichen Signatur wie eine Schnittstellen- oder Klassenmethode wird nie aufgerufen. Bei der Kompilierung verfügen Erweiterungsmethoden immer über niedrigere Priorität als im Typ selbst definierte Instanzmethoden. Das heißt, wenn ein Typ eine Methode mit dem Namen Process(int i) hat und Sie über eine Erweiterungsmethode mit der gleichen Signatur verfügen, stellt der Compiler immer eine Bindung mit der Instanzmethode her. Wenn der Compiler einen Methodenaufruf erkennt, sucht er zuerst nach einer Entsprechung in den Instanzmethoden des Typs. Wenn keine Entsprechung gefunden wird, sucht er nach Erweiterungsmethoden, die für den Typ definiert wurden, und stellt eine Bindung mit der ersten gefundenen Erweiterungsmethode her. Im folgenden Beispiel wird veranschaulicht, wie der Compiler bestimmt, mit welcher Erweiterungsmethode oder Instanzmethode die Bindung erfolgen soll.
Beispiel
Das folgende Beispiel veranschaulicht die Regeln, denen der C#-Compiler folgt, um zu bestimmen, ob ein Methodenaufruf an eine Instanzmethode für den Typ oder eine Erweiterungsmethode gebunden werden soll. Die statische Klasse Extensions enthält Erweiterungsmethoden, die für jeden Typ definiert wurden, der IMyInterface implementiert. Die Klassen A, B und C implementieren alle die Schnittstelle.
Die MethodB-Erweiterungsmethode wird nie aufgerufen, da der Name und die Signatur genau mit Methoden übereinstimmen, die bereits von den Klassen implementiert wurden.
Wenn der Compiler keine Instanzmethode mit einer entsprechenden Signatur findet, stellt er ggf. eine Bindung mit einer entsprechenden Erweiterungsmethode her.
// Define an interface named IMyInterface.
namespace DefineIMyInterface
{
using System;
public interface IMyInterface
{
// Any class that implements IMyInterface must define a method
// that matches the following signature.
void MethodB();
}
}
// Define extension methods for IMyInterface.
namespace Extensions
{
using System;
using DefineIMyInterface;
// 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)");
}
}
}
// Define three classes that implement IMyInterface, and then use them to test
// the extension methods.
namespace ExtensionMethodsDemo1
{
using System;
using Extensions;
using DefineIMyInterface;
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)");
}
}
class ExtMethodDemo
{
static void Main(string[] args)
{
// 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(object, int)
a.MethodA("hello"); // Extension.MethodA(object, 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
// nethod 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(object, 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 Richtlinien
Im Allgemeinen wird empfohlen, dass Sie Erweiterungsmethoden sparsam und nur wenn unbedingt notwendig implementieren. Wenn möglich sollte der Clientcode, der einen vorhandenen Typ erweitern muss, dies durch die Erstellung eines neuen vom vorhandenen Typ abgeleiteten Typs durchführen. Weitere Informationen finden Sie unter Vererbung (C#-Programmierhandbuch).
Wenn Sie eine Erweiterungsmethode zum Erweitern eines Typs, dessen Quellcode Sie nicht ändern können, verwenden, laufen Sie Gefahr, dass eine Änderung an der Implementierung des Typs eine Unterbrechung der Erweiterungsmethode bewirkt.
Wenn Sie Erweiterungsmethoden für einen gegebenen Typ implementieren, beachten Sie die folgenden zwei Punkte:
Eine Erweiterungsmethode wird nie aufgerufen, wenn sie die gleiche Signatur wie eine im Typ definierte Methode hat.
Erweiterungsmethoden werden auf Namespace-Ebene eingebunden. Wenn Sie z. B. mehrere statische Klassen haben, die Erweiterungsmethoden in einem einzelnen Namespace mit dem Namen Extensions enthalten, werden sie alle mit der using Extensions;-Direktive eingebunden.
Siehe auch
Referenz
Lambda-Ausdrücke (C#-Programmierhandbuch)
Konzepte
Übersicht über Standardabfrageoperatoren
Weitere Ressourcen
Konvertierungsregeln für Instance-Parameter und ihre Auswirkungen
Erweiterungsmethoden-Interoperabilität zwischen Sprachen
Erweiterungsmethoden und Curry-Delegaten
Erweiterungsmethodenbindung und Fehlerberichte
Änderungsprotokoll
Datum |
Versionsgeschichte |
Grund |
---|---|---|
Oktober 2010 |
Erläutert das letzte Beispiel. |
Kundenfeedback. |