n00b pro

06. collecties

Dit hoofdstuk is onderdeel van de cursus C#. Andere cursussen in dezelfde reeks: HTML, CSS, Javascript, Ontwikkelomgeving.

Op Youtube

Deze video maakt deel uit van de IIP playlist van de Youtube channel van Rogier van der Linde.

Let op: deze video's zijn al een paar jaar oud, dus hier en daar kunnen kleine afwijkingen voorkomen met deze cursus, die wel altijd up-to-date is.

https://www.youtube.com/watch?v=mi4zT5SC7Og

Soorten collecties

Een collectie is een verzameling gegevens van hetzelfde type met één naam. Enkele voorbeelden:

Belangrijk: je kan geen verschillende types mixen (bv. getallen en teksten) in één collectie!

⚠️ alle elementen van een collectie hebben hetzelfde type

Alle klassen (behalve arrays) zitten in de System.Collections.Generic namespace. Om ze te kunnen gebruiken, moet je dus een using toevoegen:

using System;
using System.Collections.Generic;
...
Klasse Beschrijving Te vergelijken met
Array vaste lengte; wrapper class rond native C# arrays vaste stoelen in een wachtzaal
List<T> variabele lengte; toevoegen of verwijderen items kan overal platenkast
Stack<T> variabele lengte, LIFO (last in, first out): toevoegen en verwijderen aan einde stapel pannenkoeken
Queue<T> variabele lengte; FIFO (first in, first out): toevoegen aan einde, verwijderen aan begin wachtrij aan de kassa
Dictionary<TKey, Tvalue> variabele lengte; key-value paren, met unieke keys lockers met unieke naambordjes
HashSet<T> variabele lengte; lijst unieke waarden
SortedDictionary<TKey, Tvalue> variabele lengte; als Dictionary, maar dan automatisch gesorteerd op TKey
in volgorde: Array, List, Stack, Queue en Dictionary

In dit hoofdstuk focussen we op arrays en lijsten. Maar je moet de eigenschappen van de andere collecties wel kennen voor de theorie.

Arrays

Arrays maken deel uit van een breder concept, collecties genoemd. .NET biedt verschillende soorten collecties aan als List, Stack, Queue enz... — zie dit overzicht. C# als taal kent maar één soort collectie, de array.

Een array is een variabele die verwijst naar een genummerde reeks waarden, startend bij index 0:

array

Arrays hebben volgende fundamentele eigenschappen:

Zie ook deze vergelijking met lijsten

Declaratie en initialisatie

Voorbeelden:

int[] cijfers = new int[6]; // array van 6 ints
string[] namen = new string[20]; // array van 20 strings
bool[] weekdagenOpen = new bool[7]; // array van 7 bools

Je kunt meteen beginwaarden opgeven tussen accolades, gescheiden met komma's:

int[] getallen = { 5, 8, 3, 12, 7 };
double[] kommagetallen = { 1.5, 10.2, 19.85, 3.1415 };
char[] letters = { 'a', 'b', 'c', 'd' };
string[] fruit = { "appel", "banaan", "citroen" };

Waarden aanpassen

Je kan geen elementen toevoegen of verwijderen in een array; het aantal elementen is vast. Je kan wel de waarde van een element veranderen:

int[] getallen = new int[6]; // array van 6 ints, van 0 tot 5
getallen[0] = -2;  // zet getal -2 op eerste plaats
getallen[2] = 7;   // zet getal 7 op derde plaats
getallen[4] = 1;  // zet getal 1 op vijfde plaats
getallen[5] = 11;  // zet getal 11 op zesde (laatste) plaats

string[] namenlijst = new string[5]; // array van 5 strings, van 0 tot 4
namenlijst[0] = "Jef";
namenlijst[1] = "Yasmina";
namenlijst[4] = "Long";

⚠️ plaatsen worden genummerd vanaf 0
⚠️ type int heeft standaardwaarde 0
⚠️ type string heeft standaardwaarde null

Gebruiken

één element opvragen

Je kunt een waarde uitlezen uit een array op basis van de index van dit element (eerste = index 0):

