Polymorphie

Polymorphismus wird häufig nach der Kapselung und der Vererbung als die dritte Säule der objektorientierten Programmierung bezeichnet. Es handelt sich dabei um ein griechisches Wort, das "Vielgestaltigkeit" bedeutet und zwei verschiedene Aspekte umfasst:

  • In Methodenparametern, Auflistungen und Arrays können Objekte einer abgeleiteten Klasse zur Laufzeit als Objekte einer Basisklasse behandelt werden. Wenn diese Polymorphie auftritt, entspricht der deklarierte Typ des Objekts nicht mehr dem Runtimetyp.
  • Basisklassen können virtuelleMethoden definieren und implementieren und können von abgeleiteten Klassen überschrieben werden, was bedeutet, dass sie ihre eigene Definition und Implementierung bereitstellen. Zur Laufzeit, wenn die Methode von Clientcode aufgerufen wird, sucht die CLR den Laufzeittyp des Objekts und ruft die Überschreibung der virtuellen Methode auf. In Ihrem Quellcode können Sie eine Methode in einer Basisklasse aufrufen und bewirken, dass die Methodenversion der abgeleiteten Klasse ausgeführt wird.

Dank virtueller Methoden können Sie auf einheitliche Weise mit Gruppen verwandter Objekte arbeiten. Nehmen Sie beispielsweise an, Sie haben eine Zeichenanwendung, mit der ein Benutzer verschiedene Arten von Formen auf einer Zeichenoberfläche erstellen kann. Sie wissen zur Kompilierzeit nicht, welche bestimmten Arten von Formen ein*e Benutzer*in erstellt. Die Anwendung muss jedoch alle verschiedenen Formentypen, die erstellt werden, nachverfolgen und diese als Antwort auf die Mausaktionen des Benutzers aktualisieren. Sie können Polymorphismus verwenden, um dieses Problem mithilfe von zwei einfachen Schritten zu lösen:

  1. Erstellen Sie eine Klassenhierarchie, in der jede spezifische Formenklasse von einer gemeinsamen Basisklasse abgeleitet wird.
  2. Verwenden Sie eine virtuelle Methode, um die entsprechende Methode in einer abgeleiteten Klasse durch einen einzigen Aufruf der Basisklassenmethode aufzurufen.

Erstellen Sie zuerst eine Basisklasse namens Shape und abgeleitete Klassen, wie z. B. Rectangle, Circle und Triangle. Geben Sie der Shape-Klasse eine virtuelle Methode namens Draw, und überschreiben Sie sie in jeder abgeleiteten Klasse, um die jeweilige Form zu zeichnen, die die Klasse darstellt. Erstellen Sie ein List<Shape>-Objekt, und fügen Sie die abgeleiteten Klassen Circle, Triangle und Rectangle hinzu.

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");
    }
}

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

Um die Zeichenoberfläche zu aktualisieren, verwenden Sie eine foreach-Schleife, um die Liste zu durchlaufen und die Draw-Methode für jedes Shape-Objekt in der Liste aufzurufen. Obwohl jedes Objekt in der Liste einen deklarierten Shape-Typ aufweist, wird der Runtimetyp (die überschriebene Version der Methode in jeder abgeleiteten Klasse) aufgerufen.

// Polymorphism at work #1: a Rectangle, Triangle and Circle
// can all be used wherever a Shape is expected. No cast is
// required because an implicit conversion exists from a derived
// class to its base class.
var shapes = new List<Shape>
{
    new Rectangle(),
    new Triangle(),
    new Circle()
};

