20 Gemachtigden
20.1 Algemeen
Een gedelegeerdedeclaratie definieert een klasse die is afgeleid van de klasse System.Delegate
. Een gemachtigde instantie bevat een aanroeplijst, een lijst met een of meer methoden, die elk een aanroepbare entiteit wordt genoemd. Een aanroepbare entiteit bestaat bijvoorbeeld uit een exemplaar en een methode voor dat exemplaar. Voor statische methoden bestaat een aanroepbare entiteit uit slechts een methode. Als u een gemachtigde instantie aanroept met een geschikte set argumenten, wordt elk van de aanroepbare entiteiten van de gemachtigde aangeroepen met de opgegeven set argumenten.
Opmerking: Een interessante en nuttige eigenschap van een gemachtigde instantie is dat het niet weet of om de klassen van de methoden die het inkapselt; alles wat van belang is, is dat deze methoden compatibel zijn (§20.4) met het type van de gemachtigde. Dit maakt gemachtigden perfect geschikt voor 'anonieme' aanroep. eindnotitie
20.2 Declaraties delegeren
Een delegate_declaration is een type_declaration (§14.7) die een nieuw type gemachtigde declareert.
delegate_declaration
: attributes? delegate_modifier* 'delegate' return_type delegate_header
| attributes? delegate_modifier* 'delegate' ref_kind ref_return_type
delegate_header
;
delegate_header
: identifier '(' parameter_list? ')' ';'
| identifier variant_type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
delegate_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier wordt gedefinieerd in §23.2.
Het is een compilatiefout voor dezelfde wijziging die meerdere keren in een gedelegeerdedeclaratie wordt weergegeven.
Een gedelegeerdedeclaratie die een variant_type_parameter_list levert, is een algemene gedelegeerdedeclaratie. Bovendien is elke gedelegeerde die genest is in een algemene klassedeclaratie of een algemene struct-declaratie zelf een algemene gedelegeerdedeclaratie, omdat typeargumenten voor het betreffende type worden opgegeven om een samengesteld type te maken (§8.4).
De new
wijzigingsfunctie is alleen toegestaan voor gemachtigden die binnen een ander type zijn gedeclareerd. In dat geval wordt aangegeven dat een dergelijke gemachtigde een overgenomen lid met dezelfde naam verbergt, zoals beschreven in §15.3.5.
De public
, protected
en internal
private
modifiers bepalen de toegankelijkheid van het type gedelegeerde. Afhankelijk van de context waarin de gedelegeerdedeclaratie plaatsvindt, zijn sommige van deze wijzigingskenmerken mogelijk niet toegestaan (§7.5.2).
De typenaam van de gemachtigde is id.
Net als bij methoden (§15.6.1), als ref
deze aanwezig is, retourneert de gemachtigde per verw; anders retourneert de gemachtigde, indien return_type is void
, de gedelegeerde retourneert-no-value; anders retourneert de gemachtigde per waarde.
Met de optionele parameter_list worden de parameters van de gemachtigde opgegeven.
De return_type van een retour-by-value of retourneert-no-value delegate-declaratie geeft het type van het resultaat op, indien van toepassing, geretourneerd door de gemachtigde.
De ref_return_type van een declaratie van de gedelegeerden van retourneert het type variabele waarnaar wordt verwezen door de variable_reference (§9.5) die door de gedelegeerde wordt geretourneerd.
De optionele variant_type_parameter_list (§18.2.3) specificeert de typeparameters aan de gemachtigde zelf.
Het retourtype van een gemachtigdentype is void
ofwel uitvoerveilig (§18.2.3.2).
Alle parametertypen van een gemachtigde zijn invoerveilig (§18.2.3.2). Bovendien zijn alle typen uitvoer- of referentieparameters ook uitvoerveilig.
Opmerking: uitvoerparameters moeten invoerveilig zijn vanwege algemene implementatiebeperkingen. eindnotitie
Bovendien is elke beperking van het klassetype, de beperking van het interfacetype en de parameterbeperking van het type voor elk type van de gemachtigde invoerveilig.
Gedelegeerdentypen in C# zijn naamequivalent, niet structureel gelijkwaardig.
Voorbeeld:
delegate int D1(int i, double d); delegate int D2(int c, double d);
De gedelegeerdentypen
D1
enD2
zijn twee verschillende typen, dus ze zijn niet uitwisselbaar, ondanks hun identieke handtekeningen.eindvoorbeeld
Net als andere algemene typedeclaraties worden typeargumenten gegeven om een samengesteld type gedelegeerde te maken. De parametertypen en het retourtype van een samengesteld gemachtigdetype worden gemaakt door voor elke typeparameter in de declaratie van de gedelegeerde het bijbehorende typeargument van het samengestelde gemachtigdetype te vervangen.
De enige manier om een type gedelegeerde te declareren, is via een delegate_declaration. Elk type gemachtigde is een verwijzingstype dat is afgeleid van System.Delegate
. De leden die voor elk type gemachtigde zijn vereist, worden beschreven in §20.3. Gedelegeerdentypen zijn impliciet sealed
, dus het is niet toegestaan om een type af te leiden van een gemachtigde. Het is ook niet toegestaan om een niet-gemachtigde klassetype te declareren dat is afgeleid van System.Delegate
. System.Delegate
is zelf geen gemachtigdentype; het is een klassetype waaruit alle gedelegeerdentypen worden afgeleid.
20.3 Leden delegeren
Elk gemachtigdentype neemt leden over van de Delegate
klasse, zoals beschreven in §15.3.4. Bovendien moet elk type gemachtigde een niet-generieke Invoke
methode opgeven waarvan de parameterlijst overeenkomt met de parameter_list in de declaratie van de gedelegeerde, waarvan het retourtype overeenkomt met de return_type of ref_return_type in de declaratie van gedelegeerden, en voor retouren-by-ref-gemachtigden waarvan de ref_kind overeenkomt met die in de gedelegeerdedeclaratie. De Invoke
methode moet ten minste zo toegankelijk zijn als het met gedelegeerdentype. Het aanroepen van de Invoke
methode voor een gemachtigde is semantisch gelijk aan het gebruik van de syntaxis voor het aanroepen van gemachtigden (§20.6).
Implementaties kunnen extra leden definiëren in het type gedelegeerde.
Met uitzondering van instantiëring kan elke bewerking die kan worden toegepast op een klasse- of klasse-exemplaar, ook worden toegepast op respectievelijk een gemachtigde klasse of instantie. Het is met name mogelijk om toegang te krijgen tot leden van het System.Delegate
type via de gebruikelijke toegangssyntaxis voor leden.
20.4 Compatibiliteit delegeren
Een methode of gemachtigdentype is compatibel met een gemachtigdentype M
D
als aan alle volgende voorwaarden wordt voldaan:
D
enM
hetzelfde aantal parameters hebben en elke parameter heeftD
dezelfde by-reference parameter modifier als de bijbehorende parameter inM
.- Voor elke waardeparameter bestaat een identiteitsconversie (§10.2.2) of impliciete verwijzingsconversie (§10.2.8) van het parametertype
D
in het bijbehorende parametertype inM
. - Voor elke by-reference-parameter is het parametertype
D
hetzelfde als het parametertype inM
. - Een van de volgende beweringen is waar:
D
enM
zijn beide returns-no-valueD
enM
worden geretourneerd per waarde (§15.6.1, §20.2) en een identiteit of impliciete verwijzingsconversie bestaat van het retourtypeM
naar het retourtype vanD
.D
enM
beide retourneert per verw, er bestaat een identiteitsconversie tussen het retourtype enM
het retourtypeD
, en beide hebben dezelfde ref_kind.
Deze definitie van compatibiliteit staat covariantie toe in retourtype en contravariantie in parametertypen.
Voorbeeld:
delegate int D1(int i, double d); delegate int D2(int c, double d); delegate object D3(string s); class A { public static int M1(int a, double b) {...} } class B { public static int M1(int f, double g) {...} public static void M2(int k, double l) {...} public static int M3(int g) {...} public static void M4(int g) {...} public static object M5(string s) {...} public static int[] M6(object o) {...} }
De methoden
A.M1
enB.M1
zijn compatibel met zowel de typen gemachtigdenD1
alsD2
, omdat ze hetzelfde retourtype en dezelfde parameterlijst hebben. De methodenB.M2
,B.M3
enB.M4
zijn niet compatibel met de gedelegeerdentypenD1
enD2
omdat ze verschillende retourtypen of parameterlijsten hebben. De methodenB.M5
enB.M6
zijn beide compatibel met het typeD3
gemachtigde.eindvoorbeeld
Voorbeeld:
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
De methode
X.F
is compatibel met het type gemachtigdePredicate<int>
en de methodeX.G
is compatibel met het type gemachtigdePredicate<string>
.eindvoorbeeld
Opmerking: De intuïtieve betekenis van compatibiliteit met gemachtigden is dat een methode compatibel is met een gemachtigde als elke aanroep van de gemachtigde kan worden vervangen door een aanroep van de methode zonder inbreuk te maken op typeveiligheid, waarbij optionele parameters en parametermatrices als expliciete parameters worden behandeld. Bijvoorbeeld in de volgende code:
delegate void Action<T>(T arg); class Test { static void Print(object value) => Console.WriteLine(value); static void Main() { Action<string> log = Print; log("text"); } }
De
Action<string>
type gemachtigde, omdat elke aanroep van eenAction<string>
gemachtigde ook een geldige aanroep van deAls de handtekening van de
Print(object value, bool prependTimestamp = false)
, is deAction<string>
de regels van deze component.eindnotitie
20.5 Instantieer delegeren
Een instantie van een gemachtigde wordt gemaakt door een delegate_creation_expression (§12.8.16.6), een conversie naar een gemachtigdetype, combinatie van gemachtigde of verwijdering van gemachtigde. Het zojuist gemaakte gemachtigde exemplaar verwijst vervolgens naar een of meer van:
- De statische methode waarnaar wordt verwezen in de delegate_creation_expression of
- Het doelobject (dat niet kan zijn
null
) en de instantiemethode waarnaar wordt verwezen in de delegate_creation_expression, of - Nog een gemachtigde (§12.8.16.6).
Voorbeeld:
delegate void D(int x); class C { public static void M1(int i) {...} public void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // Static method C t = new C(); D cd2 = new D(t.M2); // Instance method D cd3 = new D(cd2); // Another delegate } }
eindvoorbeeld
De set methoden die door een gemachtigde instantie worden ingekapseld, wordt een aanroeplijst genoemd. Wanneer een gemachtigde instantie wordt gemaakt op basis van één methode, wordt deze methode ingekapseld en bevat de aanroeplijst slechts één vermelding. Wanneer echter twee niet-gemachtigdenull
instanties worden gecombineerd, worden hun aanroeplijsten samengevoegd, in de volgorde van de linkeroperand en de rechteroperand, om een nieuwe aanroeplijst te vormen, die twee of meer vermeldingen bevat.
Wanneer een nieuwe gemachtigde wordt gemaakt op basis van één gemachtigde, heeft de lijst met resulterende aanroepen slechts één vermelding. Dit is de brondelegatie (§12.8.16.6).
Gemachtigden worden gecombineerd met behulp van het binaire bestand +
(§12.10.5) en +=
operators (§12.21.4). Een gemachtigde kan worden verwijderd uit een combinatie van gemachtigden, met behulp van het binaire bestand -
(§12.10.6) en -=
operators (§12.21.4). Gemachtigden kunnen worden vergeleken voor gelijkheid (§12.12.9).
Voorbeeld: In het volgende voorbeeld ziet u de instantie van een aantal gemachtigden en de bijbehorende aanroeplijsten:
delegate void D(int x); class C { public static void M1(int i) {...} public static void M2(int i) {...} } class Test { static void Main() { D cd1 = new D(C.M1); // M1 - one entry in invocation list D cd2 = new D(C.M2); // M2 - one entry D cd3 = cd1 + cd2; // M1 + M2 - two entries D cd4 = cd3 + cd1; // M1 + M2 + M1 - three entries D cd5 = cd4 + cd3; // M1 + M2 + M1 + M1 + M2 - five entries D td3 = new D(cd3); // [M1 + M2] - ONE entry in invocation // list, which is itself a list of two methods. D td4 = td3 + cd1; // [M1 + M2] + M1 - two entries D cd6 = cd4 - cd2; // M1 + M1 - two entries in invocation list D td6 = td4 - cd2; // [M1 + M2] + M1 - two entries in invocation list, // but still three methods called, M2 not removed. } }
Wanneer
cd1
encd2
worden geïnstantieerd, worden elke methode ingekapseld. Wanneercd3
wordt geïnstantieerd, heeft het een aanroeplijst van twee methoden enM1
M2
, in die volgorde.cd4
De aanroeplijst bevatM1
,M2
enM1
, in die volgorde. Voorcd5
de aanroeplijst bevatM1
de aanroep , ,M2
M1
enM1
M2
, in die volgorde.Wanneer u een gemachtigde maakt van een andere gemachtigde met een delegate_creation_expression heeft het resultaat een aanroeplijst met een andere structuur dan het origineel, maar dit resulteert in dezelfde methoden die in dezelfde volgorde worden aangeroepen. Wanneer
td3
wordt gemaakt op basis vancd3
de aanroeplijst, heeft slechts één lid, maar dat lid is een lijst met de methodenM1
enM2
die methoden worden aangeroepentd3
in dezelfde volgorde als die worden aangeroepen doorcd3
. Op dezelfde manier als detd4
aanroeplijst wordt geïnstantieerd, zijn er slechts twee vermeldingen, maar worden de drie methodenM1
aangeroepen,M2
enM1
in die volgorde, net alscd4
in die volgorde.De structuur van de aanroeplijst is van invloed op aftrekken van gemachtigden. Delegeren
cd6
, gemaakt door af te trekkencd2
(die aanroeptM2
) vancd4
(die aanroeptM1
,M2
enM1
) roeptM1
enM1
. Delegeertd6
echter, gemaakt door af te trekkencd2
(die aanroeptM2
) vantd4
(die aanroeptM1
,M2
enM1
) nog steeds aanroeptM1
,M2
enM1
, in die volgorde, zoalsM2
niet één vermelding in de lijst, maar een lid van een geneste lijst is. Zie §20.6 voor meer voorbeelden van het combineren (en verwijderen van) gemachtigden.eindvoorbeeld
Zodra een instantie is geïnstantieerd, verwijst een gemachtigde instantie altijd naar dezelfde aanroeplijst.
Opmerking: Houd er rekening mee dat wanneer twee gemachtigden worden gecombineerd of een van de andere wordt verwijderd, een nieuwe gemachtigde resultaten met een eigen aanroeplijst; de aanroeplijsten van de gedelegeerden gecombineerd of verwijderd blijven ongewijzigd. eindnotitie
20.6 Gemachtigde aanroep
C# biedt speciale syntaxis voor het aanroepen van een gemachtigde. Wanneer een niet-gemachtigdenull
instantie waarvan de aanroeplijst één vermelding bevat, wordt aangeroepen, wordt de ene methode aangeroepen met dezelfde argumenten die zijn opgegeven en wordt dezelfde waarde geretourneerd als de methode waarnaar wordt verwezen. (Zie §12.8.9.4 voor gedetailleerde informatie over het aanroepen van gemachtigden.) Als er een uitzondering optreedt tijdens de aanroep van een dergelijke gemachtigde en die uitzondering niet wordt gevangen binnen de methode die is aangeroepen, blijft de zoekopdracht naar een uitzonderings catch-component in de methode die de gemachtigde wordt genoemd, alsof die methode de methode rechtstreeks had aangeroepen waarnaar die gemachtigde heeft verwezen.
Aanroepen van een gemachtigde instantie waarvan de aanroeplijst meerdere vermeldingen bevat, wordt voortgezet door elk van de methoden in de aanroeplijst synchroon aan te roepen. Elke methode die wordt aangeroepen, wordt dezelfde set argumenten doorgegeven als die is gegeven aan het gemachtigde exemplaar. Als een dergelijke gemachtigde aanroep referentieparameters bevat (§15.6.2.3.3), vindt elke aanroepmethode plaats met een verwijzing naar dezelfde variabele. Wijzigingen in die variabele met één methode in de aanroeplijst zijn zichtbaar voor methoden verderop in de aanroeplijst. Als de gemachtigde aanroep uitvoerparameters of een retourwaarde bevat, komt de uiteindelijke waarde uit de aanroep van de laatste gemachtigde in de lijst. Als er een uitzondering optreedt tijdens de verwerking van de aanroep van een dergelijke gemachtigde en deze uitzondering niet wordt gevangen binnen de methode die is aangeroepen, wordt de zoekactie naar een uitzonderings catch-component voortgezet in de methode die de gemachtigde wordt genoemd, en worden eventuele methoden verderop in de aanroeplijst niet aangeroepen.
Er wordt geprobeerd een gemachtigde instantie aan te roepen waarvan de waarde null
resulteert in een uitzondering van het type System.NullReferenceException
.
Voorbeeld: In het volgende voorbeeld ziet u hoe u gedelegeerden kunt instantiëren, combineren, verwijderen en aanroepen:
delegate void D(int x); class C { public static void M1(int i) => Console.WriteLine("C.M1: " + i); public static void M2(int i) => Console.WriteLine("C.M2: " + i); public void M3(int i) => Console.WriteLine("C.M3: " + i); } class Test { static void Main() { D cd1 = new D(C.M1); cd1(-1); // call M1 D cd2 = new D(C.M2); cd2(-2); // call M2 D cd3 = cd1 + cd2; cd3(10); // call M1 then M2 cd3 += cd1; cd3(20); // call M1, M2, then M1 C c = new C(); D cd4 = new D(c.M3); cd3 += cd4; cd3(30); // call M1, M2, M1, then M3 cd3 -= cd1; // remove last M1 cd3(40); // call M1, M2, then M3 cd3 -= cd4; cd3(50); // call M1 then M2 cd3 -= cd2; cd3(60); // call M1 cd3 -= cd2; // impossible removal is benign cd3(60); // call M1 cd3 -= cd1; // invocation list is empty so cd3 is null // cd3(70); // System.NullReferenceException thrown cd3 -= cd1; // impossible removal is benign } }
Zoals wordt weergegeven in de instructie
cd3 += cd1;
, kan een gemachtigde meerdere keren aanwezig zijn in een aanroeplijst. In dit geval wordt deze eenvoudigweg één keer per exemplaar aangeroepen. In een aanroeplijst zoals deze, wanneer die gemachtigde wordt verwijderd, is de laatste instantie in de aanroeplijst degene die daadwerkelijk is verwijderd.Direct vóór de uitvoering van de definitieve verklaring
cd3 -= cd1
verwijst de gedelegeerdecd3
naar een lege aanroeplijst. Het verwijderen van een gemachtigde uit een lege lijst (of het verwijderen van een niet-bestaande gemachtigde uit een niet-lege lijst) is geen fout.De geproduceerde uitvoer is:
C.M1: -1 C.M2: -2 C.M1: 10 C.M2: 10 C.M1: 20 C.M2: 20 C.M1: 20 C.M1: 30 C.M2: 30 C.M1: 30 C.M3: 30 C.M1: 40 C.M2: 40 C.M3: 40 C.M1: 50 C.M2: 50 C.M1: 60 C.M1: 60
eindvoorbeeld
ECMA C# draft specification