string[] kleuren = { "blauw", "groen", "rood", "geel" };
string eerste = kleuren[0]; // blauw; eerste element heeft index 0
string tweede = kleuren[1]; // groen; tweede element heeft index 1
string laatste = kleuren[kleuren.Length - 1]; // geel; manier om laatste element op te vragen

lengte opvragen

Met de lengte wordt het aantal beschikbare plaatsen bedoeld, niet het aantal plaatsen dat effectief een waarde gekregen heeft. Gebruik de Length property:

int[] getallen = new int[7]; // array met 7 plaatsen → lengte is 7
getallen[0] = 10;
getallen[2] = 5;
getallen[5] = 7;
int lengte = getallen.Length;
Console.WriteLine($"De lengte van de array is: {lengte}");
De lengte van de array is: 7
resultaat in de console

inlezen met for-loop

Codevoorbeeld voor het inlezen van een array:

string[] namen = new string[4];
for (int i = 0; i < namen.Length; i++) {
   Console.Write($"Geef naam {i + 1} van {namen.Length}: ");
   namen[i] = Console.ReadLine();
}
Console.WriteLine("Bedankt!");
Geef naam 1 van 4: Kawtar
Geef naam 1 van 4: Arne
Geef naam 1 van 4: Dmitri
Geef naam 1 van 4: Yolanthe
Bedankt!
resultaat in de console

overlopen met for-loop

Gebruik de for-lus met de Length property als je de index van elk element in de array nodig hebt:

string[] namen = { "Zakaria", "Nelly", "Killian", "Chun" };
for (int i = 0; i < namen.Length; i++)
{
   string naam = namen[i]; // element op index i
   Console.WriteLine($"{i + 1}. {naam}");
}
1. Zakaria
2. Nelly
3. Killian
4. Chun
resultaat in de console

overlopen met foreach-loop

Gebruik foreach als je de index van de elementen niet nodig hebt:

int[] numbers = { 2, 5, 9, 3, 4, 6, 7 };

foreach (int num in numbers)
{
   Console.WriteLine(num);
}
2
5
9
3
4
6
7
resultaat in de console

Properties en methodes

De .NET Array class biedt volgende properties en methodes:

Property Omschrijving
Length het aantal beschikbare plaatsen in de array
Methode Omschrijving
Clear() reset alle elementen naar de defaultwaarde (0 voor ints, null voor strings enz...)
Contains() controleert of de array een gegeven waarde bevat
Array.IndexOf() zoekt de positie (index) van een gegeven waarde; -1 indien niet gevonden
Array.Reverse() keert de volgorde van de elementen om
Array.Sort() sorteert een gegeven array
String.Split() splitst een string in een array
String.Join() voegt een array samen tot een string

voorbeeld: nagaan of waarde voorkomt in een array met Contains()

Gebruik Contains() (returnwaarde true of false):

string[] fruit = { "appel", "banaan", "citroen" };
bool gevonden = fruit.Contains("citroen");
if (gevonden)
{
   Console.WriteLine("De array bevat het woord \"citroen\"");
}

voorbeeld: positie van een waarde in een array met IndexOf()

Gebruik IndexOf() om de positie te vinden (-1 indien niet gevonden):

string[] fruit = { "appel", "banaan", "citroen" };
int pos = Array.IndexOf(fruit, "citroen");
if (pos > -1)
{
   Console.WriteLine($"Het woord \"citroen\" is gevonden op positie {pos}");
}

voorbeeld: array sorteren met Sort()

Gebruik Array.Sort() om een array oplopend te sorteren:

int[] getallen = { 7, 3, 9, 2, 6 };
Array.Sort(getallen); // sorteer getallen van klein naar groot

foreach (int getal in getallen)
{
   Console.WriteLine(getal);
}
   
2
3
6
7
9
resultaat in de console

Gebruik in combinatie met Array.Reverse() om een array aflopend te sorteren:

int[] getallen = { 7, 3, 9, 2, 6 };
Array.Sort(getallen); // sorteer getallen van klein naar groot
Array.Reverse(getallen); // keer volgorde om

