Polymorfismus

Polymorfismus se často označuje jako třetí pilíř objektově orientovaného programování, a to po zapouzdření a dědičnosti. Polymorfismus je řecký Word, který znamená "velký tvar" a má dva odlišné aspekty:

  • V době běhu lze objekty odvozené třídy považovat za objekty základní třídy v místech, jako jsou parametry metody a kolekce nebo pole. Pokud tato polymorfismua nastane, deklarovaný typ objektu již není totožný s jeho typem za běhu.
  • Základní třídy mohou definovat a implementovat virtuální metody a odvozené třídy je mohou přepsat , což znamená, že poskytují svou vlastní definici a implementaci. V době běhu, když kód klienta volá metodu, modul CLR vyhledá typ za běhu objektu a vyvolá přepsání virtuální metody. Ve zdrojovém kódu můžete zavolat metodu pro základní třídu a způsobit spuštění odvozené třídy verze metody.

Virtuální metody umožňují pracovat se skupinami souvisejících objektů jednotným způsobem. Předpokládejme například, že máte aplikaci pro kreslení, která umožňuje uživateli vytvářet různé druhy tvarů na kreslicí ploše. V době kompilace neznáte, které konkrétní typy tvarů uživatel vytvoří. Aplikace však musí sledovat všechny různé typy tvarů, které byly vytvořeny, a musí je aktualizovat v reakci na akce myši uživatele. K vyřešení tohoto problému můžete použít polymorfismus ve dvou základních krocích:

  1. Vytvořte hierarchii třídy, ve které jsou jednotlivé konkrétní třídy tvarů odvozeny ze společné základní třídy.
  2. Použijte virtuální metodu k vyvolání vhodné metody pro jakoukoli odvozenou třídu prostřednictvím jediného volání metody základní třídy.

Nejprve vytvořte základní třídu s názvem Shape a odvozené třídy, jako například Rectangle , Circle a Triangle . Poskytněte Shape třídě virtuální metodu s názvem Draw a přepište ji v každé odvozené třídě pro vykreslení konkrétního tvaru, který třída představuje. Vytvořte List<Shape> objekt a přidejte Circle Triangle do něj objekt,, a Rectangle .

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

Chcete-li aktualizovat plochu pro kreslení, použijte smyčku foreach k iterování seznamu a zavolejte Draw metodu pro každý Shape objekt v seznamu. I když každý objekt v seznamu má deklarovaný typ Shape , je typ za běhu (přepsaná verze metody v každé odvozené třídě), která bude vyvolána.

// 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.
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
*/

V jazyce C# je každý typ polymorfní, protože všechny typy, včetně uživatelsky definovaných typů, dědí z Object .

Přehled polymorfismu

Virtuální členové

Když odvozená třída dědí ze základní třídy, získá všechny metody, pole, vlastnosti a události základní třídy. Návrhář odvozené třídy má různé možnosti pro chování virtuálních metod:

  • Odvozená třída může přepsat virtuální členy v základní třídě a definovat nové chování.
  • Odvozená třída může dědit nejbližší metodu základní třídy, aniž by ji přepsala, zachovává stávající chování, ale umožňuje dalším odvozeným třídám přepsat metodu.
  • Odvozená třída může definovat novou nevirtuální implementaci těchto členů, kteří skryjí implementace základní třídy.

Odvozená třída může přepsat člen základní třídy pouze v případě, že je člen základní třídy deklarován jako virtuální nebo abstraktní. Odvozený člen musí použít klíčové slovo override k explicitnímu označení toho, že je metoda určena k účasti ve virtuálním vyvolání. Následující kód poskytuje příklad:

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

Pole nemůžou být virtuální. virtuální můžou být jenom metody, vlastnosti, události a indexery. Když odvozená třída přepíše virtuální člen, je tento člen volán i v případě, že instance této třídy je k dispozici jako instance základní třídy. Následující kód poskytuje příklad:

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

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

Virtuální metody a vlastnosti umožňují odvozeným třídám rozšiřuje základní třídu bez nutnosti použití implementace základní třídy metody. Další informace najdete v tématu Správa verzí pomocí klíčových slov override a New. Rozhraní poskytuje další způsob, jak definovat metodu nebo sadu metod, jejichž implementace je ponechána na odvozené třídy.

Skrýt členy základní třídy novými členy

Chcete-li, aby vaše odvozená třída měla člena se stejným názvem jako členem v základní třídě, můžete použít klíčové slovo New pro skrytí člena základní třídy. newKlíčové slovo je vloženo před návratový typ člena třídy, který se nahrazuje. Následující kód poskytuje příklad:

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

Ke skrytým členům základní třídy je možné přivodit z klientského kódu přetypování instance odvozené třídy na instanci základní třídy. Například:

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

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

Zabránit odvozeným třídám z přepsání virtuálních členů

Virtuální členové zůstávají virtuální bez ohledu na to, kolik tříd bylo deklarováno mezi virtuálním členem a třídou, která ji původně deklarovala. Pokud třída A deklaruje virtuální člen a třída je B odvozena z třídy A a třída je C odvozena z B třídy, třída C zdědí virtuálního člena a může jej přepsat bez ohledu na to, zda třída B deklarovala přepsání pro daného člena. Následující kód poskytuje příklad:

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

Odvozená třída může zastavit virtuální dědičnost deklarací override jako sealed. Zastavování dědičnosti vyžaduje vložení sealed klíčového slova před override klíčové slovo v deklaraci člena třídy. Následující kód poskytuje příklad:

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

V předchozím příkladu již metoda není typu DoWork Virtual pro žádnou třídu odvozenou z C . Je stále virtuální pro instance C , i když jsou přetypování na typ B nebo typ A . Zapečetěné metody lze nahradit odvozenými třídami pomocí new klíčového slova, jak ukazuje následující příklad:

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

V tomto případě, pokud DoWork je volána při D použití proměnné typu D , DoWork je volána nová. Pokud proměnná typu C , B nebo A je použita pro přístup k instanci D , volání metody DoWork bude následovat pravidla virtuální dědičnosti, směrování těchto volání do implementace DoWork třídy na třídu C .

Přístup k virtuálním členům základní třídy z odvozených tříd

Odvozená třída, která nahradila nebo přepsala metodu nebo vlastnost, může stále přistupovat k metodě nebo vlastnosti základní třídy pomocí base klíčového slova. Následující kód poskytuje příklad:

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

Další informace naleznete v tématu Base.

Poznámka

Doporučuje se, aby virtuální členové použili base k volání implementace základní třídy tohoto člena ve své vlastní implementaci. V případě, že dojde k chování základní třídy, umožňuje odvozeným třídám soustředit se na implementaci konkrétního chování, které je specifické pro odvozenou třídu. Pokud není volána implementace základní třídy, je až do odvozené třídy, aby jejich chování bylo kompatibilní s chováním základní třídy.