Delen via


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, protecteden internalprivate 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 voidofwel 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 en D2 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 en M hetzelfde aantal parameters hebben en elke parameter heeft D dezelfde by-reference parameter modifier als de bijbehorende parameter in M.
  • 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 in M.
  • Een van de volgende beweringen is waar:
    • D en M zijn beide returns-no-value
    • D en M worden geretourneerd per waarde (§15.6.1, §20.2) en een identiteit of impliciete verwijzingsconversie bestaat van het retourtype M naar het retourtype van D.
    • D en M beide retourneert per verw, er bestaat een identiteitsconversie tussen het retourtype en M het retourtype D, 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 en B.M1 zijn compatibel met zowel de typen gemachtigden D1 als D2, omdat ze hetzelfde retourtype en dezelfde parameterlijst hebben. De methoden B.M2, B.M3en B.M4 zijn niet compatibel met de gedelegeerdentypen D1 en D2omdat ze verschillende retourtypen of parameterlijsten hebben. De methoden B.M5 en B.M6 zijn beide compatibel met het type D3gemachtigde.

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 gemachtigde Predicate<int> en de methode X.G is compatibel met het type gemachtigde Predicate<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 Print methode is compatibel met het Action<string> type gemachtigde, omdat elke aanroep van een Action<string> gemachtigde ook een geldige aanroep van de Print methode is.

Als de handtekening van de Print bovenstaande methode bijvoorbeeld is gewijzigd Print(object value, bool prependTimestamp = false) , is de Print methode niet langer compatibel met Action<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 en cd2 worden geïnstantieerd, worden elke methode ingekapseld. Wanneer cd3 wordt geïnstantieerd, heeft het een aanroeplijst van twee methoden en M1 M2, in die volgorde. cd4De aanroeplijst bevat M1, M2en M1, in die volgorde. Voor cd5de aanroeplijst bevat M1de aanroep , , M2M1en M1M2, 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 van cd3 de aanroeplijst, heeft slechts één lid, maar dat lid is een lijst met de methoden M1 en M2 die methoden worden aangeroepen td3 in dezelfde volgorde als die worden aangeroepen door cd3. Op dezelfde manier als de td4 aanroeplijst wordt geïnstantieerd, zijn er slechts twee vermeldingen, maar worden de drie methoden M1aangeroepen, M2en M1in die volgorde, net als cd4 in die volgorde.

De structuur van de aanroeplijst is van invloed op aftrekken van gemachtigden. Delegeren cd6, gemaakt door af te trekken cd2 (die aanroept M2) van cd4 (die aanroept M1, M2en M1) roept M1 en M1. Delegeer td6echter, gemaakt door af te trekken cd2 (die aanroept M2) van td4 (die aanroept M1, M2en M1) nog steeds aanroept M1, M2 en M1, in die volgorde, zoals M2 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 -= cd1verwijst de gedelegeerde cd3 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