// Polymorphism at work #2: the virtual method Draw is
// invoked on each of the derived classes, not the base class.
foreach (var shape in shapes)
{
    shape.Draw();
}
/* 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 benutzerdefinierten Typen, von Object erben.

Übersicht über Polymorphie

Virtuelle Member

Wenn eine abgeleitete Klasse von einer Basisklasse erbt, enthält sie alle Member der Basisklasse. Das gesamte in der Basisklasse deklarierte Verhalten ist Teil der abgeleiteten Klasse. Dadurch können Objekte der abgeleiteten Klasse als Objekte der Basisklasse behandelt werden. Zugriffsmodifizierer (public, protected, private usw.) bestimmen, ob auf diese Member über die Implementierung der abgeleiteten Klasse zugegriffen werden kann. Durch virtuelle Methoden haben Designer*innen verschiedene Auswahlmöglichkeiten für das Verhalten der abgeleiteten Klasse:

  • Die abgeleitete Klasse kann virtuelle Member in der Basisklasse überschreiben, wodurch ein neues Verhalten definiert wird.
  • Die abgeleitete Klasse kann die nächstgelegene Basisklassenmethode erben, ohne sie zu überschreiben. Das vorhandene Verhalten wird zwar beibehalten, aber es wird weiteren abgeleiteten Klassen ermöglicht, die Methode zu überschreiben.
  • Die abgeleitete Klasse kann neue nicht virtuelle Implementierungen der Member definieren, die die Basisklassenimplementierungen verbergen.

Eine abgeleitete Klasse kann einen Basisklassenmember nur überschreiben, wenn der Basisklassenmember als virtuell oder abstrakt deklariert ist. Der abgeleitete Member muss das override-Schlüsselwort verwenden, um explizit anzugeben, dass die Methode an dem virtuellen Aufruf beteiligt 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; }
    }
}

Anders als Methoden, Eigenschaften, Ereignisse und Indexer können Felder nicht virtuell sein. Wenn eine abgeleitete Klasse einen virtuellen Member überschreibt, wird dieser 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 = B;
A.DoWork();  // Also calls the new method.

Mithilfe virtueller Methoden und Eigenschaften können abgeleitete Klassen eine Basisklasse erweitern, ohne die Basisklassenimplementierung einer Methode verwenden zu müssen. Weitere Informationen finden Sie unter Versionsverwaltung mit den Schlüsselwörtern „override“ und „new“. Eine Schnittstelle bietet eine weitere Möglichkeit zur Definition einer Methode bzw. einer Gruppe von Methoden, deren Implementierung von abgeleiteten Klassen übernommen wird.

Ausblenden von Basisklassenmembern für neue Member

Wenn Sie möchten, dass Ihre abgeleiteten Klassen einen Member mit demselben Namen wie ein Member in einer Basisklasse enthalten, können Sie das Schlüsselwort new verwenden, um den Basisklassenmember auszublenden. Das new-Schlüsselwort wird dem Rückgabetyp eines Klassenmembers vorangestellt, der ersetzt wird. 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 Klassenmember kann vom Clientcode immer noch zugegriffen werden, indem die Instanz der abgeleiteten Klasse in eine Instanz der Basisklasse umgewandelt wird. Zum Beispiel:

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

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

Verhindern der Überschreibung virtueller Member durch abgeleitete Klassen

Virtuelle Member bleiben virtuell, unabhängig davon, wie viele Klassen zwischen dem virtuellen Member und der Klasse deklariert werden, die ihn ursprünglich deklariert hat. Wenn die Klasse A einen virtuellen Member deklariert, die Klasse B von A abgeleitet und die Klasse C von B abgeleitet wird, erbt die Klasse C den virtuellen Member und kann diesen überschreiben, unabhängig davon, ob die 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 stoppen, indem das Überschreiben als versiegelt deklariert wird. Zum Beenden der Vererbung muss das Schlüsselwort sealed vor dem Schlüsselwort override in der Klassenmemberdeklaration eingefügt werden. Der folgende Code veranschaulicht dies:

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

Im vorherigen Beispiel ist die Methode DoWork für Klassen, die von C abgeleitet wurden, nicht mehr virtuell. Für Instanzen von C wird sie weiterhin als virtuell betrachtet, auch wenn die Instanzen in den B- oder A-Typ umgewandelt werden. Versiegelte Methoden können durch abgeleitete Klassen ersetzt werden, indem das Schlüsselwort new wie im folgenden Beispiel gezeigt verwendet wird:

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

Wenn DoWork für D mithilfe einer Variable vom Typ D aufgerufen wird, wird in diesem Fall die neue DoWork-Methode aufgerufen. Wenn eine Variable vom Typ C, B oder A zum Zugreifen auf eine Instanz von D verwendet wird, befolgt ein Aufruf von DoWork die Regeln der virtuellen Vererbung, und diese Aufrufe werden an die Implementierung von DoWork für die Klasse C weitergeleitet.

Zugreifen auf virtuelle Basisklassenmember über abgeleitete Klassen

Eine abgeleitete Klasse, die eine Methode oder Eigenschaft ersetzt oder überschrieben hat, kann immer noch auf die Methode oder Eigenschaft in der Basisklasse mithilfe des base-Schlüsselworts 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.

Hinweis

Es wird empfohlen, dass virtuelle Member base verwenden, um die Basisklassenimplementierung dieses Members in seiner eigenen Implementierung aufzurufen. Durch Zulassen des Basisklassenverhaltens kann sich die abgeleitete Klasse auf die Implementierung von Verhalten konzentrieren, das spezifisch für die abgeleitete Klasse ist. Wenn die Basisklassenimplementierung nicht aufgerufen wird, liegt es an der abgeleiteten Klasse, ihr Verhalten kompatibel mit dem Verhalten der Basisklasse zu gestalten.