Polymorfismus
Polymorfismus se často označuje jako třetí pilíř objektově orientovaného programování po zapouzdření a dědičnosti. Polymorfismus je řecké slovo, které znamená "mnohotvarý" a má dva odlišné aspekty:
- Za běhu mohou být objekty odvozené třídy považovány za objekty základní třídy na místech, jako jsou parametry metody a kolekce nebo pole. Pokud dojde k polymorfismu, deklarovaný typ objektu už není identický 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í vlastní definici a implementaci. Při spuštění, když kód klienta volá metodu, CLR vyhledá typ běhu objektu a vyvolá toto přepsání virtuální metody. Ve zdrojovém kódu můžete volat metodu základní třídy a způsobit spuštění verze metody odvozené třídy.
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 kreslení, která uživateli umožňuje vytvářet různé druhy obrazců na ploše výkresu. V době kompilace nevíte, které konkrétní typy obrazců uživatel vytvoří. Aplikace ale musí sledovat všechny typy vytvořených obrazců a musí je aktualizovat v reakci na akce myši uživatele. Tento problém můžete vyřešit pomocí polymorfismu ve dvou základních krocích:
- Vytvořte hierarchii tříd, ve které každá konkrétní třída obrazce je odvozena od společné základní třídy.
- Pomocí virtuální metody můžete vyvolat příslušnou metodu pro jakoukoli odvozenou třídu prostřednictvím jediného volání metody základní třídy.
Nejprve vytvořte základní třídu nazvanou Shape
a odvozené třídy, jako Rectangle
je , Circle
a Triangle
. Shape
Dejte třídě virtuální metodu volal Draw
a přepsat ji v každé odvozené třídě nakreslit konkrétní obrazec, který třída představuje. Vytvořte List<Shape>
objekt a přidejte do Circle
Triangle
něj objekt , a Rectangle
do něj.
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();
}
}
Pokud chcete aktualizovat plochu výkresu, použijte smyčku foreach k iteraci seznamu a volání Draw
metody na každém Shape
objektu v seznamu. I když každý objekt v seznamu má deklarovaný typ Shape
, je to typ runtime (přepsáná 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 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
*/
V jazyce C# je každý typ polymorfní, protože všechny typy, včetně uživatelem definovaných typů, dědí z Object.
Přehled polymorfismu
Virtuální členové
Když odvozená třída dědí ze základní třídy, zahrnuje všechny členy základní třídy. Veškeré chování deklarované v základní třídě je součástí odvozené třídy. To umožňuje objekty odvozené třídy být považovány za objekty základní třídy. Modifikátory přístupu (public
atd.) určují, protected
private
jestli jsou tyto členy přístupné z implementace odvozené třídy. Virtuální metody poskytují návrháři různé volby pro chování odvozené třídy:
- 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žší základní třídu metoda bez přepsání, zachování existujícího chování, ale povolení další odvozené třídy přepsání metody.
- Odvozená třída může definovat novou ne virtuální implementaci těchto členů, které 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 přepsání explicitně indikovat, že metoda je určena k účasti na virtuální vyvolání. Následující kód obsahuje 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 nemohou být virtuální; virtuální mohou být pouze metody, vlastnosti, události a indexery. Když odvozená třída přepíše virtuální člen, tento člen je volána i v případě, že instance této třídy je přístupná jako instance základní třídy. Následující kód obsahuje 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é třídy rozšířit základní třídu, aniž by bylo nutné použít implementaci základní třídy metody. Další informace naleznete v tématu Správa verzí s přepsáním a novými klíčovými slovy. Rozhraní poskytuje jiný způsob, jak definovat metodu nebo sadu metod, jejichž implementace je ponechána na odvozené třídy.
Skrytí členů základní třídy s novými členy
Pokud chcete, aby odvozená třída měla člena se stejným názvem jako člen základní třídy, můžete pomocí nového klíčového slova skrýt člena základní třídy. Klíčové new
slovo se vloží před návratový typ člena třídy, který se nahrazuje. Následující kód obsahuje 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; }
}
}
Skryté členy základní třídy mohou být přístupné z kódu klienta přetypováním instance odvozené třídy na instanci základní třídy. Příklad:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
Zabránění přepsání virtuálních členů odvozené třídy
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 B
je odvozena od A
třídy C
B
, C
třída dědí virtuální člen a může jej přepsat bez ohledu na to, zda třída B
deklarovala přepsání daného člena. Následující kód obsahuje 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í přepsání jako zapečetěné. Zastavení dědičnosti vyžaduje vložení klíčového sealed
override
slova před klíčové slovo v deklaraci člena třídy. Následující kód obsahuje příklad:
public class C : B
{
public sealed override void DoWork() { }
}
V předchozím příkladu již není metoda DoWork
virtuální pro žádnou třídu odvozenou z C
. Stále je 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í klíčového new
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 pomocí D
proměnné typu D
, je volána nová DoWork
. Je-li proměnná typu , nebo je použita pro přístup k instanci D
, volání, která DoWork
bude dodržovat pravidla virtuální dědičnosti, směrování těchto volání na implementaci třídy DoWork
C
.A
B
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í klíčového base
slova. Následující kód obsahuje 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 najdete v základu.
Poznámka:
Doporučuje se, aby virtuální členové volali base
implementaci základní třídy daného člena ve své vlastní implementaci. Povolení chování základní třídy umožňuje odvozené třídě soustředit se na implementaci chování 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.