Zie verder

Even terugblikken

In dit hoofdstuk ontdekte je dat elke klasse in C# uiteindelijk overerft van System.Object. Daardoor heeft elk object al vier ingebouwde methoden, waarvan er drie virtual zijn en dus door jou aangepast kunnen worden. Daarnaast leerde je abstracte klassen kennen: parent-klassen waarvan je geen instanties kan maken, maar die wel gedeelde code en verplichte “gaten” voor de child-klassen kunnen bevatten. Tot slot zag je dat ook exceptions gewone klassen zijn, waardoor je er zelf een kan maken door van Exception over te erven.

De kern op een rij:

  • Alle klassen stammen af van System.Object; ook ingebouwde types en zelfs int en bool.
  • ToString(), Equals() en GetHashCode() zijn virtual en kan je overriden; GetType() niet, want die moet altijd het echte runtime-type teruggeven.
  • Override je Equals, dan moet je ook GetHashCode overriden (gebruik HashCode.Combine(...)), anders krijg je rare bugs als je het object als sleutel in een Dictionary of HashSet gebruikt.
  • Een abstract klasse kan je niet instantiëren; een abstract methode heeft geen body en de child-klasse moet ze overriden.
  • abstract en sealed zijn elkaars tegenpolen: bij abstract moet je overerven, bij sealed mag je net niet overerven.
  • Een eigen exception is een klasse die overerft van Exception. Je werpt ze op met throw new ... en vangt ze op met een catch op dat type. Met de drie standaard-constructors kan je er onder meer een eigen Message aan meegeven.
WaarschuwingValkuilen
  • Override je Equals zonder null- of type-controle en cast je hard met (Student)o, dan crasht je code op een null of een verkeerd type. Gebruik if (o is not Student temp) return false;.
  • Een abstracte property zoals public abstract int MaxLeeftijd { get; } is géén auto-property: er zit geen verborgen instantievariabele achter, de child moet de implementatie zelf voorzien.
  • Gooi je een opgevangen exception verder met throw e;, dan wis je de stack trace en lijkt het alsof de fout daar ontstond. Schrijf throw;.
  • Een eigen exception zonder constructor met een string-parameter kan je geen boodschap meegeven: new Timception("...") compileert dan niet.

In andere talen

Één oer-klasse

Dit is geen typisch C#-trucje: ook Java kent precies dezelfde oer-klasse, daar gewoon Object genoemd. Elke klasse erft er impliciet van, en je krijgt er eveneens methoden zoals toString(), equals() en hashCode() bovenop:

class Student {
    // erft automatisch van java.lang.Object
}

Student stud = new Student();
System.out.println(stud.getClass().getName()); // Student

Heel gelijkaardig dus: één gemeenschappelijke basisklasse waar alles van afstamt. Ook Python doet dit met object.

Abstract zonder keyword

C++ kent het keyword abstract niet. Toch bestaat exact hetzelfde idee daar onder een andere naam: een “pure virtual” methode. Je zet = 0 achter de signatuur, en zo’n klasse kan je niet instantiëren:

class Dier {
public:
    virtual std::string MaakGeluid() = 0; // pure virtual: geen body
};

class Paard : public Dier {
public:
    std::string MaakGeluid() override { return "Hinnikhinnik"; }
};

Het is hetzelfde concept: een klasse met een gat dat de child moet invullen. Python doet dit met de ABC-module en @abstractmethod, dichter bij de C#-aanpak.

Zoek de fout

Onderstaande C#-code wil twee studenten vergelijken op hun naam. De Equals-override doet z’n werk, maar bij het gebruik van Student-objecten als sleutel in een Dictionary of HashSet lopen dingen mis. Wat ontbreekt er?

internal class Student
{
    public string Voornaam { get; set; }
    public int Geboortejaar { get; set; }

    public override bool Equals(object o)
    {
        if (o is not Student temp)
            return false;
        return Geboortejaar == temp.Geboortejaar && Voornaam == temp.Voornaam;
    }
}

Wie Equals override, moet ook GetHashCode override. Twee gelijke objecten horen dezelfde hashcode terug te geven, en daar rekenen Dictionary en HashSet op. Voeg toe:

public override int GetHashCode()
{
    return HashCode.Combine(Voornaam, Geboortejaar);
}