7.5 Erweiterungsmethoden 

Erweiterungsmethoden stellen ein wenig das strenge Konzept der Objektorientierung auf den Kopf. Unsere Aussage war bisher immer, dass die zu einer Klasse gehörenden Methoden in dieser Klasse implementiert werden müssen und an die Subklassen vererbt werden. Die in C# 3.0 eingeführten Erweiterungsmethoden weichen dieses Prinzip auf, indem auch außerhalb einer Klasse Methoden definiert werden können, die sich wie eine Instanzmethode aufrufen lassen.
Nehmen wir dazu das Beispiel der hinlänglich bekannten Klasse Circle. Vielleicht genügt uns das Angebot an Methoden nicht, weil wir noch zusätzlich gern eine Methode hätten, um auf Grundlage des Radius das Kugelvolumen zu berechnen, beispielsweise so:
Circle kreis = new Circle(5); Console.WriteLine("Kugelvolumen = {0}", kreis.GetVolume());
Durch Bereitstellung einer Erweiterungsmethode ist das kein Problem.
static class Extensionmethods { // Erweiterungsmethode zur Berechnung des Kugelvolumens // eines Objekts vom Typ 'Circle' public static double GetVolume(this Circle kreis) { return Math.Pow(kreis.Radius, 3) * Math.PI * 4 / 3; } }
Erweiterungsmethoden werden in static-Klassen implementiert und müssen selbst als public static signiert sein. Beachten Sie bitte, dass trotz der static-Definition Erweiterungsmethoden wie Instanzmethoden aufgerufen werden. Der erste Parameter in der Parameterliste muss das Schlüsselwort this vor dem ersten Parametertyp aufweisen. Hier wird der Typ angegeben, der um die genannte Methode erweitert wird. In unserem Beispiel handelt es sich um Circle. Sie können beliebig viele Erweiterungsmethoden für einen Typ schreiben, ganz so, wie Sie es benötigen. Üblicherweise werden Erweiterungsmethoden in eigens dafür vorgesehenen Klassenbibliotheken definiert.
Hinweis |
Erweiterungsmethoden können auch beliebige Parameter aufweisen. Diese werden nach der Angabe der zu erweiternden Klasse definiert. |
Mit Erweiterungsmethoden können Sie alle Klassen beliebig erweitern und so an Ihre spezifischen Anforderungen anpassen. Erweiterungsmethoden stellen die einzige Möglichkeit dar, sogar Klassen, die mit sealed als nicht ableitbar definiert worden sind, um eigene spezifische Methoden zu ergänzen. Eine von den Klassen, die in der Praxis häufig um Erweiterungsmethoden ergänzt werden, ist String. Selbst mit sealed signiert, gestattet String es nicht, durch Ableitungen weitere Features hinzuzufügen. Das ist im Grunde genommen sehr bedauerlich, da insbesondere die Verarbeitung von Zeichenfolgen oft nach spezifischen Gesichtspunkten erfolgen muss. Mit Erweiterungsmethoden ist das alles nun kein Problem mehr.
Dem Einsatz von Erweiterungsmethoden sind aber auch Grenzen gesetzt, denn Erweiterungsmethoden können nur public-Member der zu erweiternden Klasse aufrufen.
Wird eine Klasse um eine Erweiterungsmethode ergänzt, vererbt sich diese auch an die abgeleitete Klasse weiter. Bezogen auf unser Beispiel oben könnten Sie demnach GetVolume auch auf einem Objekt vom Typ GraphicCircle aufrufen. Hinsichtlich der Überladungsfähigkeit gelten dieselben Regeln wie bei den herkömmlichen Methoden.
Prioritätsregeln
Da Erweiterungsmethoden auch von Entwicklern geschrieben werden, die nicht Urheber der erweiterten Klasse sind, haben Erweiterungsmethoden nur eine untergeordnete Priorität. Betrachten Sie dazu das folgende Beispiel, in dem die Klasse Circle um die Methode Draw erweitert wird.
static class Extensionmethods { // Erweiterungsmethode zur Berechnung des Kugelvolumens // eines Objekts vom Typ 'Circle' public static double GetVolume(this Circle kreis) { return Math.Pow(kreis.Radius, 3) * Math.PI * 4 / 3; } // Erweiterungsmethode 'Draw' public static void Draw(this Circle kreis) { Console.WriteLine("Draw in der Erweiterungsmethode."); } }
Circle ist um die Methode Draw erweitert worden, die sich an GraphicCircle weitervererbt. Da eine gleichnamige Instanzmethode in GraphicCircle existiert, muss die Entscheidung getroffen werden, welche der beiden zur Ausführung kommt: Es handelt sich definitiv um die Draw-Methode der Klasse GraphicCircle.
static void Main(string[] args) {
Circle kreis = new Circle(5);
kreis.Draw();
GraphicCircle g = new GraphicCircle();
g.Draw();
}
Die Ausgabe dieses Codefragments wird wie folgt lauten:
Draw in der Erweiterungsmethode. Der Kreis wird gezeichnet.
Ob eine Erweiterungsmethode aufgerufen wird, hängt davon ab, ob eine gleichnamige Instanzmethode existiert. Wie Sie gesehen haben, hat eine Instanzmethode in jedem Fall Priorität vor einer gleichnamigen Erweiterungsmethode.
Die Erweiterungsmethode einer Klasse kann stets durch eine spezifischere Version ersetzt werden, die für einen Typ definiert ist. Gewissermaßen haben wir es dabei mit einer Überschreibung zu tun. Angenommen, die Klasse Object sei um die Methode Display erweitert worden. Damit steht jeder Klasse die Erweiterungsmethode zur Verfügung – soweit sie sich im aktuellen Namespace befindet oder in einem Namespace, der mit using importiert wird. Eine spezifische Version von Display kann aber auch für alle Objekte vom Typ Circle bereitgestellt werden. Die Circle-Version überdeckt in diesem Fall die geerbte Erweiterungsmethode der Klasse Object.
static class Extensionmethods { public static void Display(this object obj) { Console.WriteLine(obj.ToString()); } public static void Display(this Circle kreis) { Console.WriteLine("Kreis mit Radius {0}", kreis.Radius); } }
Die Spezialisierung einer Erweiterungsmethode für einen bestimmten Typ setzt sich auch in den abgeleiteten Klassen durch. Damit wird ein GraphicCircle-Objekt ebenfalls von der spezifischen Version profitieren, es sei denn, für den abgeleiteten Typ gibt es wiederum eine eigene Version der Erweiterungsmethode, die noch spezialisierter ist.
// Aufruf der Erweiterungsmethode 'Display' Circle kreis = new Circle(5); kreis.Display(); GraphicCircle g = new GraphicCircle(3); g.Display();
Generische Erweiterungsmethoden
Erweiterungsmethoden lassen sich generisch prägen. Damit wird es möglich, eine Erweiterungsmethode beispielsweise nur für eine bestimmte Gruppe von Objekten zur Verfügung zu stellen. Der folgende Code beschreibt die Erweiterungsmethode GetFlaechen. Diese Methode erweitert alle Arrays vom Typ GeometricObject und somit auch Arrays vom Typ Circle, Rectangle usw.
class Program { static void Main(string[] args) { GeometricObject[] geoArr = new GeometricObject[3]; geoArr[0] = new Circle(5); geoArr[1] = new GraphicCircle(9); geoArr[2] = new Rectangle(12, 7); geoArr.GetFlaechen(); Console.ReadLine(); } } static class Extensionmethods { public static void GetFlaechen<T>(this T[] objects) where T : GeometricObject { foreach (GeometricObject geoObj in objects) Console.WriteLine(geoObj.GetFlaeche()); } }
Richtlinien für Erweiterungsmethoden
Mit den Erweiterungsmethoden wird uns ein sehr interessantes Feature an die Hand gegeben, um vorhandene Klassen zu erweitern. Im Allgemeinen sollten Sie aber darauf achten, dass Sie nur dann Erweiterungsmethoden implementieren, wenn es unbedingt notwendig ist. Nach Möglichkeit sollten Sie besser eine Klasse ableiten, anstatt eine Erweiterungsmethode bereitzustellen.
Wenn Sie eine Klassenbibliothek implementieren, sollten Sie es grundsätzlich vermeiden, die darin definierten Typen um Erweiterungsmethoden zu ergänzen, um das Konzept der Objektorientierung nicht unnötig aufzuweichen. Erweiterungsmethoden sind nur dann ein sinnvolles Feature, wenn Ihnen anderweitig keine Möglichkeit mehr bleibt, beispielsweise weil Sie eine sealed-, also eine nicht ableitbare Klasse erweitern möchten.
Sie sollten sich aber auch darüber im Klaren sein, dass die Versionsänderung einer Assembly dazu führen kann, dass eine zuvor für eine Klasse bereitgestellte Erweiterungsmethode wirkungslos wird, weil die entsprechende Klasse um eine gleichnamige Instanzmethode ergänzt worden ist.