Polimorfizmus
A polimorfizmust gyakran nevezik az objektumorientált programozás harmadik pillérének a beágyazás és az öröklés után. A polimorfizmus egy görög szó, amely "többalakú" szót jelent, és két különböző aspektusa van:
- Futtatáskor a származtatott osztály objektumai egy alaposztály objektumaként kezelhetők olyan helyeken, mint a metódusparaméterek, gyűjtemények vagy tömbök. Ha ez a polimorfizmus bekövetkezik, az objektum deklarált típusa már nem azonos a futásidejű típusával.
- Az alaposztályok meghatározhatnak és implementálhatnak virtuális metódusokat, a származtatott osztályok pedig felülbírálhatják őket, ami azt jelenti, hogy saját definíciót és implementációt biztosítanak. Futásidőben, amikor az ügyfélkód meghívja a metódust, a CLR megkeresi az objektum futásidejének típusát, és meghívja a virtuális metódus felülbírálását. A forráskódban meghívhat egy metódust egy alaposztályon, és a metódus származtatott osztályának verzióját hajthatja végre.
A virtuális módszerek lehetővé teszik a kapcsolódó objektumok csoportjainak egységes használatát. Tegyük fel például, hogy van egy rajzalkalmazása, amely lehetővé teszi a felhasználó számára, hogy különféle alakzatokat hozzon létre a rajzfelületen. Fordításkor nem tudja, hogy a felhasználó milyen típusú alakzatokat fog létrehozni. Az alkalmazásnak azonban nyomon kell követnie az összes létrehozott alakzattípust, és frissítenie kell őket a felhasználói egérműveletekre válaszul. A probléma megoldásához két alapvető lépésben használhatja a polimorfizmust:
- Hozzon létre egy osztályhierarchiát, amelyben minden egyes alakzatosztály egy közös alaposztályból származik.
- Virtuális metódus használatával meghívhatja a megfelelő metódust bármely származtatott osztályon az alaposztály metódusának egyetlen hívásával.
Először hozzon létre egy alaposztályt , és Shape
hozzon létre olyan származtatott osztályokat, mint az Rectangle
, Circle
és Triangle
. Adjon meg egy virtuális metódust az Shape
osztálynak Draw
, és bírálja felül minden származtatott osztályban, hogy megrajzolja az osztály által képviselt alakzatot. Hozzon létre egy objektumot List<Shape>
, és adjon hozzá egy Circle
, Triangle
és Rectangle
hozzá.
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();
}
}
A rajzfelület frissítéséhez használjon egy foreach hurkot a lista iterálásához, és hívja meg a Draw
metódust a lista minden objektumánShape
. Annak ellenére, hogy a lista minden objektumának Shape
deklarált típusa van, a meghívandó futásidejű típus (a metódus felülírt verziója az egyes származtatott osztályokban).
// 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
*/
A C#-ban minden típus polimorfikus, mert minden típus, beleértve a felhasználó által definiált típusokat is, öröklődik.Object
A polimorfizmus áttekintése
Virtuális tagok
Ha egy származtatott osztály örökli az alaposztályt, az az alaposztály összes tagját tartalmazza. Az alaposztályban deklarált összes viselkedés a származtatott osztály része. Ez lehetővé teszi, hogy a származtatott osztály objektumai az alaposztály objektumaiként legyenek kezelve. A hozzáférési módosítók (public
és így tovább) határozzák meg, protected
private
hogy ezek a tagok elérhetők-e a származtatott osztály implementációjából. A virtuális metódusok különböző választási lehetőségeket adnak a tervezőnek a származtatott osztály viselkedéséhez:
- A származtatott osztály felülírhatja az alaposztály virtuális tagjait, és új viselkedést határozhat meg.
- A származtatott osztály anélkül örökölheti a legközelebbi alaposztály-metódust, hogy felülbírálja azt, megőrizve a meglévő viselkedést, de lehetővé teszi a további származtatott osztályok számára a metódus felülbírálását.
- A származtatott osztály definiálhatja azon tagok új, nem virtuális implementációit, amelyek elrejtik az alaposztály-implementációkat.
A származtatott osztály csak akkor bírálhat felül egy alaposztálytagot, ha az alaposztálytag virtuális vagy absztraktként van deklarálva. A származtatott tagnak a felülbírálási kulcsszóval kell egyértelműen jeleznie, hogy a metódus a virtuális meghívásban való részvételre szolgál. Az alábbi kód egy példát mutat be:
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; }
}
}
A mezők nem lehetnek virtuálisak; csak metódusok, tulajdonságok, események és indexelők lehetnek virtuálisak. Ha egy származtatott osztály felülbírál egy virtuális tagot, a rendszer akkor is meghívja ezt a tagot, ha az adott osztály egy példánya az alaposztály példányaként érhető el. Az alábbi kód egy példát mutat be:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = B;
A.DoWork(); // Also calls the new method.
A virtuális metódusok és tulajdonságok lehetővé teszik a származtatott osztályok számára az alaposztályok kiterjesztését anélkül, hogy egy metódus alaposztály-implementációját kellene használniuk. További információ: Verziószámozás felülbírálással és új kulcsszavakkal. Az interfész egy másik módot kínál egy metódus vagy metóduskészlet meghatározására, amelynek implementációja származtatott osztályokra van hagyva.
Alaposztálytagok elrejtése új tagokkal
Ha azt szeretné, hogy a származtatott osztály az alaposztály tagjával azonos nevű taggal rendelkezzen, az új kulcsszóval elrejtheti az alaposztály tagját. A new
kulcsszó a lecserélt osztálytag visszatérési típusa elé kerül. Az alábbi kód egy példát mutat be:
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; }
}
}
A rejtett alaposztálytagok az ügyfélkódból úgy érhetők el, hogy a származtatott osztály példányát az alaposztály egy példányára válogatják. Példa:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
A származtatott osztályok nem bírálják felül a virtuális tagokat
A virtuális tagok virtuálisak maradnak, függetlenül attól, hogy hány osztály lett deklarálva a virtuális tag és az eredetileg deklarált osztály között. Ha az osztály A
egy virtuális tagot deklarál, és az osztály B
származik A
, és az osztály C
származik belőle B
, az osztály C
örökli a virtuális tagot, és felülbírálhatja azt, függetlenül attól, hogy az osztály B
felülbírálást deklarált-e az adott tag számára. Az alábbi kód egy példát mutat be:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
A származtatott osztály leállíthatja a virtuális öröklést, ha lezártként deklarál egy felülbírálást. Az öröklés leállításához a sealed
kulcsszót a override
kulcsszó elé kell helyezni az osztálytag-deklarációban. Az alábbi kód egy példát mutat be:
public class C : B
{
public sealed override void DoWork() { }
}
Az előző példában a metódus DoWork
már nem virtuális a forrásból C
származtatott osztály számára. A példányok C
esetében továbbra is virtuális, még akkor is, ha beírásra B
vagy beírásra vannak öntöttve A
. A lezárt módszereket a kulcsszó használatával new
származtatott osztályok helyettesíthetik, ahogy az alábbi példa is mutatja:
public class D : C
{
public new void DoWork() { }
}
Ebben az esetben, ha DoWork
egy típusváltozót D
használD
, az újat DoWork
hívja meg a rendszer. Ha egy változó típusú C
, B
vagy A
egy példány D
elérésére szolgál, a hívás a virtuális öröklés szabályainak követésére DoWork
szolgál, és a hívásokat az osztály C
implementálásához DoWork
irányítja.
Az alaposztály virtuális tagjainak elérése származtatott osztályokból
Egy olyan származtatott osztály, amely lecserélt vagy felülírt egy metódust vagy tulajdonságot, továbbra is hozzáférhet az alaposztály metódusához vagy tulajdonságához a base
kulcsszó használatával. Az alábbi kód egy példát mutat be:
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();
}
}
További információ: alap.
Feljegyzés
Javasoljuk, hogy a virtuális tagok saját base
implementációjukban hívják meg az adott tag alaposztály-implementációját. Az alaposztály viselkedésének engedélyezése lehetővé teszi, hogy a származtatott osztály a származtatott osztályra jellemző viselkedés implementálására összpontosítson. Ha az alaposztály implementációját nem hívják meg, a származtatott osztályon múlik, hogy a viselkedésük kompatibilis legyen az alaposztály viselkedésével.