Delen via


System.Delegate en het delegate trefwoord

Vorige

In dit artikel worden de klassen in .NET behandeld die gemachtigden ondersteunen en hoe deze worden toegewezen aan het delegate trefwoord.

Wat zijn gemachtigden?

U kunt een gemachtigde beschouwen als een manier om een verwijzing naar een methode op te slaan, vergelijkbaar met de manier waarop u een verwijzing naar een object kunt opslaan. Net zoals u objecten aan methoden kunt doorgeven, kunt u methodeverwijzingen doorgeven met behulp van gemachtigden. Dit is handig als u flexibele code wilt schrijven waarin verschillende methoden kunnen worden 'aangesloten' om verschillende gedragingen te bieden.

Stel dat u een rekenmachine hebt waarmee bewerkingen op twee getallen kunnen worden uitgevoerd. In plaats van hardcoding van optelling, aftrekking, vermenigvuldiging en deling in afzonderlijke methoden, kunt u delegates gebruiken om elke bewerking weer te geven die twee getallen accepteert en een resultaat retourneert.

Gedelegeerdentypen definiëren

Laten we nu eens kijken hoe u gedelegeerdentypen maakt met behulp van het delegate trefwoord. Wanneer u een type gedelegeerde definieert, maakt u in wezen een sjabloon waarin wordt beschreven welk type methoden in die gemachtigde kunnen worden opgeslagen.

U definieert een type gemachtigde met behulp van syntaxis die lijkt op een methodehandtekening, maar met het delegate trefwoord aan het begin:

// Define a simple delegate that can point to methods taking two integers and returning an integer
public delegate int Calculator(int x, int y);

Deze Calculator delegate kan verwijzingen vasthouden naar elke methode die twee int parameters gebruikt en een int retourneert.

Laten we eens kijken naar een praktischer voorbeeld. Wanneer u een lijst wilt sorteren, moet u het sorteeralgoritmen laten weten hoe items moeten worden vergeleken. Laten we eens kijken hoe gemachtigden helpen met de List.Sort() methode. De eerste stap bestaat uit het maken van een gemachtigdentype voor de vergelijkingsbewerking:

// From the .NET Core library
public delegate int Comparison<in T>(T left, T right);

Deze Comparison<T> gedelegeerde kan verwijzingen bevatten naar elke methode die:

  • Neemt twee parameters van het type T
  • Retourneert een int (meestal -1, 0 of 1 om 'kleiner dan', 'gelijk aan' of 'groter dan' aan te geven)

Wanneer u een type gemachtigde als dit definieert, genereert de compiler automatisch een klasse die is afgeleid van System.Delegate die overeenkomt met uw handtekening. Deze klasse verwerkt alle complexiteit van het opslaan en aanroepen van de methodeverwijzingen voor u.

Het Comparison type gemachtigde is een algemeen type, wat betekent dat het met elk type Tkan werken. Zie Algemene klassen en methoden voor meer informatie over generics.

U ziet dat hoewel de syntaxis lijkt op het declareren van een variabele, u eigenlijk een nieuw type declareren. U kunt gemachtigdentypen definiëren binnen klassen, rechtstreeks binnen naamruimten of zelfs in de globale naamruimte.

Opmerking

Het rechtstreeks declareren van gedelegeerde typen (of andere typen) in de globale naamruimte wordt niet aanbevolen.

De compiler genereert ook toevoeg- en verwijderhandlers voor dit nieuwe type, zodat clients van deze klasse methoden aan de aanroeplijst van een exemplaar kunnen toevoegen en daaruit kunnen verwijderen. De compiler dwingt af dat de handtekening van de methode die wordt toegevoegd of verwijderd overeenkomt met de handtekening die wordt gebruikt bij het declareren van het type gemachtigde.

Instanties van delegaten declareren

Nadat u het type gedelegeerde hebt gedefinieerd, kunt u exemplaren (variabelen) van dat type maken. U kunt dit beschouwen als het maken van een 'site' waar u een verwijzing naar een methode kunt opslaan.

Net als alle variabelen in C# kunt u gedelegeerde-instanties niet rechtstreeks in een naamruimte of in de globale naamruimte declareren.

// Inside a class definition:
public Comparison<T> comparator;

