Een enumeratie is een opsomming. In een aantal programmeertalen kan je een enumeratie als datatype gebruiken. Variabelen van dit datatype kunnen dan slechts de waarden aannemen die in de enumeratie opgegeven zijn.
Voorbeeld:
In onderstaand voorbeeld zie je een enumeratie met de naam Color die een aantal kleuren bevat. Vaak zullen de enumeraties die in een project gebruikt worden in een aparte codefile binnen het project geplaatst worden.
public enum Color
{
green,
blue,
red,
yellow,
orange,
pink
}
Eens de enumeratie binnen het project bestaat kunnen we ze als datatype bij variabelen gebruiken, zoals in onderstaande Main-functie gebeurt. Een variable met de naam c1 krijgt als datatype Color
. Merk op hoe de initialisatie van deze variabele gebeurt: een waarde uit de enumeratie wordt aangesproken door gebruik te maken van de naam van de enumeratie d.m.v. een punt gekoppeld aan een waarde van de enumeratie. In onderstaand voorbeeld wordt c1 dus geïnitialiseerd op de waarde green.
static void Main(string[] args)
{
Color c1 = Color.green; // Let op de syntax van initialisatie:
// naamEnumeratie.waardeUitEnumeratie
Console.WriteLine("The first color is {0}.", c1);
}
Let op! Als we een enumeratie als datatype voor een variabele gebruiken, dan kan deze variabele enkel nog waarden uit de enumeratie krijgen. Het volgende kan dus niet meer:
static void Main(string[] args)
{
Color c1 = Color.green;
Color c2 = "cyan"; // FOUT! Een variabele met
// een enumeratie als type
// kan enkel waarden uit de
// enumeratie krijgen.
Console.WriteLine("The first color is {0}.", c1);
Console.WriteLine("The second color is {0}.", c2);
}
Om het nut van enumeraties aan te tonen, volgen hieronder enkele voorbeelden van problemen die zich kunnen voordoen en hoe ze opgelost worden door het gebruik van een enumeratie.
Voorbeeld 1
In het onderstaande voorbeeld staat een class Day
waarmee je een dag in een maand kan onthouden, samen met welke dag in de week dat is. (bv. de 21ste dag van de maand en de 4de dag van de week)
We vinden in deze class volgende zaken terug:
Een constructor die een Day-object op de meegegeven argumenten initialiseert.
public class Day {
public int DayInWeek { get; private set;}
public int DayInMonth { get; private set; }
//De utility IsWeekend test of de dag in het weekend valt.
public bool IsWeekend { get => dayInWeek >= 5; }
//Constructor
public Day(int inMonth, int inWeek) {
DayInMonth = inMonth;
DayInWeek = inWeek;
}
//De functie WeekDayToString() zet de integer dayInWeek om naar de bijhorende
//dag in stringformaat.
public string WeekdayToString() {
if(DayInWeek == 0) return "Monday";
if(DayInWeek == 1) return "Tuesday";
//... en zo verder tot Sunday.
}
}
public void Main() {
Day day1 = new Day(21, 0); // maandag de 21ste
Day day2 = new Day(5, 6); // zondag de 5de
Day day3 = new Day(13, 4); // vrijdag de 13de
}
Wat is het probleem met deze manier van werken?
Deze werkwijze lijkt logisch, maar het kan snel fout lopen. Je gaat er bij deze werkwijze van uit dat iedereen zich aan de afspraak houdt en als eerste dag maandag zal kiezen en dan een 0 ingeeft voor DayInWeek. Je kan je daar makkelijk in vergissen, en bijvoorbeeld 1 ingeven voor maandag omdat je even vergeet dat programmeurs vanaf 0 beginnen tellen. Of je kan de week misschien op zondag laten beginnen i.p.v. op maandag en dan zal je 0 ingeven voor zondag. Of misschien is er iemand gewoon niet op de hoogte van de afspraak en is het bijgevolg niet gekend dat maandag de waarde 0 moet krijgen. Het is dus duidelijk dat deze werkwijze snel tot foutieve ingave kan leiden en dat dit dus geen goede oplossing is.
Voorbeeld 2
In voorbeeld 1 zagen we reeds dat er mogelijke foute waarden ingegeven kunnen worden omdat mensen niet zo goed zijn met cijfers en dus vroeg of laat een fout maken. Met woorden zien we veel duidelijker wat de bedoeling is. Een mogelijke oplossing voor het probleem van voorbeeld 1 ligt dus voor de hand: gebruik een string in plaats van een nummer voor de property DayInWeek.
In onderstaand voorbeeld is dit toegepast. Omdat DayInWeek nu een string-variabele is, zal ook de utility IsWeekend anders geïnitialiseerd moeten worden. Je kan deze werkwijze terugvinden in de commentaarlijnen bij de utility zelf.
public class Day {
public string DayInWeek { get; private set;} //Nu een string in plaats van een integer.
public int DayInMonth { get; private set; }
//De utility IsWeekend test of de dag in het weekend valt. Hierbij wordt gebruik gemaakt van
//de functie Equals die nagaat of een string-variabele gelijk is aan de waarde tussen
//de haakjes. Er wordt bij het initialiseren van IsWeekend dus getest of dayInWeek gelijk is
//aan "Saturday" of aan "Sunday".
public bool IsWeekend { get => DayInWeek.Equals("Saturday") || DayInWeek.Equals("Sunday"); }
//Constructor
public Day(int inMonth, string inWeek) {
DayInMonth = inMonth;
DayInWeek = inWeek;
}
}
De functie WeekdayToString
wordt nu zelfs overbodig, want de naam van de dag is nu beschikbaar via de string DayInWeek.
Zoals je in het voorbeeld kan zien, bepalen we of een dag in het weekend valt door de string-variabele te vergelijken met de namen van de dagen in het weekend. En daar zit een nieuw probleem.
saturday
, zonder hoofdletter. Aangezien de functie Equals in het voorbeeld hoofdlettergevoelig werkt, zal saturday
dus niet aanzien worden als een dag in het weekend. Fout, dus!Problemen, zoals beschreven in bovenstaande voorbeelden, kunnen we vermijden door gebruik te maken van een enumeratie.
Afzonderlijk van de class Day
declareren we een enum WeekDay
. Die bevat een oplijsting van alle dagen in de week. De computer ziet deze lijst als een lijst van nummers, waarin Monday
eigenlijk gelijk staat aan 0, Tuesday
aan 1, enzovoort. We moeten dit nu zelf niet onthouden.
Deze enumeratie ziet er als volgt uit:
// Declaratie van de enumeratie
public enum Weekday {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
};
We kunnen deze enumeratie nu als datatype gebruiken. In de class Day gebruiken we de enumeratie als datatype voor de property DayInWeek.
Ook voor de utility IsWeekend heeft het gebruik van de enumeratie gevolgen, zoals je in de commentaar bij de utility kan lezen.
public class Day {
public Weekday DayInWeek { get; private set;} //We gebruiken hier de naam van de
//enumeratie als gegevenstype.
public int DayInMonth { get; private set; }
// Constructor
public Day(int inMonth, Weekday inWeek) {
DayInMonth = inMonth;
DayInWeek = inWeek;
}
// De utility IsWeekend test of de dag in het weekend valt.
// Aangezien de computer de enumeratie als integers bekijkt, kunnen we gebruik
// maken van de test dayInWeek >= WeekDay.Friday.
// De computer ziet het item WeekDay.Friday als 4.
// Aangezien alle weekdagen voor Friday in de enumeratie opgesomd zijn, hebben
// de weekdagen (maandag tot en met vrijdag) allemaal een waarde kleiner dan 4
// (Monday = 0, enz.).
// Er wordt dus getest dat de dag na vrijdag in de enumeratie
// staat. En, ook al gebruiken we woorden
// in de enumeratie, de computer zal met nummers werken -> sneller!
public bool IsWeekend { get => DayInWeek > WeekDay.Friday; }
}
We kunnen de enum op de volgende manier gebruiken bij het meegeven van de argumenten voor de constructor.
public void Main() {
Day day1 = new Day(21, Weekday.Monday); // maandag de 21ste
Day day2 = new Day( 5, Weekday.Sunday); // zondag de 5de
Day day3 = new Day(13, Weekday.Friday); // vrijdag de 13de
}
Niet alleen kunnen we ons nu niet van nummer van de de dag vergissen (zoals in voorbeeld 1), spellingsfouten worden dadelijk opgemerkt door de compiler. Bovendien zal Visual Studio ons na het typen van Weekday.
dadelijk tonen wat de mogelijke opties uit de enumeratie zijn (de verschillende dagen, dus). En de computer zelf zal nummers gebruiken voor deze dagen. Vandaar dat we in de utility IsWeekend
de dag van de week met vrijdag kunnen vergelijken, zoals je in de commentaar kan terugvinden.
Het is dus duidelijk dat de enumeratie hier een groot aantal voordelen heeft.
En er is nog een voordeel: elke enum bevat automatisch een functie ToString()
die de tekstuele representatie van de waarde geeft. De dag van de week als string tonen gaat dus vanzelf:
public void Main() {
Day day1 = new Day(21, Weekday.Monday); // maandag de 21ste
// Mogelijkheid 1:
// gebruik van de ToString() functie die automatisch voorzien is bij de enumeratie.
string result = day1.DayInWeek.ToString();
// Mogelijkheid 2:
// console.WriteLine maakt deze conversie naar string zelfs automatisch, zonder
// dat de ToString() functie toegepast wordt op DayInWeek.
Console.WriteLine("Day " + day1.DayInMonth + " is a " + day1.DayInWeek);
}
Je kan enums ook eenvoudig in een loop gebruiken. Uiteindelijk is een enum niet meer dan een reeks getallen met een naam. De loop variabele kan dus ook een enum zijn. Wat niet zo eenvoudig is, is het bepalen van de grootste waarde. Dat kan je oplossen door die expliciet in je enum te declareren.
In het onderstaande voorbeeld gebruiken we hiervoor de waarde End. Het is op die manier duidelijk welke waarde we als bovengrens voor de for
moeten gebruiken.:
// Declaratie enumeratie
enum Values { one, two, three, End }
// Doorlopen van de enum d.m.v. for
for (Values i = 0; i < Values.End; i++) {
Console.WriteLine(i);
}
Opmerking: Een enum kan eveneens doorlopen worden met een foreach
-lus, maar aangezien deze syntax minder eenvoudig is, beperken we ons in deze cursus tot de for-lus.
Maak de oefeningenreeks Enumeraties die je op Smartschool vindt.
Soms wil je een willekeurige enum waarde toekennen aan een variabele. Hiervoor kan je gebruik maken van de random number generator. Je gebruikt deze als volgt:
var generator = new Random();
var value = generator.Next(100);
De 100 is de bovengrens die je meegeeft, het nummer dat zal gegenereerd worden ligt in dit voorbeeld tussen 0 en 100.Je zou dus het onderstaande kunnen proberen om een random Enum waarde te verkrijgen:
var generator = new Random(); //Initialisatie random number generator
var value = generator.Next(Values.End); // <-- Compiler geeft een fout bij het genereren
//van de random waarde.
Ook al is een enum eigenlijk een getal, de functie Next wil echt een integer zien. Dit los je op door de enum hier te converteren naar een integer:
var generator = new Random();
var value = generator.Next((int)Values.End); // <-- Compiler geeft GEEN fout
In bovenstaand voorbeeld plaatsen we de random waarde die we genereren in de variabele value, waar we als type var
meegeven. Soms zal het echter nodig zijn om de random enum waarde in een reeds gedeclareerde waarde van een bepaald type te plaatsen. In dat geval moet er opnieuw een conversie gebeuren naar het juiste type. (Zie onderstaand voorbeeld)
In het onderstaande voorbeeld worden objecten van de class Circle
gemaakt. Elk object heeft een straal en een kleur. De kleur wordt via een random waarde uit de enum Color gekozen.
We voorzien in dit voorbeeld 3 bestanden:
Belangrijke opmerking i.v.m. het starten van de Random
generator: het is belangrijk om dit slechts éénmalig in je programma te voorzien. De Random
generator werkt intern als volgt: bij het opstarten bepaalt hij een lijst van random waarden a.h.v. een seed
. Standaard is deze seed
de systeemtijd.
Eens de Random
generator gestart is, kunnen we door Next
op de generator toe te passen telkens de volgende waarde uit de interne lijst opvragen. Zo krijgen we dus random waarden. Indien we echter telkens opnieuw de generator zouden starten op heel korte tijd en dan telkens een random waarde opvragen, dan kan dit als gevolg hebben dat we steeds dezelfde waarde als resultaat krijgen. De generator zal in die korte tijd immers vaak intern steeds dezelfde lijst genereren a.h.v. de seed en uit deze lijst vragen we dan steeds de eerste waarde op. Gevolg: de kans is groot dat we steeds dezelfde waarde krijgen.
Door de Random
generator slechts eenmalig op te starten, vermijden we dit probleem en wordt via Next
de lijst met random waarden steeds verder afgegaan. Gevolg: we krijgen nu wel verschillende resultaten.
Enum.cs
public enum Color
{
Red,
Green,
Blue,
Orange,
Yellow,
Purple,
Lime,
End
};
Circle.cs
public class Circle
{
public Color Color { get; private set;}
public int Radius { get; private set; }
// Constructor: het argument color wordt in
// de Main-functie a.h.v. Random geïnitialiseerd
// op een random waarde uit de enum Color.
public Circle(int radius, Color color)
{
Radius = radius;
Color = color;
}
public override string ToString()
{
return "The circle has a radius of " + Radius + " and its color is " + Color + ".";
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
// Eenmalig starten van de Random generator.
var generator = new Random();
// Declaratie variabele voor opslaan Random color.
Color color;
// Circle c1 wordt gemaakt.
// Eerst krijgt de variabele color een waarde via
// de Random generator.
// De variabele wordt dan gebruikt als argument voor
// de constructor.
color = (Color)generator.Next((int)Color.End);
Circle c1=new Circle(1,color);
// Circle c2 wordt gemaakt.
// De variabele color krijgt eerst een andere random
// waarde.
color = (Color)generator.Next((int)Color.End);
Circle c2 = new Circle(3, color);
// Circle c3 wordt gemaakt.
// De variabele color krijgt eerst een andere random
// waarde.
color = (Color)generator.Next((int)Color.End);
Circle c3 = new Circle(8, color);
// Uitvoer van de drie Circle objecten.
Console.WriteLine(c1);
Console.WriteLine(c2);
Console.WriteLine(c3);
Console.ReadKey();
}
}
In deze oefenreeks gebruiken we nog iets nieuws: Overrides. Je vindt de uitleg hierover in het hoofdstuk Details onder het puntje Override. Lees eerst dit onderdeel van het hoofdstuk Details voordat je de oefeningen maakt.
Open het project oefening-enum-1 en maak de oefeningenreeks