Polymorfisme
Polymorfisme wordt vaak de derde pijler van objectgeoriënteerde programmering genoemd, na inkapseling en overname. Polymorfisme is een Grieks woord dat 'veelvormig' betekent en twee verschillende aspecten heeft:
- Tijdens runtime kunnen objecten van een afgeleide klasse worden behandeld als objecten van een basisklasse op plaatsen zoals methodeparameters en verzamelingen of matrices. Wanneer dit polymorfisme optreedt, is het gedeclareerde type van het object niet meer identiek aan het runtimetype.
- Basisklassen kunnen virtuele methoden definiëren en implementeren, en afgeleide klassen kunnen deze overschrijven, wat betekent dat ze hun eigen definitie en implementatie bieden. Wanneer tijdens runtime clientcode de methode aanroept, zoekt de CLR het runtimetype van het object op en roept deze onderdrukking van de virtuele methode aan. In uw broncode kunt u een methode aanroepen op een basisklasse en ervoor zorgen dat de versie van de methode van een afgeleide klasse wordt uitgevoerd.
Met virtuele methoden kunt u op een uniforme manier werken met groepen gerelateerde objecten. Stel dat u een tekentoepassing hebt waarmee een gebruiker verschillende soorten vormen op een tekenoppervlak kan maken. U weet niet op het moment dat u compileert welke specifieke typen shapes de gebruiker gaat maken. De toepassing moet echter alle verschillende typen shapes bijhouden die worden gemaakt en deze moeten worden bijgewerkt als reactie op gebruikersmuisacties. U kunt polymorfisme gebruiken om dit probleem op te lossen in twee basisstappen:
- Maak een klassehiërarchie waarin elke specifieke shapeklasse is afgeleid van een gemeenschappelijke basisklasse.
- Gebruik een virtuele methode om de juiste methode op een afgeleide klasse aan te roepen via één aanroep naar de basisklassemethode.
Maak eerst een basisklasse met de naam Shape
en afgeleide klassen, zoals Rectangle
, Circle
en Triangle
. Geef de Shape
klasse een virtuele methode aangeroepen Draw
en overschrijf deze in elke afgeleide klasse om de specifieke vorm te tekenen die door de klasse wordt aangegeven. Maak een List<Shape>
object en voeg er een Circle
, Triangle
en Rectangle
aan toe.
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();
}
}
Als u het tekenoppervlak wilt bijwerken, gebruikt u een foreach-lus om de lijst te doorlopen en de Draw
methode aan te roepen voor elk Shape
object in de lijst. Hoewel elk object in de lijst een gedeclareerd type Shape
heeft, is dit het runtimetype (de overschreven versie van de methode in elke afgeleide klasse) die wordt aangeroepen.
// 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# is elk type polymorf omdat alle typen, inclusief door de gebruiker gedefinieerde typen, overnemen van Object.
Overzicht van polymorfisme
Virtuele leden
Wanneer een afgeleide klasse overneemt van een basisklasse, bevat deze alle leden van de basisklasse. Al het gedrag dat in de basisklasse wordt gedeclareerd, maakt deel uit van de afgeleide klasse. Hiermee kunnen objecten van de afgeleide klasse worden behandeld als objecten van de basisklasse. Toegangsaanpassingen (public
, protected
enzovoort private
) bepalen of deze leden toegankelijk zijn vanuit de implementatie van afgeleide klassen. Virtuele methoden geven de ontwerper verschillende keuzes voor het gedrag van de afgeleide klasse:
- De afgeleide klasse kan virtuele leden in de basisklasse overschrijven, waarbij nieuw gedrag wordt gedefinieerd.
- De afgeleide klasse kan de dichtstbijzijnde basisklassemethode overnemen zonder deze te overschrijven, waarbij het bestaande gedrag behouden blijft, maar dat verdere afgeleide klassen de methode kunnen overschrijven.
- De afgeleide klasse kan nieuwe niet-virtuele implementatie definiëren van leden die de basisklasse-implementaties verbergen.
Een afgeleide klasse kan alleen een basisklasselid overschrijven als het basisklasselid wordt gedeclareerd als virtueel of abstract. Het afgeleide lid moet het onderdrukkingswoord gebruiken om expliciet aan te geven dat de methode bedoeld is om deel te nemen aan virtuele aanroep. De volgende code bevat een voorbeeld:
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; }
}
}
Velden kunnen niet virtueel zijn; alleen methoden, eigenschappen, gebeurtenissen en indexeerfuncties kunnen virtueel zijn. Wanneer een afgeleide klasse een virtueel lid overschrijft, wordt dat lid ook aangeroepen wanneer een exemplaar van die klasse wordt geopend als een exemplaar van de basisklasse. De volgende code bevat een voorbeeld:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = B;
A.DoWork(); // Also calls the new method.
Met virtuele methoden en eigenschappen kunnen afgeleide klassen een basisklasse uitbreiden zonder dat u de basisklasse-implementatie van een methode hoeft te gebruiken. Zie Versiebeheer met de onderdrukking en nieuwe trefwoorden voor meer informatie. Een interface biedt een andere manier om een methode of set methoden te definiëren waarvan de implementatie wordt overgelaten aan afgeleide klassen.
Basisklasseleden verbergen met nieuwe leden
Als u wilt dat uw afgeleide klasse een lid heeft met dezelfde naam als een lid in een basisklasse, kunt u het nieuwe trefwoord gebruiken om het lid van de basisklasse te verbergen. Het new
trefwoord wordt geplaatst vóór het retourtype van een klasselid dat wordt vervangen. De volgende code bevat een voorbeeld:
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; }
}
}
Verborgen basisklasseleden kunnen worden geopend vanuit clientcode door het exemplaar van de afgeleide klasse naar een exemplaar van de basisklasse te casten. Voorbeeld:
DerivedClass B = new DerivedClass();
B.DoWork(); // Calls the new method.
BaseClass A = (BaseClass)B;
A.DoWork(); // Calls the old method.
Voorkomen dat afgeleide klassen virtuele leden overschrijven
Virtuele leden blijven virtueel, ongeacht het aantal klassen dat is gedeclareerd tussen het virtuele lid en de klasse die deze oorspronkelijk heeft gedeclareerd. Als de klasse A
een virtueel lid declareert en de klasse B
is afgeleid van A
, en de klasse C
is afgeleid van B
, neemt de klasse C
het virtuele lid over en kan deze overschrijven, ongeacht of de klasse B
een onderdrukking heeft gedeclareerd voor dat lid. De volgende code bevat een voorbeeld:
public class A
{
public virtual void DoWork() { }
}
public class B : A
{
public override void DoWork() { }
}
Een afgeleide klasse kan virtuele overname stoppen door een onderdrukking als verzegeld te declareren. Als overname wordt gestopt, moet u het sealed
trefwoord vóór het override
trefwoord in de declaratie van het klasselid plaatsen. De volgende code bevat een voorbeeld:
public class C : B
{
public sealed override void DoWork() { }
}
In het vorige voorbeeld is de methode DoWork
niet langer virtueel voor een klasse die is afgeleid van C
. Het is nog steeds virtueel voor exemplaren van C
, zelfs als ze worden gecast naar type B
of type A
. Verzegelde methoden kunnen worden vervangen door afgeleide klassen met behulp van het new
trefwoord, zoals in het volgende voorbeeld wordt weergegeven:
public class D : C
{
public new void DoWork() { }
}
Als er in dit geval DoWork
een variabele van het type D
wordt gebruiktD
, wordt de nieuwe DoWork
aangeroepen. Als een variabele van het type C
, B
of A
wordt gebruikt voor toegang tot een exemplaar van D
, een aanroep om de regels van virtuele overname te DoWork
volgen, routeren deze aanroepen naar de implementatie van DoWork
on-class C
.
Virtuele leden van basisklasse openen op basis van afgeleide klassen
Een afgeleide klasse die een methode of eigenschap heeft vervangen of overschreven, heeft nog steeds toegang tot de methode of eigenschap op de basisklasse met behulp van het base
trefwoord. De volgende code bevat een voorbeeld:
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();
}
}
Zie basis voor meer informatie.
Notitie
Het wordt aanbevolen dat virtuele leden de base
basisklasse-implementatie van dat lid aanroepen in hun eigen implementatie. Door het gedrag van de basisklasse op te laten treden, kan de afgeleide klasse zich concentreren op het implementeren van gedrag dat specifiek is voor de afgeleide klasse. Als de implementatie van de basisklasse niet wordt aangeroepen, is het aan de afgeleide klasse om hun gedrag compatibel te maken met het gedrag van de basisklasse.