Dela via


20 ombud

20.1 Allmänt

En ombudsdeklaration definierar en klass som härleds från klassen System.Delegate. En ombudsinstans kapslar in en anropslista, som är en lista över en eller flera metoder, som var och en kallas för en anropsbar entitet. Till exempel består en anropsbar entitet av en instans och en metod för den instansen. För statiska metoder består en anropsbar entitet av bara en metod. Om du anropar en delegatinstans med en lämplig uppsättning argument anropas var och en av ombudets anropsbara entiteter med den angivna uppsättningen argument.

Obs! En intressant och användbar egenskap för en delegatinstans är att den inte känner till eller bryr sig om klasserna för de metoder som den kapslar in. Allt som betyder något är att dessa metoder är kompatibla (§20.4) med ombudets typ. Detta gör ombuden perfekt lämpade för "anonyma" anrop. slutkommentar

20.2 Delegera deklarationer

En delegate_declaration är en type_declaration (§14.7) som deklarerar en ny delegattyp.

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 definieras i §23.2.

Det är ett kompileringsfel för samma modifierare som ska visas flera gånger i en delegatdeklaration.

En delegatdeklaration som tillhandahåller en variant_type_parameter_list är en allmän delegatdeklaration. Dessutom är alla ombud kapslade i en generisk klassdeklaration eller en allmän structdeklaration i sig en allmän delegatdeklaration, eftersom typargument för den innehållande typen ska tillhandahållas för att skapa en konstruerad typ (§8.4).

Modifieraren new tillåts endast för ombud som deklareras inom en annan typ, i vilket fall den anger att en sådan delegat döljer en ärvd medlem med samma namn, enligt beskrivningen i §15.3.5.

Modifierarna public, protected, internaloch private styr tillgängligheten för ombudstypen. Beroende på i vilken kontext ombudsdeklarationen inträffar är vissa av dessa modifierare kanske inte tillåtna (§7.5.2).

Ombudets typnamn är identifierare.

Som med metoder (§15.6.1), om ref finns, returnerar ombudet-by-ref; annars, om return_type är void, returnerar ombudet-inget-värde; annars returnerar ombudet per värde.

Den valfria parameter_list anger ombudets parametrar.

Den return_type av en delegatdeklaration som returneras per värde eller returnerar inget värde anger vilken typ av resultat som returneras av ombudet.

Ref_return_type i en delegatdeklaration för return-by-ref anger vilken typ av variabel som refereras av variable_reference (§9.5) som returneras av ombudet.

Den valfria variant_type_parameter_list (§18.2.3) anger typparametrarna för själva ombudet.

Returtypen för en ombudstyp ska vara antingen void, eller utdatasäker (§18.2.3.2).

Alla parametertyper av en delegattyp ska vara indatasäkra (§18.2.3.2). Dessutom ska alla typer av utdata eller referensparametrar också vara utdatasäkra.

Obs! Utdataparametrar måste vara indatasäkra på grund av vanliga implementeringsbegränsningar. slutkommentar

Dessutom ska varje klasstypsbegränsning, gränssnittstypsbegränsning och typparameterbegränsning för alla typparametrar i ombudet vara indatasäkra.

Ombudstyper i C# är namnmotsvarigheter, inte strukturellt likvärdiga.

Exempel:

delegate int D1(int i, double d);
delegate int D2(int c, double d);

Ombudstyperna D1 och D2 är två olika typer, så de är inte utbytbara trots deras identiska signaturer.

slutexempel

Precis som andra allmänna typdeklarationer ska typargument ges för att skapa en konstruerad delegattyp. Parametertyperna och returtypen för en konstruerad ombudstyp skapas genom att ersätta, för varje typparameter i delegatdeklarationen, motsvarande typargument för den konstruerade delegattypen.

Det enda sättet att deklarera en ombudstyp är via en delegate_declaration. Varje ombudstyp är en referenstyp som härleds från System.Delegate. De medlemmar som krävs för varje ombudstyp beskrivs i §20.3. Ombudstyper är implicit sealed, så det är inte tillåtet att härleda någon typ från en ombudstyp. Det är inte heller tillåtet att deklarera en klasstyp som inte är delegerad som härleds från System.Delegate. System.Delegate är inte i sig en ombudstyp. det är en klasstyp som alla ombudstyper härleds från.

20.3 Delegera medlemmar