foreach (int getal in getallen)
{
   Console.WriteLine(getal);
}
9
7
6
3
2
resultaat in de console

Samenvoegen en splitsen

De .NET String class biedt volgende methodes:

Methode Omschrijving
split() splitst een string in een array
join() voegt een array samen tot een string

voorbeeld: array samenvoegen tot een string met string.Join()

Gebruik string.Join():

int[] getallen = { 5, 7, 12, 8, 3 }; // → int[] array
string samengevoegd = string.Join(", ", getallen); // "5, 7, 12, 8, 3" → één string
Console.WriteLine(samengevoegd);
5, 7, 12, 8, 3
Join() voorbeeld met array

voorbeeld: string rond spaties splitsen naar een array met Split()

string zin = "dit is een zin met meerdere woorden";
string[] woorden = zin.Split(' '); // verdeel zin in woorden
foreach (string woord in woorden)
{
   Console.WriteLine(woord);
}
dit
is
een
zin
met
meerdere
woorden
resultaat in de console

voorbeeld: string rond karakters splitsen naar een array met Split()

Je kan als parameter van Split() een array van karakters opgeven waarrond gesplitst moet worden:

string txt = "0477/234.56.78"; // → één string
string[] parts2 = txt.Split(new char[] { '/', '.' }); // { "0477", "234", "56", "78" } → string[] array

LINQ extensie methodes

.NET LINQ biedt ook een hoop extensie methodes die je kan gebruiken,
zie 02. NET classes - LINQ extensie methodes voor collecties

Lijsten

Een lijst is een collectie van elementen van één bepaald type (string, int, double, ...). Het verschil met een gewone array is dat het aantal elementen onbepaald is.

Declaratie en initialisatie

Algemene syntax:

List<type> naamLijst = new List<type>(); // type is het type van de elementen in de collectie (int, double, string, ...)

Je mag het ook inkorten tot:

List<type> naamLijst = new(); // korte notatie

Code voorbeelden:

List<int> getallen = new(); // een lijst van int's
List<bool> myList = new(); // een lijst van bool's
List<string> namen = new(); // een lijst van string's

Je kan meteen een aantal beginwaarden meegeven:

List<int> getallen = new() { 5, 12, 7, 9, 3 };  // de lijst getallen bevat 5 elementen: 5, 12, 7, 9 en 3

Waarden aanpassen

Elementen wijzigen doe je net zoals bij arrays eenvoudig met een toewijzing (=). Code voorbeeld:

List<int> getallen = new() { 4, 8, 3 }; // lijst is 4, 8, 3
getallen[1] = 5; // tweede element wijzigen naar 5, lijst is nu: 4, 5, 3

Elementen toevoegen of verwijderen

Gebruik hiervoor de methodes Add(), Remove(), RemoveAt() of Clear().

Voorbeeld voor toevoegen:

List<string> fruit = new(); // declaratie (nieuwe lijst van string's)
fruit.Add("appel"); // lijst bevat 1 element
fruit.Add("banaan"); // lijst bevat 2 elementen
fruit.Add("citroen"); // lijst bevat 3 elementen

Voorbeeld voor verwijderen (enkel de eerst gevonden waarde zal verwijderd worden):

List<int> getallen = new() {2, 5, 7, 5 }; // inhoud van de lijst: 2, 5, 7, 5
getallen.Remove(5); // verwijder element met waarde 5 (enkel eerste); inhoud van de lijst: 2, 7, 5

Je kan een element ook verwijderen op basis van de index met RemoveAt():

List<int> getallen = new() {2, 5, 7, 5 }; // inhoud van de lijst: 2, 5, 7, 5
getallen.RemoveAt(2); // verwijder het derde element; de lijst is nu 2, 5, 5

Met de methode Clear(), kan je alle elementen uit de lijst verwijderen:

List<int> getallen = new() { 2, 5, 7, 5 }; // inhoud van de lijst: 2, 5, 7, 5
getallen.Clear(); // de lijst is nu leeg

Gebruiken

één element opvragen

Je kunt een element uit een lijst opvragen op basis van de index van het element, startend vanaf 0. Code voorbeeld:

