OOD Handboek

5Polymorfisme

Veelvormigheid: dezelfde aanroep, maar elk object voert zijn eigen versie uit.

OverloadingOverriding
WatZelfde naam, andere parameters, in dezelfde klasseSubklasse geeft eigen implementatie van ouder-methode
Wanneer bepaaldCompile-timeRuntime (late binding / dynamic dispatch)
Keywordsgeenvirtual/abstract + override
VoorbeeldTel(int a, int b) en Tel(int a, int b, int c)override decimal BerekenPrijs(int m)

#Polymorfe collectie

List<Voertuig> lijst = new()
{
    new Fiets("F1", "Gazelle", 7),
    new Scooter("S1", "Niu", "DX-123-B", 80),
};
foreach (Voertuig v in lijst)
{
    // juiste override wordt automatisch gekozen
    Console.WriteLine($"{v.Id}: {v.BerekenPrijs(20):0.00}");
    Console.WriteLine(v);   // roept overridden ToString() aan
}
Uitleg

Eén lijst van het type Voertuig met verschillende soorten erin. De foreach roept steeds dezelfde methode aan, maar elk object voert zijn eigen versie uit. Dat is polymorfisme: nieuwe voertuigtypes werken zonder deze code aan te passen.

Functies en keywords
$"...{x}"
String-interpolatie: zet de waarde van x direct in de tekst.
new()
Target-typed new: het type wordt afgeleid uit de declaratie links.
foreach
Loopt één voor één door alle elementen van een collectie.
List<T>
Lijst die automatisch groeit; toevoegen met Add, aantal met Count.

#Indexer

class Collectie
{
    private int[] _arr = new int[10];
    public int this[int i]
    {
        get => _arr[i];
        set => _arr[i] = value;
    }
}
var c = new Collectie(); c[0] = 5;
Uitleg

Met een indexer (this[int i]) kun je een eigen klasse gebruiken alsof het een array is. Handig als je de opslag verborgen wilt houden of bij elke toegang wilt controleren.

Functies en keywords
this[...]
Indexer: maakt obj[i] mogelijk op een eigen klasse.
private
Alleen zichtbaar binnen deze klasse.
value
In een setter: de waarde die iemand probeert toe te kennen.
=>
Korte schrijfwijze voor een methode of property die één expressie teruggeeft; in LINQ ook de pijl van een lambda.
var
De compiler leidt het type zelf af uit de rechterkant.

#Upcasting en downcasting

// UPCASTING: kind → ouder/interface. Altijd veilig, impliciet.
Hond hond = new Hond("Rex");
Dier dier = hond;                  // geen cast nodig
IVerzorgbaar z = hond;             // via interface
dier.MaakGeluid();                 // "Woef!" (dynamic dispatch)
// dier.Verzorg();                 // FOUT: Dier kent Verzorg niet

// DOWNCASTING: ouder → kind. Expliciet, kan mislukken.
Hond h1 = (Hond)dier;              // InvalidCastException als het geen Hond is

// Veilig optie 1: is-pattern
if (dier is Hond h2) h2.Verzorg();

// Veilig optie 2: as (geeft null bij mislukking)
Hond? h3 = dier as Hond;
h3?.Verzorg();

// Veilig in collecties
List<Hond> honden = dieren.OfType<Hond>().ToList();
Uitleg

Upcasting (kind naar ouder of interface) gaat altijd goed en vanzelf; je ziet dan alleen nog wat de ouder kent. Terug naar het kind (downcasting) moet je expliciet doen. Een harde cast crasht als het type niet klopt, is en as controleren eerst en zijn dus veilig.

Functies en keywords
as
Probeert te casten en geeft null als het type niet past, in plaats van een exception.
?.
Roept alleen aan als het object niet null is; anders is het resultaat null.
OfType<T>()
LINQ: houdt alleen elementen van type T over en cast ze meteen; veilig downcasten.
ToList()
Maakt van het LINQ-resultaat een nieuwe List<T>.
(cast)
Harde cast: dwingt het type af en gooit InvalidCastException als het niet klopt.
is
Controleert het type (of een patroon) en kan het object meteen in een nieuwe variabele zetten.
List<T>
Lijst die automatisch groeit; toevoegen met Add, aantal met Count.
Upcasting in de praktijk
Een private List<Voertuig> naar buiten aanbieden als IReadOnlyList<Voertuig> is upcasting via de abstracte klasse (en beschermt de lijst). OfType<IOplaadbaar>() in een List<IOplaadbaar> is upcasting via de interface.