Alle types in C# zijn afstammelingen van de System.Object
klasse. Indien je een klasse schrijft zonder een expliciete parent dan zal deze steeds System.Object
als rechtstreekse parent hebben. Ook afgeleide klassen stammen dus af van System.Object
. Concreet wil dit zeggen dat alle klassen System.Object
-klassen zijn en dus ook de bijhorende functionaliteit ervan hebben.
Merk op dat we hier niet alleen onze eigen klassen bedoelen, maar alle types, dus zelfs int
, bool
, string
,... Alle types stammen (al dan niet rechtstreeks) af van System.Object
.
Indien je de System namespace in je project gebruikt door bovenaan using System;
te schrijven dan hoef je dus niet altijd System.Object
te schrijven maar mag je ook Object
schrijven.
Wanneer je een lege klasse maakt dan zal je zien dat instanties van deze klasse reeds een aantal methoden ingebouwd hebben. Dit komt omdat deze methoden gedefinieerd zijn in de System.Object
klasse en meteen overgeërfd worden door je nieuwe klasse:
Methode
Beschrijving
Equals(Object o)
Gebruikt om te ontdekken of twee instanties "gelijk" zijn. Wat dit betekent kan bepaald worden door de auteur van de klasse.
GetHashCode()
Geeft een unieke code (hash) terug van het object; nuttig om o.a. snel te sorteren.
GetType()
Geeft het type (of klasse) van het object terug. Dit is een object van het type Type
!
ToString()
Geeft een string terug die het object voorstelt.
Er zijn er nog een paar, maar de rest ga je minder vaak tegenkomen.
Stel dat je een klasse Student
hebt gemaakt in je project. Je kan dan op een object van deze klasse de GetType() -methode aanroepen om te weten wat het type van dit object is:
Dit zal als uitvoer de namespace gevolgd door het type op het scherm geven. Als je klasse dus in de namespace StudentManager
staat, zal er verschijnen: StudentManager.Student
.
Wil je enkel het type zonder namespace dan is het nuttig te beseffen dat GetType()
een object teruggeeft van het type Type
met meerdere eigenschappen, waaronder Name
. Volgende code zal dus enkel Student
op het scherm tonen:
Deze methode is vooral nuttig in code voor frameworks en dergelijke. Dat wil zeggen: code waaraan je jouw eigen code kan toevoegen. In een meer typische eigen applicatie zou je hier niet te veel gebruik van hoeven te maken, anders schort er waarschijnlijk iets aan je ontwerp.
Deze is de nuttigste waar je al direct leuke dingen mee kan doen. Wanneer je schrijft:
wordt je code eigenlijk herschreven naar:
Op het scherm verschijnt dan StudentManager.Student
. Waarom? Wel, de methode ToString()
wordt in System.Object()
ongeveer als volgt beschreven:
Merk twee zaken op:
GetType
wordt aangeroepen en die output krijg je terug.
De methode is virtual gedefinieerd.
De hierboven vermelde methoden (behalve GetType
) in System.Object
zijn virtual
, en je kan deze overschrijven!
ToString() overriden
Het zou natuurlijk fijner zijn dat de ToString()
van onze student nuttigere info teruggeeft, zoals bv de interne Naam (string autoproperty) en Leeftijd (int autoproperty). We kunnen dat eenvoudig krijgen door gewoon ToString
to overriden:
Wanneer je nu Console.WriteLine(stud1);
zou schrijven, dan wordt je output bijvoorbeeld: Student Wolfgang Amadeus Mozart (Leeftijd:35)
.
Ook deze methode kan je dus overriden om twee objecten met elkaar te vergelijken. Hierbij moet je een applicatiespecifiek antwoord kunnen geven op de vraag "wanneer zijn twee objecten aan elkaar gelijk?"
De Equals
methode heeft dus als signatuur: public virtual bool Equals(Object o)
.NET maakt volgende afspraken voor een geldige implementatie van Equals
, maar het is aan jou om te zorgen dat je code deze afspraken volgt:
Het moet false
teruggeven indien het argument o
null
is
Het moet true
teruggeven indien je het object met zichzelf vergelijkt (bv stud1.Equals(stud1)
)
Het mag enkel true
teruggeven als volgende statements beide waar zijn:
Indien stud1.Equals(stud2)
true teruggeeft en stud1.Equals(stud3)
ook true is, dan moet stud2.Equals(stud3)
ook true zijn.
Volgt je eigen code deze afspraken niet, dan krijg je geen compilatiefouten, maar dan kan je wel onverwacht gedrag krijgen van code die gebruik maakt van Equals
zoals bijvoorbeeld de IndexOf
-methode van List<T>
.
Stel dat we vinden dat een student gelijk is aan een andere student indien z'n Naam
en Leeftijd
dezelfde zijn, we kunnen dan de Equals
-methode overriden als volgt:
De lijn Student temp = (Student) o;
zal het object o
casten naar een Student
. Doe je dit niet dan kan je niet aan de interne Student
-variabelen van het object o
. Het feit dat een object met het statische type (d.w.z.: zo staat het bij de parameter) Object
tijdens de uitvoering ook een object met runtime type Student
(d.w.z.: dat is het soort data dat op de heap staat) kan zijn is een voorbeeld van polymorfisme.
==
Equals
en ==
doen verschillende zaken. ==
is strenger. Het controleert volgende zaken:
Voor de meeste reference types: gaat het om exact dezelfde data (d.w.z. hetzelfde adres op de heap)?
Voor strings: gaat het om dezelfde tekst?
Voor value types: is de waarde aan de linkerkant een kopie van die aan de rechterkant?
GetHashCode()
zorgt ervoor dat je een "vingerafdruk" van een object krijgt. Als twee objecten gelijk zijn onder Equals
, wordt verondersteld dat ze dezelfde vingerafdruk hebben. Omgekeerd geldt niet, maar het is voor de efficiëntie wel beter als verschillende objecten ook verschillende vingerafdrukken leveren. Het is opnieuw aan de programmeur om dit te garanderen.
Een efficiënte hashfunctie voor een type K
zorgt er bijvoorbeeld voor dat opzoekingen van keys in een Dictionary<K,V>
erg snel verlopen. Een minder efficiënte hashfunctie zal opzoekingen in datzelfde Dictionary
trager laten verlopen. Een foute hashfunctie kan er voor zorgen dat je Dictionary
niet meer werkt zoals verwacht.
Wij behandelen de theorie achter goede hashfuncties hier niet. Je kan gewoon deze vuistregel onthouden: als je een nieuwe hashfunctie moet voorzien (normaal omdat je Equals
hebt overschreven; de compiler waarschuwt je hier ook voor), kan je de hashwaarde van een onderdeel laten berekenen dat gelijk is voor twee objecten die gelijk zijn, op voorwaarde dat dat onderdeel niet verandert. Dat komt omdat je niet wil dat een object nog van hashcode verandert nadat je het in een Dictionary
, HashSet
of andere structuur hebt geplaatst.
Bijvoorbeeld:
In ons systeem is dit voldoende, want:
als twee studenten gelijk zijn onder Equals
, is hun naam gelijk (en dus de hashcode van hun naam)
Naam
wordt ingesteld bij constructie en kan daarna niet veranderd worden op basis van de code die we hebben