List<string> kleuren = new() { "rood", "groen", "blauw" };
string eerste = kleuren[0]; // eerste element heeft index 0
Console.WriteLine($"Het eerste element is: {eerste}");
Console.WriteLine($"Het tweede element is: {kleuren[1]}");
string laatste = kleuren[kleuren.Count - 1]; // manier om laatste element op te vragen
Console.WriteLine($"Het laatste element is: {laatste}");
Het eerste element is: rood
Het tweede element is: groen
Het laatste element is: blauw
resultaat in de console

aantal elementen opvragen

Met de property Count kan je het aantal elementen dat op dat moment in de List zit opvragen:

List<int> getallen = new() { 2, 5, 8 };
Console.WriteLine(getallen.Count); // Count property
3
resultaat in de console

⚠️ voor arrays gebruik je Length, bij lijsten gebruik je Count

inlezen met do-while

Codevoorbeeld voor het inlezen van een lijst:

List<string> namen = new();
string nieuweNaam;				
do 
{
   Console.Write($"Geef de volgende naam (laat leeg om te stoppen): ");
   nieuweNaam = Console.ReadLine();
   if (!string.IsNullOrWhiteSpace(nieuweNaam)) namen.Add(nieuweNaam);
} 
while (!string.IsNullOrWhiteSpace(nieuweNaam));
Console.WriteLine("Bedankt!");
Geef de volgende naam (laat leeg om te stoppen): Kawtar
Geef de volgende naam (laat leeg om te stoppen): Arne
Geef de volgende naam (laat leeg om te stoppen): Dmitri
Geef de volgende naam (laat leeg om te stoppen): 
Bedankt!
resultaat in de console

overlopen met for-loop

Indien je de index (positie) van het element in de lijst nodig hebt.

List<string> namen = new() { "Zakaria", "Nelly", "Killian", "Chun" };

for (int i = 0; i < namen.Count; i++)
{
   string naam = namen[i];
   Console.WriteLine($"{i + 1}. {naam}");
}
1. Zakaria
2. Nelly
3. Killian
4. Chun
resultaat in de console

overlopen met foreach-loop

Indien je de index (positie) van het element niet nodig hebt kan je foreach gebruiken:

List<string> namen = new() { "Zakaria", "Nelly", "Killian", "Chun" };

foreach (string naam in namen)
{
   Console.WriteLine(naam);
}
Zakaria
Nelly
Killian
Chun
resultaat in de console

Properties en methodes

Een List class biedt volgende properties en methodes:

Property Omschrijving
Count het huidig aantal elementen in de lijst
Methode Omschrijving
Add() voeg een waarde toe aan het einde
Contains() controleert of de lijst een gegeven waarde bevat
IndexOf() zoekt de positie (index) van een gegeven waarde; -1 indien niet gevonden
Insert() voeg een waarde in op een gegeven positie
Remove() verwijder een element
Reverse() keert de volgorde van de elementen om
Sort() sorteert een gegeven array

voorbeeld: nagaan of element voorkomt in lijst met Contains()

Met de methode Contains() kan je nagaan of een element voorkomt in een lijst. De methode stuurt true terug indien de meegegeven waarde zich in de lijst bevindt:

List<string> fruit = new() { "appel", "banaan", "citroen" };

bool gevonden = fruit.Contains("citroen"); // ga na of het woord "citroen" in de lijst voorkomt
if (gevonden)
{
   Console.WriteLine("Het element komt wel in de lijst voor");
}
else
{
   Console.WriteLine("Het element komt niet in de lijst voor.");
}
Het element komt in de lijst voor
resultaat in de console

voorbeeld: lijst sorteren met Sort()

De methode Sort() sorteert de elementen in een lijst in oplopende volgorde (van klein naar groot, of voor tekst alfabetische van 'a' tot 'z'):

List<int> getallen = new() { 7, 3, 9, 5 };

getallen.Sort(); // sorteer de elementen in de lijst (volgorde van elementen in lijst zélf verandert!)
foreach (int getal in getallen)
{
   Console.WriteLine(getal);
}
3
5
7
9
resultaat in de console