Varje ombudstyp ärver medlemmar från klassen enligt beskrivningen Delegate i §15.3.4. Dessutom ska varje ombudstyp tillhandahålla en icke-generisk Invoke metod vars parameterlista matchar parameter_list i ombudsdeklarationen, vars returtyp matchar return_type eller ref_return_type i delegatdeklarationen, och för ombud för return-by-ref vars ref_kind matchar den i delegatdeklarationen. Metoden Invoke ska vara minst lika tillgänglig som den som innehåller ombudstypen. Invoke Att anropa metoden för en ombudstyp är semantiskt likvärdigt med att använda syntaxen för anrop av ombud (§20.6) .

Implementeringar kan definiera ytterligare medlemmar i ombudstypen.

Förutom instansiering kan alla åtgärder som kan tillämpas på en klass- eller klassinstans också tillämpas på en ombudsklass eller instans. I synnerhet är det möjligt att komma åt medlemmar av System.Delegate typen via den vanliga syntaxen för medlemsåtkomst.

20.4 Delegera kompatibilitet

En metod- eller ombudstyp M är kompatibel med en ombudstyp D om allt följande är sant:

  • D och M har samma antal parametrar, och varje parameter i D har samma parametermodifierare med referens som motsvarande parameter i M.
  • För varje värdeparameter finns en identitetskonvertering (§10.2.2) eller implicit referenskonvertering (§10.2.8) från parametertypen in D till motsvarande parametertyp i M.
  • För varje bireferensparameter är parametertypen i D samma som parametertypen i M.
  • Något av följande är sant:
    • D och M är båda returns-no-value
    • Doch M är return-by-value (§15.6.1, §20.2), och det finns en identitet eller implicit referenskonvertering från returtypen M till returtypen .D
    • D och M är båda return-by-ref, finns det en identitetskonvertering mellan returtypen M och returtypen D, och båda har samma ref_kind.

Den här definitionen av kompatibilitet tillåter kovarians i returtyp och kontravarians i parametertyper.

Exempel:

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) {...}
}

Metoderna A.M1 och B.M1 är kompatibla med både de delegerade typerna D1 och D2, eftersom de har samma returtyp och parameterlista. Metoderna B.M2, B.M3och B.M4 är inte kompatibla med de delegerade typerna D1 och D2, eftersom de har olika returtyper eller parameterlistor. Metoderna B.M5 och B.M6 är båda kompatibla med ombudstypen D3.

slutexempel

Exempel:

delegate bool Predicate<T>(T value);

class X
{
    static bool F(int i) {...}
    static bool G(string s) {...}
}

Metoden X.F är kompatibel med ombudstypen Predicate<int> och metoden X.G är kompatibel med ombudstypen Predicate<string>.

slutexempel

Obs! Den intuitiva innebörden av ombudskompatibilitet är att en metod är kompatibel med en ombudstyp om varje anrop av ombudet kan ersättas med en anrop av metoden utan att bryta mot typsäkerheten, och behandla valfria parametrar och parametermatriser som explicita parametrar. Till exempel i följande kod:

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");
    }
}

Metoden Print är kompatibel med ombudstypen Action<string> eftersom alla anrop av ett Action<string> ombud också skulle vara ett giltigt anrop av Print metoden.

Om signaturen Print för metoden ovan ändrades till till Print(object value, bool prependTimestamp = false) exempel skulle Print metoden inte längre vara kompatibel med Action<string> reglerna i den här satsen.

slutkommentar

20.5 Instansiering av ombud

En instans av ett ombud skapas av en delegate_creation_expression (§12.8.16.6), en konvertering till en delegattyp, delegatkombination eller borttagning av ombud. Den nyligen skapade delegatinstansen refererar sedan till en eller flera av:

  • Den statiska metod som refereras i delegate_creation_expression, eller
  • Målobjektet (som inte kan vara null) och instansmetoden som refereras i delegate_creation_expression, eller
  • En annan delegat (§12.8.16.6).

Exempel:

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
    }
}

slutexempel

Den uppsättning metoder som kapslas in av en ombudsinstans kallas för en anropslista. När en ombudsinstans skapas från en enda metod kapslar den in den metoden, och dess anropslista innehåller bara en post. Men när två icke-delegeradenull instanser kombineras sammanfogas deras anropslistor – i den ordning som vänster operande och höger operand – bildar en ny anropslista som innehåller två eller flera poster.

När ett nytt ombud skapas från en enda delegat har den resulterande anropslistan bara en post, vilket är källdelegaten (§12.8.16.6).

