Polymorphismus (C#-Programmierhandbuch)

Polymorphismus wird nach Kapselung und Vererbung oft als der dritte Pfeiler objektorientierter Programmierung bezeichnet. Polymorphismus ist ein griechisches Wort, das soviel wie "Vielgestaltigkeit" bedeutet, und er verfügt über zwei unterschiedliche Aspekte:

  1. Zur Laufzeit werden Objekte einer abgeleiteten Klasse an Stellen wie Methodenparametern und Auflistungen oder Arrays möglicherweise wie Objekte einer Basisklasse behandelt. Ist dies der Fall, ist der deklarierte Typ des Objekts nicht mehr mit seinem Laufzeittyp identisch.

  2. Basisklassen können virtuelle Methoden definieren und implementieren, und abgeleitete Klassen können diese überschreiben, was bedeutet, dass sie ihre eigene Definition und Implementierung bereitstellen. Wenn die Methode zur Laufzeit vom Clientcode aufgerufen wird, sucht die CLR nach dem Laufzeittyp des Objekts und ruft diese Überschreibung der virtuellen Methode auf. Somit können Sie im Quellcode eine Methode für eine Basisklasse aufrufen und eine Version einer abgeleiteten Klasse der Methode ausführen.

Mithilfe virtueller Methoden können Sie mit Gruppen verwandter Objekte auf eine einheitliche Weise arbeiten. Denken Sie beispielsweise an eine Zeichenanwendung, mit der Benutzer verschiedene Formen auf einer Zeichenoberfläche erstellen können. Sie wissen zur Kompilierzeit nicht, welche Art von Formen der Benutzer erstellt. Die Anwendung muss jedoch alle Arten von Formen nachverfolgen, die erstellt werden, und die Formen müssen den Mausaktionen des Benutzers entsprechend aktualisiert werden. Mit Polymorphismus können Sie dieses Problem in zwei grundlegenden Schritten lösen:

  1. Erstellen Sie eine Klassenhierarchie, in der jede spezielle Shape-Klasse von einer allgemeinen Basisklasse abgeleitet wird.

  2. Verwenden Sie eine virtuelle Methode, um die jeweilige Methode für eine beliebige abgeleitete Klasse durch einen einzigen Aufruf der Basisklassenmethode aufzurufen.

Erstellen Sie zuerst eine Basisklasse mit dem Namen Shape sowie abgeleitete Klassen wie Rectangle, Circle und Triangle. Geben Sie der Shape-Klasse eine virtuelle Methode mit dem Namen Draw, und überschreiben Sie diese in jeder abgeleiteten Klasse, um die entsprechende Form zu zeichnen, die die Klasse darstellt. Erstellen Sie ein List<Shape>-Objekt, und fügen Sie diesem einen Kreis (Circle), ein Dreieck (Triangle ) und ein Rechteck (Rectangle) hinzu. Zum Aktualisieren der Zeichenoberfläche verwenden Sie eine foreach-Schleife, um die Liste zu durchlaufen und für jedes Shape-Objekt in der Liste die Draw-Methode aufzurufen. Obwohl jedes Objekt in der Liste den deklarierten Typ Shape aufweist, ist es der Laufzeittyp (die überschriebene Version der Methode in der jeweiligen abgeleiteten Klasse), der aufgerufen wird.

public class Shape
{
    // A few example members
    public int X { get; private set; }
    public int Y { get; private set; }
    public int Height { get; set; }
    public int Width { get; set; }

    // Virtual method
    public virtual void Draw()
    {
        Console.WriteLine("Performing base class drawing tasks");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        // Code to draw a circle...
        Console.WriteLine("Drawing a circle");
        base.Draw();
    }
}
class Rectangle : Shape
{
    public override void Draw()
    {
        // Code to draw a rectangle...
        Console.WriteLine("Drawing a rectangle");
        base.Draw();
    }
}
class Triangle : Shape
{
    public override void Draw()
    {
        // Code to draw a triangle...
        Console.WriteLine("Drawing a triangle");
        base.Draw();
    }
}

class Program
{
    static void Main(string[] args)
    {
        // Polymorphism at work #1: a Rectangle, Triangle and Circle
        // can all be used whereever a Shape is expected. No cast is
        // required because an implicit conversion exists from a derived 
        // class to its base class.
        System.Collections.Generic.List<Shape> shapes = new System.Collections.Generic.List<Shape>();
        shapes.Add(new Rectangle());
        shapes.Add(new Triangle());
        shapes.Add(new Circle());

        // Polymorphism at work #2: the virtual method Draw is
        // invoked on each of the derived classes, not the base class.
        foreach (Shape s in shapes)
        {
            s.Draw();
        }

        // Keep the console open in debug mode.
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }

}

/* Output:
    Drawing a rectangle
    Performing base class drawing tasks
    Drawing a triangle
    Performing base class drawing tasks
    Drawing a circle
    Performing base class drawing tasks
 */

In C# ist jeder Typ polymorph, da alle Typen, einschließlich benutzerdefinierter Typen, von Object erben.

Übersicht über Polymorphie

Virtuelle Member

Eine abgeleitete Klasse, die von einer Basisklasse erbt, übernimmt alle Methoden, Felder, Eigenschaften und Ereignisse der Basisklasse. Der Designer der abgeleiteten Klasse verfügt über folgende Optionen:

  • Überschreiben virtueller Member in der Basisklasse

  • Erben der nächsten Basisklassenmethode, ohne diese zu überschreiben

  • Definieren einer neuen, nicht virtuellen Implementierung dieser Member, die die Basisklassenimplementierungen verbergen

Ein Basisklassenmember kann von einer abgeleiteten Klasse nur überschreiben werden, wenn der Basisklassenmember als virtuell oder abstrakt deklariert ist. Der abgeleitete Member muss mit dem override-Schlüsselwort explizit angeben, dass die Methode Teil des virtuellen Aufrufs sein soll. Der folgende Code veranschaulicht dies:

public class BaseClass
{
    public virtual void DoWork() { }
    public virtual int WorkProperty
    {
        get { return 0; }
    }
}
public class DerivedClass : BaseClass
{
    public override void DoWork() { }
    public override int WorkProperty
    {
        get { return 0; }
    }
}

Nur Methoden, Eigenschaften, Ereignisse und Indexer können virtuell sein, Felder jedoch nicht. Wenn eine abgeleitete Klasse einen virtuellen Member überschreibt, wird der Member auch dann aufgerufen, wenn auf eine Instanz dieser Klasse als Instanz der Basisklasse zugegriffen wird. Der folgende Code veranschaulicht dies:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Also calls the new method.

Durch virtuelle Methoden und Eigenschaften können abgeleitete Klassen eine Basisklasse erweitern, ohne dafür die Basisklassenimplementierung einer Methode verwenden zu müssen. Weitere Informationen finden Sie unter Versionsverwaltung mit den Schlüsselwörtern "override" und "new" (C#-Programmierhandbuch). Eine Schnittstelle bietet eine andere Möglichkeit zum Definieren einer Methode oder eines Satzes von Methoden, deren Implementierung abgeleiteten Klassen überlassen wird. Weitere Informationen finden Sie unter Schnittstellen (C#-Programmierhandbuch) und Auswählen zwischen Klassen und Schnittstellen.

Ausblenden von Basisklassenmembern mit neuen Membern

Wenn der abgeleitete Member denselben Namen wie ein Member in einer Basisklasse haben soll, dieser Member jedoch nicht Teil des virtuellen Aufrufs sein soll, können Sie das new-Schlüsselwort verwenden. Das Schlüsselwort new wird vor dem Rückgabetyp des zu ersetzenden Klassenmembers platziert. Der folgende Code veranschaulicht dies:

public class BaseClass
{
    public void DoWork() { WorkField++; }
    public int WorkField;
    public int WorkProperty
    {
        get { return 0; }
    }
}

public class DerivedClass : BaseClass
{
    public new void DoWork() { WorkField++; }
    public new int WorkField;
    public new int WorkProperty
    {
        get { return 0; }
    }
}

Auf ausgeblendete Basisklassenmember kann weiterhin von Clientcode aus zugegriffen werden, indem die Instanz der abgeleiteten Klasse in eine Instanz der Basisklasse umgewandelt wird. Beispiele:

DerivedClass B = new DerivedClass();
B.DoWork();  // Calls the new method.

BaseClass A = (BaseClass)B;
A.DoWork();  // Calls the old method.

Verhindern des Überschreibens virtueller Member durch abgeleitete Klassen

Virtuelle Member bleiben stets virtuell, unabhängig davon, wie viele Klassen zwischen dem virtuellen Member und der Klasse, die ihn ursprünglich deklariert hat, deklariert wurden. Falls Klasse A einen virtuellen Member deklariert und Klasse B von A sowie Klasse C von B abgeleitet wird, dann erbt Klasse C den virtuellen Member. Sie kann diesen dann auch überschreiben, unabhängig davon, ob Klasse B eine Überschreibung für diesen Member deklariert hat. Der folgende Code veranschaulicht dies:

public class A
{
    public virtual void DoWork() { }
}
public class B : A
{
    public override void DoWork() { }
}

Eine abgeleitete Klasse kann die virtuelle Vererbung anhalten, indem sie eine Überschreibung als versiegelt deklariert. Dazu muss das sealed-Schlüsselwort vor dem override-Schlüsselwort in die Klassenmemberdeklaration eingefügt werden. Der folgende Code veranschaulicht dies:

public class C : B
{
    public sealed override void DoWork() { }
}

Im vorherigen Beispiel ist die DoWork-Methode für keine der von C abgeleiteten Klassen mehr virtuell. Für Instanzen von C ist dies immer noch virtuell, auch wenn sie in Typ B oder A umgewandelt werden. Versiegelte Methoden können durch abgeleitete Klassen mithilfe des new-Schlüsselworts ersetzt werden (siehe folgendes Beispiel).

public class D : C
{
    public new void DoWork() { }
}

In diesem Fall gilt, wenn DoWork für D mit einer Variablen des Typs D aufgerufen wird, dann wird die neue DoWork aufgerufen. Falls mithilfe einer Variablen des Typs C, B, oder A auf eine Instanz von D zugegriffen wird, folgt ein Aufruf von DoWork den Regeln der virtuellen Vererbung. Dementsprechend werden diese Aufrufe zur Implementierung von DoWork bei Klasse C weitergeleitet.

Zugreifen auf virtuelle Member von Basisklassen von abgeleiteten Klassen aus

Eine abgeleitete Klasse, die eine Methode oder Eigenschaft ersetzt bzw. überschrieben hat, kann mit dem base-Schlüsselwort weiterhin auf die Methode bzw. Eigenschaft der Basisklasse zugreifen. Der folgende Code veranschaulicht dies:

public class Base
{
    public virtual void DoWork() {/*...*/ }
}
public class Derived : Base
{
    public override void DoWork()
    {
        //Perform Derived's work here
        //...
        // Call DoWork on base class
        base.DoWork();
    }
}

Weitere Informationen finden Sie unter base.

Tipp

Es wird empfohlen, dass virtuelle Member base verwenden, um die Implementierung der Basisklasse dieses Members in der eigenen Implementierung aufzurufen. Das Zulassen des Verhaltens der Basisklasse ermöglicht es der abgeleiteten Klasse, das für sie spezifische Verhalten zu implementieren. Falls die Implementierung der Basisklasse nicht aufgerufen wird, liegt es an der abgeleiteten Klasse, ihr Verhalten mit dem der Basisklasse kompatibel zu machen.

In diesem Abschnitt

Siehe auch

Referenz

Vererbung (C#-Programmierhandbuch)

Abstrakte und versiegelte Klassen und Klassenmember (C#-Programmierhandbuch)

Methoden (C#-Programmierhandbuch)

Ereignisse (C#-Programmierhandbuch)

Eigenschaften (C#-Programmierhandbuch)

Indexer (C#-Programmierhandbuch)

Typen (C#-Programmierhandbuch)

Konzepte

C#-Programmierhandbuch

C#-Programmierhandbuch