Samenvoegen en splitsen

elementen in een lijst samenvoegen tot één string met string.Join()

Gebruik string.Join() om elementen van een list samen te voegen tot één string. Het eerste argument is de "lijm" tussen elementen:

List<int> getallen = new() { 7, 3, 9, 5 };

Console.WriteLine(string.Join(" - ", getallen));
7 - 3 - 9 - 5
resultaat in de console

Voorbeelden voor andere collecties

Stack<T>

Een Stack<T> is als een List<T> maar dan zonder methodes Add() of Remove(), en in plaats daarvan Push() (plaats element op de stapel) en Pop() (pak een element van de stapel):

Stack<int> stack = new Stack<int>(new int[] { 4, 8, 11 });
int val1 = stack.Pop(); // 11
int val2 = stack.Pop(); // 8
stack.Push(23);
int val3 = stack.Pop(); // 23
int val4 = stack.Pop(); // 4
int val5 = stack.Pop(); // FOUT! stack is leeg

Queue<T>

Een Queue<T> is als een List<T> maar dan zonder methodes Add() of Remove(), en in plaats daarvan Enqueue() (plaats element achteraan de wachtrij) en Dequeue() (pak een element vooraan de wachtrij):

Queue<int> queue = new Queue<int>(new int[] { 4, 8, 11 });
int val1 = queue.Dequeue(); // 4
int val2 = queue.Dequeue(); // 8
queue.Enqueue(23);
int val3 = queue.Dequeue(); // 11
int val4 = queue.Dequeue(); // 23
int val5 = queue.Dequeue(); // FOUT! queue is leeg

Dictionary<TKey,Tvalue>

De Dictionary wordt gebruikt voor key-value paren, waarbij de keys uniek moeten zijn. De methodes en properties lijken sterk op List<T>. Voorbeeldfragment met typische methodes en properties:

Dictionary<string, int> dict1 = new Dictionary<string, int> { { "aap", 6 }, { "noot", 3 }, { "mies", -7 } };
dict1["tuin"] = 11; // voeg een nieuw key-value paar toe
dict1.Remove("mies"); // verwijder een key-value paar
dict1["noot"] = 4; // verander de waarde van ene key-value paar
Console.WriteLine($"Dictionary 1 bevat key \"noot\": {(dict1.Keys.Contains("noot") ? "ja" : "nee")}"); // controleer of key bestaat
Console.WriteLine($"Dictionary 1 bevat waarde 3: {(dict1.Values.Contains(3) ? "ja" : "nee")}"); // controleer of value bestaat
foreach (KeyValuePair<string, int> item in dict1)
{
   Console.WriteLine($"Key: {item.Key}, Value: {item.Value}");
}
Dictionary 1 bevat key "noot": ja
Dictionary 1 bevat waarde 3: nee
Key: aap, Value: 6
Key: noot, Value: 4
Key: tuin, Value: 11
voorbeeldfragment Dictionary<string,int>

HashSet<T>

Een HashSet<T> is hetzelfde als een List<T>, behalve dat de lijst uniek gehouden wordt:

HashSet<string> set = new HashSet<string>(new string[] { "banaan", "appel" });
set.Add("banaan");
set.Add("citroen");
set.Add("banaan");
set.Add("banaan");
set.Add("appel");
set.Add("appel");
set.Add("peer");
Console.WriteLine(string.Join(", ", set));
banaan, appel, citroen, peer
voorbeeldfragment HashSet<T>

Lijst en array vergeleken

Er zijn nogal wat syntactische verschillen. Een overzichtstabel:

Array List
lengte vast flexibel
declaratie int[] cijfers = new int[3]; List<int> cijfers = new();
initialisatie int[] cijfers = { 4, 1, 11 }; List<int> cijfers = new() { 4, 1, 11 };
elementen toevoegen/verwijderen niet mogelijk Add(), Remove(), RemoveAt(), Clear()
elementen lezen/schrijven cijfers[i] cijfers[i]
aantal elementen cijfers.Length cijfers.Count
sorteren Array.Sort(cijfers) cijfers.Sort()
bevat waarde cijfers.Contains(4) cijfers.Contains(4)
string splitten naar... string txt = "aap noot mies";
string[] woorden = txt.Split(' ');
string txt = "aap noot mies";
string[] woorden = txt.Split(' ').ToList();
samenvoegen string.Join(", ", cijfers) string.Join(", ", cijfers)