Ombud kombineras med binära + (§12.10.5) och += operatorer (§12.21.4). Ett ombud kan tas bort från en kombination av ombud med hjälp av binärfilen - (§12.10.6) och -= operatorerna (§12.21.4). Ombud kan jämföras för likhet (§12.12.9).

Exempel: I följande exempel visas instansiering av ett antal ombud och deras motsvarande anropslistor:

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.
   }
}

När cd1 och cd2 instansieras kapslar de in en metod. När cd3 instansieras har den en anropslista med två metoder och M1 M2, i den ordningen. cd4"s anropslista innehåller M1, M2och M1, i den ordningen. För cd5innehåller M1listan över anrop , M2, M1, M1och M2, i den ordningen.

När du skapar ett ombud från ett annat ombud med en delegate_creation_expression har resultatet en anropslista med en annan struktur än originalet, men som resulterar i att samma metoder anropas i samma ordning. När td3 skapas från cd3 listan över anrop har bara en medlem, men den medlemmen är en lista över metoderna M1 och M2 dessa metoder anropas av td3 i samma ordning som de anropas av cd3. td4 På samma sätt när instansieras dess anropslista har bara två poster, men den anropar de tre metoderna M1, M2och M1, i den ordningen precis som cd4 gör.

Anropslistans struktur påverkar delegerade subtraktioner. Delegera cd6, som skapas genom att subtrahera cd2 (som anropar M2) från cd4 (som anropar M1, M2och M1) anropar M1 och M1. Ombudet td6, som skapas genom att subtrahera cd2 (som anropar M2) från td4 (som anropar , M2, och M1) anropar M1M1fortfarande , M2 och M1, i den ordningen, eftersom M2 det inte är en enda post i listan utan en medlem i en kapslad lista. Fler exempel på hur du kombinerar (samt tar bort) ombud finns i §20.6.

slutexempel

När en instans har instansierat refererar en delegatinstans alltid till samma anropslista.

Obs! Kom ihåg att när två ombud kombineras, eller en tas bort från en annan, resulterar ett nytt ombud med en egen anropslista. Anropslistorna för de delegater som kombineras eller tas bort förblir oförändrade. slutkommentar

20.6 Delegera anrop

C# innehåller särskild syntax för att anropa ett ombud. När en icke-delegeradnull instans vars anropslista innehåller en post anropas anropas den en metod med samma argument som den angavs och returnerar samma värde som metoden. (Se §12.8.9.4 för detaljerad information om ombudsanrop.) Om ett undantag inträffar under anropet av ett sådant ombud, och undantaget inte fångas inom metoden som anropades, fortsätter sökningen efter en undantagsfångstsats i metoden som kallade ombudet, som om metoden hade anropat den metod som ombudet hänvisade till direkt.

Anrop av en delegatinstans vars anropslista innehåller flera poster fortsätter genom att anropa var och en av metoderna i listan över anrop synkront i ordning. Varje metod som kallas skickas samma uppsättning argument som gavs till delegatinstansen. Om ett sådant ombudsanrop innehåller referensparametrar (§15.6.2.3.3), sker varje metodanrop med en referens till samma variabel. Ändringar i variabeln med en metod i listan över anrop visas för metoder längre ned i listan över anrop. Om ombudsanropet innehåller utdataparametrar eller ett returvärde kommer deras slutliga värde från anropet av det sista ombudet i listan. Om ett undantag inträffar under bearbetningen av anropet av ett sådant ombud, och undantaget inte fångas inom metoden som anropades, fortsätter sökningen efter en undantagsfångstsats i metoden som kallas ombudet, och eventuella metoder längre ned i listan över anrop anropas inte.

Försök att anropa en delegatinstans vars värde resulterar null i ett undantag av typen System.NullReferenceException.

Exempel: I följande exempel visas hur du instansierar, kombinerar, tar bort och anropar ombud:

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
    }
}

Som du ser i -instruktionen cd3 += cd1;kan ett ombud finnas i en anropslista flera gånger. I det här fallet anropas den bara en gång per förekomst. I en anropslista som den här, när ombudet tas bort, är den sista förekomsten i listan över anrop den som faktiskt har tagits bort.

Direkt före körningen av den slutliga instruktionen, cd3 -= cd1;, refererar ombudet cd3 till en tom anropslista. Att försöka ta bort ett ombud från en tom lista (eller ta bort ett icke-befintligt ombud från en icke-tom lista) är inte ett fel.

Utdata som genereras är:

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

slutexempel