Bestaande interfaces in .NET

De bestaande .NET klassen gebruiken vaak interfaces om bepaalde zaken uit te voeren. Zo heeft .NET tal van interfaces gedefiniëerd (bv. IEnumerable, IDisposable, IList, IQueryable enz.) waar je zelfgemaakte klassen mogelijk aan moeten voldoen indien ze bepaalde bestaande methoden wensen te gebruiken.

Een typisch voorbeeld is het gebruik van de Array.Sort methode. Hier wordt het echte nut van interfaces erg duidelijk: de ontwikkelaars van .NET kunnen niet voorspellen hoe andere ontwikkelaars hun bibliotheken gaan gebruiken. Via interfaces geven ze als het ware krijtlijnen en vanaf dan moeten de ontwikkelaars zelf maar bepalen hoe hun nieuwe klassen zullen samenwerken met die van .NET.

Sorteren met Array.Sort en de IComparable interface

Een veelgebruikte .NET interface is de IComparable interface. Deze wordt gebruikt indien .NET bijvoorbeeld een array van objecten wil sorteren. Bij wijze van demonstratie zal ik demonstreren waarom deze interface erg nuttig kan zijn.

Stap 1: Het probleem

Indien je een array van objecten hebt en je wenst deze te sorteren via Array.Sort dan dienen de objecten de IComparable interface te hebben.

We willen een array van landen kunnen sorteren op grootte van oppervlakte.

Stel dat we de klasse Land hebben:

internal class Land
{
    public string Naam {get;set;}
    public int Oppervlakte {get;set;}
    public int Inwoners {get;set;}
}

We plaatsen 3 landen in een array:

Land[] eurolanden = new Land[3];
eurolanden[0] = new Land() {Naam = "België", Oppervlakte = 5, Inwoners = 2000};
eurolanden[1] = new Land() {Naam = "Frankrijk", Oppervlakte = 7, Inwoners = 2500};
eurolanden[2] = new Land() {Naam = "Nederland", Oppervlakte = 6, Inwoners = 1800};

Wanneer we nu zouden proberen de landen te sorteren:

Array.Sort(eurolanden);

Dan treedt er een uitzondering op:InvalidOperationException: Failed to compare two elements in the array. Dit is erg logisch: .NET heeft geen flauw benul hoe objecten van het type Land moeten gesorteerd worden. Moet dit alfabetisch volgens de Naam property, of van groot naar klein op aantal Inwoners? Enkel jij als ontwikkelaar weet momenteel hoe er gesorteerd moet worden.

Stap 2: IComparable onderzoeken

We kunnen dit oplossen door de IComparable interface in de klasse Land te implementeren. We bekijken daarom eerst de documentatie van deze interface1. De interface is beschreven als:

interface IComparable
{
    int CompareTo(Object obj);
}

OPGELET: Deze interface bestaat al in .NET en mag je dus niet opnieuw in code schrijven!

Daarbij moet de methode een int teruggeven als volgt:

WaardeBetekenis
Getal kleiner dan 0Huidig object komt voor het obj dat werd meegegeven.
0Huidig object komt op dezelfde positie als obj.
Getal groter dan 0Huidig object komt na obj.

De Array.Sort methode zal werken tegen deze IComparable interface om juist te kunnen sorteren. Het verwacht dat de klasse in kwestie een int teruggeeft volgens de afspraken van de tabel hierboven.

Stap 3: IComparable in Land implementeren

We zorgen er nu voor dat Land deze interface implementeert. Daarbij willen we dat de landen volgens oppervlakte worden gesorteerd :

internal class Land: IComparable
{
    public int CompareTo(object obj)
    {
        Land temp =  obj as Land;
        if(temp != null)
        {  
            if(Oppervlakte > temp.Oppervlakte) 
                return 1;
            if(Oppervlakte < temp.Oppervlakte) 
                return -1;
            return 0;
        }
        else
            throw new NotImplementedException("Object is not a Land"); 
    }
}

Nu zal de Sort werken:

Array.Sort(eurolanden);

De Sort()-methode kan nu ieder object bevragen via de CompareTo()-methode en zo volgens een eigen interne sorteeralgoritme de landen in de juiste volgorde plaatsen.

Stel dat vervolgens nog beter willen sorteren: we willen dat landen met een gelijke oppervlakte, op hun aantal inwoners gesorteerd worden:

public int CompareTo(object obj)
{

    Land temp = obj as Land;
    if(temp != null)
    { 
        if(Oppervlakte > temp.Oppervlakte) return 1;
        if(Oppervlakte < temp.Oppervlakte) return -1;
        if(this.Inwoners > temp.Inwoners) return 1;
        if(this.Inwoners < temp.Inwoners) return -1;
    }
    else
        throw new ArgumentException("Object is not a Land"); 
    
}

Ik laat jou de code schrijven wat er moet gebeuren indien het aantal inwoners én de oppervlakte dezelfde is. Misschien kan je dan sorteren volgens de Naam van het land.

De bestaande datatypes in .NET hebben allemaal de IComparable interface ingebakken. Zo ook dus de gekende primitieve datatypes. string dus ook en laat dus toe om bijvoorbeeld snel te weten welke van 2 string alfabetisch eerst komt, als volgt:

return this.Naam.CompareTo(temp.Naam);

Kortom, voeg dit achteraan de eerder geschreven vergelijkingen in je Land-klasse om finaal de Naam te gebruiken als sorteer-element.

List sorteren

Indien je een List<Land> zou willen sorteren in plaats van een array van Land dan kan dit ook. Nog steeds vereisen we dat je klasse de IComparable interface gebruikt. We kunnen nu de ingebouwde Sort-methode van de List klasse gebruiken. Stel dat je een lijst van landen hebt genaamd landLijst, deze sorteren kan dan heel eenvoudig als volgt:

landLijst.Sort();

Zo simpel!

Merk op dat we hier de lijst zelf sorteren. Er wordt dus geen nieuwe lijst teruggegeven zoals bij Array.Sort() het geval is.

Indien je toch liever Array.Sort gebruikt dan kunnen we een andere, handige, ingebouwde List-methode gebruiken, namelijk ToArray(), als volgt:

landLijst = Array.Sort(landLijst.ToArray());