Het type van deze variabele is Comparison<T> (het gemachtigde type dat u eerder hebt gedefinieerd) en de naam van de variabele is comparator. Op dit moment verwijst comparator nog niet naar een methode—het is als een lege plek die nog moet worden gevuld.

U kunt ook gedelegeerde variabelen declareren als lokale variabelen of methodeparameters, net als elk ander type variabele.

Gemachtigden aanroepen

Zodra u een delegate-instantie hebt die naar een methode verwijst, kunt u die methode via de delegate aanroepen. U roept de methoden aan die zich in de aanroeplijst van een gemachtigde bevinden door die gemachtigde aan te roepen alsof het een methode was.

Hier ziet u hoe de Sort() methode de vergelijkingsdelegatie gebruikt om de volgorde van objecten te bepalen:

int result = comparator(left, right);

In deze regel roept de code de methode aan die is gekoppeld aan de gemachtigde. U behandelt de gemachtigde variabele alsof het een methodenaam is en deze aanroept met behulp van de syntaxis van de normale methode-aanroep.

Deze coderegel maakt echter een onveilige aanname: er wordt ervan uitgegaan dat er een doelmethode is toegevoegd aan de gemachtigde. Als er geen methoden zijn gekoppeld, zou de bovenstaande regel ertoe leiden dat er een NullReferenceException wordt gegenereerd. De patronen die worden gebruikt om dit probleem op te lossen, zijn geavanceerder dan een eenvoudige null-controle en worden verderop in deze reeks behandeld.

Aanroepdoelen toewijzen, toevoegen en verwijderen

U weet nu hoe u gedelegeerdentypen definieert, gemachtigde instanties declareert en gemachtigden aanroept. Maar hoe koppelt u eigenlijk een methode aan een gemachtigde? Hier komt toewijzing van delegaten van pas.

Als u een gemachtigde wilt gebruiken, moet u er een methode aan toewijzen. De methode die u toewijst, moet dezelfde handtekening (dezelfde parameters en hetzelfde retourtype) hebben als het gedelegeerdentype definieert.

Laten we een praktisch voorbeeld bekijken. Stel dat u een lijst met tekenreeksen wilt sorteren op lengte. U moet een vergelijkingsmethode maken die overeenkomt met de Comparison<string> handtekening voor gemachtigden:

private static int CompareLength(string left, string right) =>
    left.Length.CompareTo(right.Length);

Deze methode neemt twee tekenreeksen en retourneert een geheel getal dat aangeeft welke tekenreeks 'groter' is (langer in dit geval). De methode wordt gedeclareerd als privé, wat prima is. U hebt de methode niet nodig om deel uit te maken van uw openbare interface om deze te gebruiken met een gemachtigde.

U kunt deze methode nu doorgeven aan de List.Sort() methode:

phrases.Sort(CompareLength);

U ziet dat u de methodenaam zonder haakjes gebruikt. Dit vertelt de compiler dat de methodereferentie moet worden geconverteerd naar een gemachtigde die later kan worden aangeroepen. De Sort() methode roept uw CompareLength methode aan wanneer deze twee tekenreeksen moet vergelijken.

U kunt ook explicieter zijn door een gemachtigde variabele te declareren en de methode eraan toe te wijzen:

Comparison<string> comparer = CompareLength;
phrases.Sort(comparer);

Beide benaderingen bereiken hetzelfde. De eerste benadering is beknopter, terwijl de tweede de gedelegeerdetoewijzing explicieter maakt.

Voor eenvoudige methoden is het gebruikelijk om lambda-expressies te gebruiken in plaats van een afzonderlijke methode te definiëren:

Comparison<string> comparer = (left, right) => left.Length.CompareTo(right.Length);
phrases.Sort(comparer);

Lambda-expressies bieden een compacte manier om eenvoudige methoden inline te definiëren. Het gebruik van lambda-expressies voor gedelegeerde doelen wordt uitgebreider behandeld in een latere sectie.

In de voorbeelden tot nu toe worden gemachtigden met één doelmethode weergegeven. Gedelegeerde objecten kunnen echter ondersteuning bieden voor aanroeplijsten met meerdere doelmethoden die zijn gekoppeld aan één gedelegeerde-object. Deze mogelijkheid is met name handig voor scenario's voor het afhandelen van gebeurtenissen.

