10.4 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 ableitenden Klassen vererbt werden (falls die Klasse nicht sealed ist). 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;
}
}
Listing 10.7 Definition einer Erweiterungsmethode
Erweiterungsmethoden werden in static-Klassen implementiert und müssen daher selbst static sein. Beachten Sie bitte, dass Erweiterungsmethoden trotz der static-Definition später wie Instanzmethoden aufgerufen werden. Der erste Parameter in der Parameterliste muss das Schlüsselwort this vor dem Parametertyp aufweisen. Damit wird der Typ angegeben, der um die 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.
Die oben gezeigte Erweiterungsmethode GetVolume hat zwar einen Parameter, ist aber für den Typ Circle nur eine parameterlose Methode. Selbstverständlich können Sie auch beliebig parametrisierte Erweiterungsmethoden bereitstellen. Die Regeln dazu unterscheiden sich nicht von den Regeln der herkömmlichen Methoden – einschließlich einer möglichen Methodenüberladung.
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. Da diese Klasse sealed ist, können Sie nicht durch eine Ableitung weitere Features hinzufügen. Das ist im Grunde genommen sehr bedauerlich, da insbesondere die Verarbeitung von Zeichenfolgen oft nach spezifischen Gesichtspunkten erfolgen soll. 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 ein Objekt vom Typ GraphicCircle aufrufen. Hinsichtlich der Überladungsfähigkeit gelten dieselben Regeln wie bei den herkömmlichen Methoden.
Die 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 Listing 10.8, in dem die Klasse Circle um die Methode Draw erweitert wird.
public static class Extensionmethods {
// Erweiterungsmethode GetVolume
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 Erweiterungsmethode.");
}
}
Listing 10.8 Zwei Erweiterungsmethoden für die Klasse »Circle«
Circle ist um die Methode Draw erweitert worden, die sich an GraphicCircle weitervererbt. Da in GraphicCircle eine gleichnamige Instanzmethode 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();
}
Listing 10.9 Testen der geerbten Erweiterungsmethode »Draw«
Die Ausgabe dieses Codefragments wird 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);
}
}
Listing 10.10 Überdecken einer geerbten Erweiterungsmethode
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.
Circle kreis = new Circle(5);
kreis.Display();
GraphicCircle g = new GraphicCircle(3);
g.Display();
Listing 10.11 Aufruf der Erweiterungsmethode »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());
}
}
Listing 10.12 Generische Erweiterungsmethode
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. Meistens ist es ratsamer, eine Klasse abzuleiten, anstatt eine Erweiterungsmethode bereitzustellen.
Vermeiden Sie es, eine Klassenbibliothek zu veröffentlichen und die darin enthaltenen Typen bereits um Erweiterungsmethoden zu ergänzen. Diese sind nur dann ein sinnvolles Feature, wenn Ihnen anderweitig keine Möglichkeit mehr bleibt, beispielsweise weil Sie eine sealed-Klasse, 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.
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.