Generics en collecties in C#
- Inleiding
- Variabelen
- Collectie Array
- Collectie ArrayList
- Generics List<T>
- Generic Constraints
- Slot
- Voorbeeldprogramma
Inleiding
In C# worden de termen Collecties en Generics in één adem genoemd. Het zijn zaken die te maken hebben met het opslaan van gegevens. Het opslaan begint meestal met een geheugenvariabele dat verwijst naar het stukje geheugen waar het gegeven is opgeslagen.
Het wordt echter vrij onoverzichtelijk als we allerlei losse variabelen gaan gebruiken voor meerdere, min of meer bij elkaar horende gegevens. Het is dan overzichtelijker om voor al die gegevens een collectie te definiëren.
En als we collecties definiëren dan is het efficiënter als we de collecties met generics generiek maken zodat we een collectie voor meerdere gegevenstypes en objecten kunnen gebruiken. Voor elk gegevenstype apart een nieuwe collectie definiëren? Dat hoeft dan niet meer. De collectie wordt zo een generiek iets dat als een Zwitsers zakmes voor meerdere dingen gebruikt kan worden.
In deze post zullen we de collecties en de generics verder toelichten aan de hand van de drie fictieve autofabrikanten die in eerdere posts ook al de revue passeerden.
Variabelen
We kunnen aparte geheugenvariabelen gebruiken voor onze autofabrikanten:
string AutoFabrikant1 =
"GHB - GuanggongHunnenburgBetrieb";
string AutoFabrikant2 =
"MGIM - Migraine Imperialist Motors
Corporation Groupe";
string AutoFabrikant3 =
"XPAZ - Xiaonlang /
Pobeda Avtomobilny Zavod";
Console.WriteLine("Autofabrikanten
(losse variabelen):");
Console.WriteLine("{0}",AutoFabrikant1);
Console.WriteLine("{0}",AutoFabrikant2);
Console.WriteLine("{0}",AutoFabrikant3);
Zo kunnen we als keuringsinstantie van automobielen voor elke autofabrikant een variabele definiëren, maar het is de vraag of we dat willen. Ons assortiment kan namelijk bestaan uit auto’s die ook door andere fabrikanten zijn gemaakt. Je programma wordt dan erg onderhoudsintensief als je elke keer een nieuwe variabele gaat aanmaken voor elke autofabrikant die nog niet bij jou bekend is.
Collectie Array
Met collecties hoef je maar één “ding” te definiëren waarbij dat ene “ding” meerdere, min of meer bij elkaar horende waarden omvat. In dit geval gebruiken we één string array waarin we de namen van de autofabrikanten zetten. Een variabele voor elke afzonderlijke autofabrikant is dan niet meer nodig.
Er zijn naast de array ook andere collecties die door C# worden ondersteund. De array is in vergelijking met andere collecties wat minder flexibel. Zo moet je gebruik maken van de juiste voorgedefinieerde array (een string array, een int array of…) en je moet van te voren aangeven uit hoeveel elementen de array bestaat.
Een getal in de desbetreffende array zetten of een vierde autofabrikant als string toevoegen aan de string array? Dat gaat in dit geval niet omdat de array een string array is en een string array alleen maar strings kan bevatten. Verder heb je van te voren aangegeven dat de array uit maximaal drie elementen bestaat waardoor het toevoegen van een vierde string (de vierde autofabrikant) niet mogelijk is.
Console.WriteLine("Autofabrikanten (array):");
// We geven hier op dat de array
// uit max drie elementen bestaat
string[] Autofabrikanten = new string[3];
Autofabrikanten[0] =
"GHB - GuanggongHunnenburgBetrieb";
Autofabrikanten[1] =
"MGIM - Migraine Imperialist Motors
Corporation Groupe";
Autofabrikanten[2] =
"XPAZ - Xiaonlang /
Pobeda Avtomobilny Zavod";
for (int i = 0; i < Autofabrikanten.Length; i++)
{
Console.WriteLine(
"{0}",
Autofabrikanten[i]);
}
Resultaat:
Autofabrikanten (array):
GHB - GuanggongHunnenburgBetrieb
MGIM - Migraine Imperialist Motors Corporation Groupe
XPAZ - Xiaonlang / Pobeda Avtomobilny Zavod
Collectie ArrayList
De ArrayList collectie is de tegenpool van de Array. De arrayList is super flexibel. Alles mag in de arrayList gezet worden en je hoeft niet van te voren op te geven uit hoeveel elementen de collectie bestaat. De arrayList vergroot automatisch haar capaciteit zodra een nieuw element wordt toegevoegd:
// Dit gaan we in de ArrayList zetten
int getal = 123;
string tekst = "ABC" ;
GHB ghbAutomobiel = new GHB();
// De ArrayList
ArrayList lijst = new ArrayList();
lijst.Add(getal);
lijst.Add(tekst);
lijst.Add(ghbAutomobiel);
Alles in een ArrayList zetten dat is één, maar je zult het nodige moeten doen om het er weer uit te krijgen. Zo kan een element van een ArrayList van alles wezen en je zult eerst moeten bepalen om wat voor soort object het gaat. We doen dat in dit voorbeeld met een GetType(). Weten we om wat voor soort object het gaat, dan is de volgende stap dat we het element van de ArrayList moeten omzetten naar dat type object (unboxen). En we hebben, als we dat gedaan hebben uiteindelijk een object met alle specialiteiten die bij dat object horen.
// Wat hebben we in de ArrayList gezet?
Console.WriteLine(
"We stoppen alles in een ArrayList
en dit staat er in:");
for (int i = 0; i < lijst.Count;i++)
{
switch(Convert.ToString(lijst[i].GetType()))
{
case "System.Int32":
int getal_ = (int)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1} ",
lijst[i].GetType(), getal_);
break;
case "System.String":
string tekst_ = (string)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1} ",
lijst[i].GetType(), tekst_);
break;
case "Generieken.GHB":
GHB ghb_ = (GHB)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1}",
lijst[i].GetType(),
ghb_.MissionStatement());
break;
}
}
Resultaat, de verschillende objecten die we in de ArrayList gestopt hebben. Zo kunnen we na wat “unboxen” bijvoorbeeld een int-object hebben waarop we rekenkundige bewerkingen kunnen loslaten. Met een string-object kan dat echter niet met en met een GHB-automobiel-object al helemaal niet. Een GHB-object heeft wel weer haar eigen MissionStatement()-methode, een methode dat de andere objecten niet hebben.
We stoppen alles in een ArrayList en dit staat er in:
Een element van het type System.Int32
met als inhoud: 123
Een element van het type System.String
met als inhoud: ABC
Een element van het type Generieken.GHB
met als inhoud: Wij van GHB streven naar
wereldwijde samenwerkingsverbanden.
Elk voordeel heeft zijn nadeel en dat geldt ook voor de ArrayList. Je hebt veel flexibilititeit voor wat je in een ArrayList kunt stoppen, maar je moet boxen (gebeurt bij de ArrayList automatisch achter de schermen) om alles in de ArrayList te krijgen en je moet weer unboxen om alles er weer uit te krijgen. Het elke keer weer boxen en unboxen is niet goed voor de performance en de software wordt op die manier ook niet echt strongly typed.
Generics List<T>
Generics werden in C# 2.0 geïntroduceerd en staan voor een concept waarbij gegevenstypes en het soort object waarmee iets gedaan moet worden ook als parameter meegegeven kan worden aan de desbetreffende collectie of methode. Als een gegevenstype of objectsoort als parameter meegegeven kan worden dan wordt dit aangeduid met een <T> symbool en de desbetreffende collectie en methode worden dan beschouwd als generiek (generic).
Zo hebben we de List<T> collectie waarbij het aan jou is wat je wilt opnemen in de List. Als je bijvoorbeeld string-objecten wilt opnemen in de List dan dien je dat bij de ingebruikname van de List als volgt op te geven: list = new List<string>(); Het generieke zit hem erin dat de te gebruiken gegevenstype of objectsoort als parameter meegegeven kan worden. Zo kan je dit opgeven als de List<T> bijvoorbeeld int-objecten moet bevatten: list = new List<int>().
Als we de generieke List<T> collectie vergelijken met de eerder besproken Array en ArrayList dan zien we dat een generieke collectie een middenweg is tussen enerzijds de Array en anderzijds de ArrayList.
Zo staat van de string array vast dat deze alleen maar voor string objecten gebruikt mag worden. Microsoft heeft dat zo bepaald en discussie is verder niet mogelijk. Bij de generieke collectie bepaal jij waarvoor de generieke collectie gebruikt wordt. Wat je in de collectie stopt? Dat geef jij op in jouw programma en dat is niet iets wat door Microsoft wordt bepaald.
Je hoeft bij de ArrayList geen discussies te voeren over wat erin mag. Alles is toegestaan en de ArrayList zal nooit protesteren. De generieke collectie is daarentegen wat kritischer. De generieke collectie zal objecten van een ander type niet accepteren als je eerder in het programma hebt opgegeven dat de collectie alleen maar bestemd is voor bijvoorbeeld string objecten. Het strongly typed zijn wordt zo ook afgedwongen.
Bij een generieke collectie hoef je verder niet te boxen en te unboxen omdat het soort object bekend is dat in de collectie opgeslagen wordt.
We gebruiken in onderstaand voorbeeld een generieke List<T> collectie waarbij we aangeven dat de collectie uitsluitend bedoeld is voor string objecten. List<T> vergroot automatisch haar capaciteit zodra een nieuw element wordt toegevoegd
Console.WriteLine(
"We stoppen alles in een List
van het type string.");
List<string>
list = new List<string>();
list.Add(
"GHB -
GuanggongHunnenburgBetrieb");
list.Add(
"MGIM - Migraine Imperialist Motors
Corporation Groupe");
list.Add(
"XPAZ -
Xiaonlang / Pobeda Avtomobilny Zavod");
// Alles uit de List halen
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(
"- Automerk '{0}'
maakt deel uit van de lijst.",list[i]);
}
Resultaat:
We stoppen alles in een List van het type string.
- Automerk 'GHB -
GuanggongHunnenburgBetrieb'
maakt deel uit van de lijst.
- Automerk 'MGIM -
Migraine Imperialist Motors
Corporation Groupe'
maakt deel uit van de lijst.
- Automerk 'XPAZ -
Xiaonlang /
Pobeda Avtomobilny Zavod'
maakt deel uit van de lijst.
Generic Constraints
Wat je in een generieke collectie gaat zetten of door een generieke methode wilt laten verwerken, dat kun je ook weer beperken middels een Generic Constraint. We gaan het één en ander verduidelijken aan de hand van het voorbeeld in deze post.
We zijn als keuringsinstantie van automobielen geïnteresseerd in automobielen. Daarbij gaat onze interesse uit naar alle automobielen die gearriveerd zijn in Nederland en die klaar staan om de Nederlandse markt te betreden. We beperken ons niet tot de automobielen van de drie eerder genoemde autofabrikanten. Onze “scope” is ruim, maar ook weer niet zo ruim dat we geïnteresseerd zijn in bijvoorbeeld huisdieren. We zijn immers geen opvangcentrum voor weggelopen huisdieren.
We maken onderstaand waarbij een methode alleen maar datgene kan krijgen wat relevant is voor de desbetreffende methode:
// Objecten
GHB ghb = new GHB();
MGIM mgim = new MGIM();
XPAZ xpaz = new XPAZ();
Huiskat huiskat = new Huiskat();
// Onderzoek de objecten
Console.WriteLine(
"Wat drijft de autofabrikant?");
Console.WriteLine(
"Het antecedentenonderzoek heeft
alleen betrekking op
autofabrikanten.");
AntecedentenAutoFabrikant(ghb);
AntecedentenAutoFabrikant(mgim);
AntecedentenAutoFabrikant(xpaz);
AntecedentenAutoFabrikant(huiskat);
Console.WriteLine("{0}",
huiskat.OverJeDier());
Een object is de input voor statische methode AntecedentenAutoFabrikant<T> en de methode maken we generiek. We willen dat de methode alleen maar kan werken met objecten waarvan diens klassen interface IAutomobiel hebben geïmplementeerd.
We kunnen voor het inbouwen van zo’n restrictie gebruik maken van een generic constraint en de generic constraint ziet er in dit geval als volgt uit: where T: IAutomobiel.
public static void
AntecedentenAutoFabrikant<T>
(T AutoFabrikant) where T : IAutomobiel
{
// Dit is een generieke methode die
// alleen objecten accepteert die interface
// IAutomobiel geïmplementeerd hebben
Console.WriteLine(
"- " + AutoFabrikant.MissionStatement());
}
Interface IAutomobiel is niet geïmplementeerd door de klasse van object huiskat en het object mag niet door de desbetreffende functie verwerkt worden. De compiler zal dan ook een foutmelding geven als we object huiskat aanbieden aan methode AntecedentenAutoFabrikant<T> omdat we met een generic constraint een restrictie hebben ingebouwd:
Als we de generic constraint daarentegen beperken tot objecten die geïnstantieerd zijn uit klasse GHB dan krijgen we weer een te beperkte “scope” want we zijn geïnteresseerd in alle automobielen en niet alleen in de automobielen van autofabrikant GHB.
Hieronder het resultaat van methode AntecedentenAutoFabrikant<T>. Voor ieder object wordt methode MissionStatement gestart waarmee duidelijk wordt wat de drijfveren zijn van een autofabrikant.
Wat drijft de autofabrikant?
Het antecedentenonderzoek heeft
alleen betrekking op autofabrikanten.
- Wij van GHB streven naar
wereldwijde samenwerkingsverbanden.
- Wij van MGIM streven naar
imperialistische werelddominantie.
- Wij van XPAZ streven naar
volksbevrijdingsautomobielen.
We zien in dit voorbeeld de toegevoegde waarde van de generic constraint want methode AntecedentenAutoFabrikant<T> gaat er stilzwijgend vanuit dat de binnengekomen objecten in het bezit zijn van methode MissionStatement.
En dat gaat allemaal goed omdat de generic constraint ervoor zorgt dat alleen objecten binnenkomen waarvan diens klassen interface IAutomobiel hebben geïmplementeerd waardoor de objecten in het bezit zijn van methode MissionStatement.
Slot
In deze post heb ik laten zien hoe we gegevens kunnen opslaan in variabelen en hoe we gegevens kunnen opslaan in een collectie. Ik heb laten zien hoe de Array en de ArrayList worden gebruikt en ik heb getoond hoe een generieke collectie (List<T>) wordt gevuld. Tevens heb ik laten zien hoe een generieke collectie (de List<T>) zich onderscheidt van een niet generieke collectie (de Array en de ArrayList).
Er zijn naast de drie eerder genoemde collecties ook andere collecties die door C# worden ondersteund en ik zal in een volgende post een aantal van die collecties ter sprake brengen.
Ten slotte heb ik laten zien hoe we een methode generiek kunnen maken waarbij we een constraint definiëren voor de soorten objecten die de methode mag hebben.
Hopelijk ben je met deze posting weer wat wijzer geworden en ik hoop je weer terug te zien in één van mijn volgende blog posts. Wil je weten wat ik nog meer over C# heb geschreven? Hit the C# button…
Voorbeeldprogramma
using System;
using System.Collections;
using System.Collections.Generic;
namespace Generieken
{
public interface IAutomobiel
{
string MissionStatement();
}
public interface IDier
{
string OverJeDier();
}
//-----------------------------------------------
class GHB: IAutomobiel
{
public string MissionStatement()
{
return
"Wij van GHB streven " +
"naar wereldwijde samenwerkingsverbanden.";
}
}
class MGIM : IAutomobiel
{
public string MissionStatement()
{
return
"Wij van MGIM streven naar " +
"imperialistische werelddominantie.";
}
}
class XPAZ : IAutomobiel
{
public string MissionStatement()
{
return
"Wij van XPAZ streven " +
"naar volksbevrijdingsautomobielen.";
}
}
class Huiskat : IDier
{
public string OverJeDier()
{
return "Je dier is een Europese Korthaar, " +
"een kleine katachtige.";
}
}
//-----------------------------------------------
class MainClass
{
public static void
AntecedentenAutoFabrikant<T>
(T AutoFabrikant)
where T : IAutomobiel
{
// Dit is een generieke methode die
// alleen objecten accepteert die interface
// IAutomobiel geïmplementeerd hebben
Console.WriteLine
("- " + AutoFabrikant.MissionStatement());
}
//-----------------------------------------------
public static void Main(string[] args)
{
/*
* ---------------------------
* Losse variabelen
* ---------------------------
*/
string AutoFabrikant1 =
"GHB -
GuanggongHunnenburgBetrieb";
string AutoFabrikant2 =
"MGIM -
Migraine Imperialist Motors
Corporation Groupe";
string AutoFabrikant3 =
"XPAZ -
Xiaonlang /
Pobeda Avtomobilny Zavod";
Console.WriteLine(
"---\r\nAutofabrikanten (losse variabelen):");
Console.WriteLine(
"{0}",AutoFabrikant1);
Console.WriteLine(
"{0}",AutoFabrikant2);
Console.WriteLine(
"{0}",AutoFabrikant3);
/*
* -----
* Array
* -----
*/
Console.WriteLine(
"\r\n---\r\n
Autofabrikanten (array):");
string[] Autofabrikanten = new string[3];
Autofabrikanten[0] =
"GHB -
GuanggongHunnenburgBetrieb";
Autofabrikanten[1] =
"MGIM -
Migraine Imperialist Motors
Corporation Groupe";
Autofabrikanten[2] =
"XPAZ - Xiaonlang /
Pobeda Avtomobilny Zavod";
for (int i = 0; i < Autofabrikanten.Length; i++)
{
Console.WriteLine("{0}", Autofabrikanten[i]);
}
/*
* ------------------------------
* Alles in een ArrayList stoppen
* ------------------------------
*/
// Alles in een List stoppen en
// dan kijken wat er in zit
int getal = 123;
string tekst = "ABC" ;
GHB ghbAutomobiel = new GHB();
ArrayList lijst = new ArrayList();
lijst.Add(getal);
lijst.Add(tekst);
lijst.Add(ghbAutomobiel);
Console.WriteLine(
"\r\n---\r\n
We stoppen alles in een ArrayList
en dit staat er in:");
for (int i = 0; i < lijst.Count;i++)
{
switch(Convert.ToString(lijst[i].GetType()))
{
case "System.Int32":
int getal_ = (int)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1} ",
lijst[i].GetType(), getal_);
break;
case "System.String":
string tekst_ = (string)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1} ",
lijst[i].GetType(), tekst_);
break;
case "Generieken.GHB":
GHB ghb_ = (GHB)lijst[i];
Console.WriteLine(
"Een element van het type {0}
met als inhoud: {1}",
lijst[i].GetType(),
ghb_.MissionStatement());
break;
}
}
/*
* ---------------------------------------
* Alles stoppen we in een
* generieke lijst List<T>
* --------------------------------------
*/
Console.WriteLine("\r\n---\r\n
We stoppen alles in een
List van het type string.");
List<string> list = new List<string>();
list.Add("GHB -
GuanggongHunnenburgBetrieb");
list.Add("MGIM -
Migraine Imperialist Motors
Corporation Groupe");
list.Add("XPAZ -
Xiaonlang /
Pobeda Avtomobilny Zavod");
// Alles uit de List halen
for (int i = 0; i < list.Count; i++)
{
Console.WriteLine(
"- Automerk '{0}'
maakt deel uit van de lijst.",list[i]);
}
/*
* ---------------------
* Generic Contstraints
* ---------------------
*/
// Instantieer objecten
GHB ghb = new GHB();
MGIM mgim = new MGIM();
XPAZ xpaz = new XPAZ();
Huiskat huiskat = new Huiskat();
// Onderzoek de objecten
Console.WriteLine("\r\n---\r\n
Wat drijft de autofabrikant?");
Console.WriteLine("Het antecedentenonderzoek
heeft alleen betrekking op autofabrikanten.");
AntecedentenAutoFabrikant(ghb);
AntecedentenAutoFabrikant(mgim);
AntecedentenAutoFabrikant(xpaz);
// AntecedentenAutoFabrikant(huiskat);
Console.WriteLine(
"\r\n---\r\n{0}\r\n",
huiskat.OverJeDier());
}
}
}
Resultaat
---
Autofabrikanten (losse variabelen):
GHB - GuanggongHunnenburgBetrieb
MGIM - Migraine Imperialist Motors Corporation Groupe
XPAZ - Xiaonlang / Pobeda Avtomobilny Zavod
---
Autofabrikanten (array):
GHB - GuanggongHunnenburgBetrieb
MGIM - Migraine Imperialist Motors Corporation Groupe
XPAZ - Xiaonlang / Pobeda Avtomobilny Zavod
---
We stoppen alles in een ArrayList en dit staat er in:
Een element van het type System.Int32 met als inhoud: 123
Een element van het type System.String met als inhoud: ABC
Een element van het type Generieken.GHB met als inhoud:
Wij van GHB streven naar wereldwijde samenwerkingsverbanden.
---
We stoppen alles in een List van het type string.
- Automerk 'GHB - GuanggongHunnenburgBetrieb'
maakt deel uit van de lijst.
- Automerk 'MGIM - Migraine Imperialist Motors
Corporation Groupe'
maakt deel uit van de lijst.
- Automerk 'XPAZ - Xiaonlang / Pobeda Avtomobilny Zavod'
maakt deel uit van de lijst.
---
Wat drijft de autofabrikant?
Het antecedentenonderzoek heeft alleen betrekking
op autofabrikanten.
- Wij van GHB streven naar
wereldwijde samenwerkingsverbanden.
- Wij van MGIM streven naar
imperialistische werelddominantie.
- Wij van XPAZ streven naar
volksbevrijdingsautomobielen.
---
Je dier is een Europese Korthaar, een kleine katachtige.
Press any key to continue...