Klassen Delegate en MulticastDelegate

Achter de schermen zijn de gedelegeerde functies die u hebt gebruikt, gebouwd op twee belangrijke klassen in het .NET-framework: Delegate en MulticastDelegate. Meestal werkt u niet rechtstreeks met deze klassen, maar ze bieden de basis waarmee gemachtigden werken.

De System.Delegate klasse en de directe subklasse System.MulticastDelegate bieden de frameworkondersteuning voor het maken van gemachtigden, het registreren van methoden als gemachtigde doelen en het aanroepen van alle methoden die zijn geregistreerd bij een gemachtigde.

Hier volgt een interessant ontwerpdetail: System.Delegate en System.MulticastDelegate zijn niet zelf gedelegeerde typen die u kunt gebruiken. In plaats daarvan fungeren ze als de basisklassen voor alle specifieke gedelegeerdentypen die u maakt. De C#-taal voorkomt dat u rechtstreeks van deze klassen overgaat. In plaats daarvan moet u het delegate trefwoord gebruiken.

Wanneer u het delegate trefwoord gebruikt om een gemachtigdentype te declareren, maakt de C#-compiler automatisch een klasse die is afgeleid van MulticastDelegate uw specifieke handtekening.

Waarom dit ontwerp?

Dit ontwerp heeft zijn wortels in de eerste release van C# en .NET. Het ontwerpteam had verschillende doelen:

  1. Typeveiligheid: Het team wilde ervoor zorgen dat de taal typeveiligheid verzekerde bij het gebruik van delegates. Dit betekent dat gemachtigden worden aangeroepen met het juiste type en het juiste aantal argumenten, en dat retourtypen correct worden geverifieerd tijdens het compileren.

  2. Prestaties: Door de compiler concrete gedelegeerdenklassen te laten genereren die specifieke methodehandtekeningen vertegenwoordigen, kan de runtime gedelegeerde aanroepen optimaliseren.

  3. Eenvoud: Gedelegeerden werden opgenomen in de .NET-release 1.0, voordat generics werden geïntroduceerd. Het ontwerp moest binnen de tijdsbeperkingen functioneren.

De oplossing was om de compiler de concrete delegeringsklassen te laten maken die overeenkomen met uw methodehandtekeningen, waardoor de veiligheid van het type wordt gewaarborgd terwijl de complexiteit van u wordt verborgen.

Werken met delegatiemethoden

Hoewel u geen afgeleide klassen rechtstreeks kunt maken, gebruikt u af en toe methoden die zijn gedefinieerd voor de Delegate en MulticastDelegate klassen. Hier volgen de belangrijkste die u moet weten:

Elke gemachtigde waarmee u werkt, is afgeleid van MulticastDelegate. Een 'multicast'-gemachtigde betekent dat meer dan één methodedoel kan worden aangeroepen bij het aanroepen via een gemachtigde. Het oorspronkelijke ontwerp beschouwde een onderscheid te maken tussen gemachtigden die slechts één methode konden aanroepen versus gedelegeerden die meerdere methoden konden aanroepen. In de praktijk bleek dit onderscheid minder nuttig dan oorspronkelijk gedacht, dus alle gemachtigden in .NET ondersteunen meerdere doelmethoden.

De meestgebruikte methoden bij het werken met gemachtigden zijn:

  • Invoke(): roept alle methoden aan die zijn gekoppeld aan de gemachtigde
  • BeginInvoke() / EndInvoke(): Wordt gebruikt voor asynchrone aanroeppatronen (hoewel async/await dit nu de voorkeur heeft)

In de meeste gevallen roept u deze methoden niet rechtstreeks aan. In plaats daarvan gebruikt u de syntaxis van de methode-aanroep voor de gemachtigde variabele, zoals wordt weergegeven in de bovenstaande voorbeelden. Zoals u later in deze reeks ziet, zijn er echter patronen die rechtstreeks met deze methoden werken.

Samenvatting

Nu u hebt gezien hoe de syntaxis van de C#-taal aan de onderliggende .NET-klassen is toegewezen, kunt u onderzoeken hoe sterk getypte delegaten worden gemaakt, gebruikt en aangeroepen in meer complexe scenario's.

Volgende