This article is currently in the process of being translated into Dutch (~99% done).
Method parameters
In het vorige artikel spraken we over methoden en kregen we een korte inleiding in het concept van methode/functieparameters. In dit artikel gaan we dieper in op dit onderwerp in al zijn variaties. Zoals gezegd kunnen methoden werken zonder parameters, maar meestal hebben ze één of meer parameters die de methode helpen bij het uitvoeren van zijn taak.
In het vorige artikel zagen we al een heel eenvoudig gebruiksscenario voor parameters: Onze AddNumbers() methode, die twee getallen als parameter neemt en de som van deze twee getallen teruggeeft:
public int AddNumbers(int number1, int number2)
{
return number1 + number2;
}
Dit toont aan wat een slim concept methods is, want met methods kun je de functionaliteit inkapselen in de methode, maar het resultaat beïnvloeden wanneer je deze methode aanroept via de parameters:
AddNumbers(2, 3);
Result: 5
AddNumbers(38, 4);
Result: 42
Dit is het basistype van de parameters, maar laten we het eens hebben over alle verschillende modifiers en opties die je kunt gebruiken om het gedrag van de parameters te veranderen.
Merk op dat we in dit artikel echt zullen ingaan op alle verschillende soorten parameters en hoe ze je kunnen helpen, maar als je net begint met C# en je gewoon wat resultaten wilt zien, is het volgende misschien een beetje te complex en technisch voor nu. Sla de rest van het artikel gerust over en kom later terug als je er klaar voor bent.
Optionele parameters
Als u een methode aanroept met één of meer parameters, bent u standaard verplicht voor al deze parameters waarden op te geven. In sommige situaties kan het echter nodig zijn om één of meer parameters optioneel te maken. In sommige programmeertalen kunt u dat doen door de parameter als optioneel te markeren, maar in C# wordt een parameter optioneel gemaakt door er een standaardwaarde voor op te geven in de declaratie van de methode. Dit is eigenlijk een mooie oplossing, want het bespaart je het schrijven van extra code voor situaties waarin de parameter niet wordt geleverd door de aanroeper.
Hier is een voorbeeld van een methode met een optionele parameter:
public int AddNumbers(int number1, int number2, int number3 = 0)
{
return number1 + number2 + number3;
}
De laatste parameter (nummer 3) is nu optioneel, omdat we er een standaardwaarde voor hebben opgegeven (0). Wanneer u het aanroept, kunt u nu twee of drie waarden opgeven, zoals dit:
public void Main(string[] args)
{
AddNumbers(38, 4);
AddNumbers(36, 4, 2);
}
Je kan meer dan één parameter optioneel maken - in feite kan je methode bestaan uit alleen maar optionele parameters als dat nodig is. Vergeet alleen niet dat optionele parameters als laatste in de methodedeclaratie moeten komen - niet als eerste of tussen niet-optionele parameters.
The params modifier
Als alternatief voor een aantal optionele parameters kunt u de params modifier gebruiken om een willekeurig aantal parameters toe te staan. Het zou er zo uit kunnen zien:
public void GreetPersons(params string[] names) { }
Het oproepen ervan zou er dan zo uit kunnen zien:
GreetPersons("John", "Jane", "Tarzan");
Een ander voordeel van de params-aanpak is dat je ook nul parameters aan de methode mag doorgeven. Methoden met params kunnen ook gewone parameters aannemen, zolang de parameter met de params modifier de laatste is. Daarnaast kan slechts één parameter met het params keyword gebruikt worden per methode. Hier is een compleet voorbeeld waarin we de params modifier gebruiken om een variabel aantal namen af te drukken met onze GreetPersons() methode:
public void Main(string[] args)
{
GreetPersons("John", "Jane", "Tarzan");
}
public void GreetPersons(params string[] names)
{
foreach(string name in names)
Console.WriteLine("Hello " + name);
}
Parametertypes: waarde of referentie
C# en ook andere programmeertalen, maken een onderscheid tussen twee parametertypes: "per waarde" en "per referentie". De standaard in C# is "per waarde", wat betekent dat wanneer je een variabele doorgeeft aan een methode-aanroep, je eigenlijk een kopie van het object stuurt, in plaats van een verwijzing ernaar. Dit betekent ook dat je de parameter vanuit de methode kunt wijzigen, zonder dat dit gevolgen heeft voor het oorspronkelijke object dat je als parameter hebt doorgegeven.
We kunnen dit gedrag gemakkelijk aantonen met een voorbeeld:
public void Main(string[] args)
{
int number = 20;
AddFive(number);
Console.WriteLine(number);
}
public void AddFive(int number)
{
number = number + 5;
}
We hebben een heel eenvoudige methode genaamd AddFive() die 5 toevoegt aan het getal dat we doorgeven. Dus in onze methode Main() maken we een variabele genaamd number met de waarde 20 en roepen dan de methode AddFive() aan. Wanneer we nu op de volgende regel het variabele getal uitschrijven, zou je verwachten dat die waarde nu 25 is, maar in plaats daarvan blijft het 20. Waarom? Omdat de parameter standaard werd doorgegeven als een kopie van het oorspronkelijke object (met waarde), dus toen onze AddFive() methode op de parameter werkte, werkte het op een kopie en had het geen invloed op de oorspronkelijke variabele.
Er zijn momenteel twee manieren om dit gedrag te veranderen, zodat onze AddFive() methode de oorspronkelijke waarde mag wijzigen: We kunnen de ref modifier of de in/out modifiers gebruiken.
The ref modifier
De ref modifier is een afkorting voor "reference" en verandert in feite het gedrag van de parameter van het standaard gedrag van "by value" naar "by reference", wat betekent dat we nu een verwijzing naar de oorspronkelijke variabele doorgeven in plaats van een kopie van de waarde ervan. Hier is een aangepast voorbeeld:
public void Main(string[] args)
{
int number = 20;
AddFive(ref number);
Console.WriteLine(number);
}
public void AddFive(ref int number)
{
number = number + 5;
}
U zult merken dat ik het sleutelwoord "ref" op twee plaatsen heb toegevoegd: In de methode-declaratie en bij het doorgeven van de parameter in de methode-aanroep. Met deze wijziging krijgen we nu het gedrag dat we oorspronkelijk hadden kunnen verwachten - het resultaat is nu 25 in plaats van 20, omdat de ref modifier de methode laat werken op de werkelijke waarde die als parameter wordt doorgegeven in plaats van een kopie.
The out modifier
Net als de ref modifier zorgt de out modifier ervoor dat de parameter per referentie wordt doorgegeven in plaats van per waarde, maar er is een belangrijk verschil: Wanneer je de ref modifier gebruikt, geef je een geïnitialiseerde waarde door die je binnen de methode kunt wijzigen, of laten zoals hij is. Als je daarentegen de out modifier gebruikt, moet je de parameter in de methode initialiseren. Dit betekent ook dat u niet-geïnitialiseerde waarden kunt doorgeven als u de out-modifier gebruikt - de compiler zal klagen als u probeert een methode met een out-parameter te verlaten zonder er een waarde aan toe te kennen.
In C# kan een methode slechts één waarde teruggeven, maar als u de out modifier gebruikt, kunt u dit omzeilen door meerdere parameters door te geven met de out modifier - wanneer de methode is aangeroepen, zullen alle out parameters zijn toegewezen. Hier is een voorbeeld, waarbij we twee getallen doorgeven en dan met behulp van out modifiers, zowel een optelling als een aftrekking met deze getallen teruggeven:
public void Main(string[] args)
{
int addedValue, subtractedValue;
DoMath(10, 5, out addedValue, out subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);
}
public void DoMath(int number1, int number2, out int addedValue, out int subtractedValue)
{
addedValue = number1 + number2;
subtractedValue = number1 - number2;
}
Output:
15
5
Zoals u in het begin van het voorbeeld kunt zien, declareer ik de twee variabelen (addedValue en subtractedValue) voordat ik ze als out parameters doorgeef. Dit was een vereiste in vorige versies van de C# taal, maar vanaf C# versie 6 kun je ze direct declareren in de methode-aanroep, zoals dit:
DoMath(10, 5, out int addedValue, out int subtractedValue);
Console.WriteLine(addedValue);
Console.WriteLine(subtractedValue);
The in modifier
Net als de out modifier zorgt de in modifier ervoor dat de parameter wordt doorgegeven als een referentie in plaats van een kopie van de waarde, maar in tegenstelling tot de out modifier voorkomt de in modifier dat u wijzigingen aanbrengt in de variabele binnen de methode.
Je eerste gedachte zou kunnen zijn: Als ik de waarde van de parameter niet kan veranderen, dan kan ik hem net zo goed als een gewone parameter doorgeven, waarbij veranderingen geen invloed hebben op de oorspronkelijke variabele. En je hebt gelijk, het resultaat lijkt precies hetzelfde, maar er is nog steeds een heel geldige reden om de in-modifier te gebruiken: Door het door te geven als een referentie in plaats van een waarde, bespaar je resources omdat het framework geen tijd hoeft te besteden aan het maken van een kopie van het object bij het doorgeven aan de methode als een gewone parameter.
In de meeste gevallen zal dit niet veel uitmaken, omdat de meeste parameters eenvoudige strings of gehele getallen zijn, maar in situaties waarin u dezelfde methode herhaaldelijk aanroept in een lus of waarin u parameters met grote waarden doorgeeft, bv. zeer grote strings of structs, kan dit een mooie prestatieboost geven.
Hier is een voorbeeld waar we de in modifier gebruiken:
public void Main(string[] args)
{
string aVeryLargeString = "Lots of text...";
InMethod(aVeryLargeString);
}
public void InMethod(in string largeString)
{
Console.WriteLine(largeString);
}
In onze methode, InMethod(), is de parameter largeString nu een alleen-lezen verwijzing naar de oorspronkelijke variabele (aVeryLargeString), zodat we hem kunnen gebruiken, maar niet wijzigen. Als we dat proberen, zal de compiler klagen:
public void InMethod(in string largeString)
{
largeString = "We can't do this...";
}
Error: Cannot assign to variable 'in string' because it is a readonly variable
Named parameters
Zoals je in alle bovenstaande voorbeelden hebt gezien, heeft elke parameter een unieke naam in de methodedeclaratie. Hierdoor kun je binnen de methode naar de parameter verwijzen. Wanneer je de methode aanroept, gebruik je deze namen echter niet - je levert gewoon de waarden in dezelfde volgorde als ze zijn gedeclareerd. Dit is geen probleem voor eenvoudige methoden die 2-3 parameters nodig hebben, maar sommige methoden zijn complexer en vereisen meer parameters. In die situaties kan het vrij lastig zijn om uit te zoeken waar de verschillende waarden in een methode-aanroep naar verwijzen, zoals in dit voorbeeld:
PrintUserDetails(1, "John", 42, null);
Het is niet erg duidelijk wat de verschillende parameters betekenen in deze methode-aanroep, maar als je naar de methode-declaratie kijkt, kun je het zien:
public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
// Print details...
}
Maar het is vervelend als je voortdurend de methodedeclaratie moet opzoeken om te begrijpen wat de parameters doen, dus voor complexere methoden kun je de parameternamen rechtstreeks in de methodeaanroep opgeven. Hierdoor kun je ook de parameternamen in een willekeurige volgorde opgeven, in plaats van gedwongen te worden de declaratievolgorde te gebruiken. Hier is een voorbeeld:
PrintUserDetails(name: "John Doe", userId: 1);
Als extra bonus kunt u zo voor elk van uw optionele parameters een waarde opgeven, zonder dat u voor alle voorgaande optionele parameters in de declaratie waarden hoeft op te geven. Met andere woorden, als je een waarde wilt opgeven voor de addressLines parameter in dit voorbeeld, moet je ook een waarde opgeven voor de age parameter, omdat die eerst komt in de declaratie van de methode. Als u echter named parameters gebruikt, doet de volgorde er niet meer toe en kunt u gewoon waarden opgeven voor de vereiste parameters en één of meer van de optionele parameters, bijvoorbeeld op deze manier:
PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
Hier is het volledige voorbeeld van het gebruik van named parameters:
public void Main(string[] args)
{
PrintUserDetails(1, "John", 42, null);
PrintUserDetails(name: "John Doe", userId: 1);
PrintUserDetails(addressLines: new List<string>() { }, name: "Jane Doe", userId: 2);
}
public void PrintUserDetails(int userId, string name, int age = -1, List<string> addressLines = null)
{
// Print details...
Console.WriteLine(name + " is " + age + " years old...");
}
Samenvatting
Zoals je in dit artikel kunt zien, zijn parameters er in vele vormen en typen. Gelukkig kom je al een heel eind met gewone, ouderwetse parameters, maar als je eenmaal dieper in de C#-taal begint te graven, zal je er baat bij hebben alle types en modifiers te kennen zoals uitgelegd in dit artikel.