Vergelijkend voorbeeld met een aantal typische methodes:

string tekst = "aap noot mies";
string[] woorden = tekst.Split(' ');
Console.WriteLine($"deze tekst bevat {woorden.Length} woorden");
if (woorden.Contains("aap")) Console.WriteLine("de woorden bevatten \"aap\"");
Array.Sort(woorden);
Console.WriteLine($"de woorden, alfabetisch: {string.Join(" - ", woorden)}");
deze tekst bevat 3 woorden
de woorden bevatten "aap"
de woorden, alfabetisch: aap - mies - noot
resultaat in de console
List<int> cijfers = new();
cijfers.Add(7);
cijfers.Add(1);
cijfers.Add(-3);
Console.WriteLine($"deze lijst bevat {cijfers.Count} getallen");
if (cijfers.Contains(1)) Console.WriteLine("de lijst bevat de waarde 1");
cijfers.Sort();
Console.WriteLine($"cijfers (van laag naar hoog): {string.Join(", ", cijfers)}");
deze lijst bevat 3 getallen
de lijst bevat de waarde 1
cijfers (van laag naar hoog): -3, 1, 7
resultaat in de console

Lijsten zijn zeker makkelijker in gebruik en flexibeler dan arrays. Arrays nemen dan weer een vaste geheugenruimte in, terwijl voor lijsten bij elke toevoeging nieuw geheugen moet gezocht worden, waardoor ze een stuk trager zijn. Dus:

LINQ extensie methodes

Linq biedt een aantal extensie methodes met de lambda expressie syntax voor collecties. Een greep uit het aanbod:

Methode Omschrijving Returntype
All() controleer of alle items aan een voorwaarde voldoen bool
Any() controleer of minstens één item aan een voorwaarde voldoet bool
Average() bepaal het gemiddelde van de items double
Count() tel het aantal items int
Distinct() verwijder alle dubbels IEnumerable
First() geef het eerste item; fout indien niet gevonden object
FirstOrDefault() geef het eerste item; standaardwaarde (null/0/...) indien niet gevonden object
Last() geef het laatste item; fout indien niet gevonden object
LastOrDefault() geef het laatste item; standaardwaarde (null/0/...) indien niet gevonden object
OrderBy() sorteer items op basis van een item waarde IEnumerable
Select() selecteer van elk item een waarde IEnumerable
Sum() bepaal de som van de items int/double
Take() neem de eerste n items IEnumerable
Where() filter items op basis van een voorwaarde IEnumerable

Voorbeelden met arrays:

string[] fruits = { "apple", "pear", "grape", "banana", "banana", "jackfruit", "banana" };
string[] fruitsLength5 = fruits.Where(f => f.Length == 5).ToArray(); // { "apple", "grape" }
int[] fruitLengths = fruits.Select(f => f.Length).ToArray(); // { 5, 4, 5, 6, 9 }
bool containsBanana = fruits.Any(f => f == "banana"); // true
int numBananas = fruits.Count(f => f == "banana"); // 3
string firstLength7 = fruits.FirstOrDefault(f => f.Length == 7); // null
string[] fruitsUnique = fruits.Distinct().ToArray(); // { "apple", "pear", "grape", "banana", "jackfruit" }
string[] fruitsFirst3 = fruits.Take(3).ToArray(); // { "apple", "pear", "grape" }
string[] fruitsByLength = fruits.Distinct().OrderBy(f => f.Length).ToArray(); // { "pear", "apple", "grape", "banana", "jackfruit" }

int[] numbers = { 3, 8, 1, 5, 10 };
double avg = numbers.Average(); // 5.4
int sum = numbers.Sum(); // 27
int lowest = numbers.Min() // 1
int first = numbers.First() // 3
int last = numbers.Last() // 10