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
, internal
och 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
ochD2
ä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
ochM
har samma antal parametrar, och varje parameter iD
har samma parametermodifierare med referens som motsvarande parameter iM
.- 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 iM
. - För varje bireferensparameter är parametertypen i
D
samma som parametertypen iM
. - Något av följande är sant:
D
ochM
är båda returns-no-valueD
ochM
är return-by-value (§15.6.1, §20.2), och det finns en identitet eller implicit referenskonvertering från returtypenM
till returtypen .D
D
ochM
är båda return-by-ref, finns det en identitetskonvertering mellan returtypenM
och returtypenD
, 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
ochB.M1
är kompatibla med både de delegerade typernaD1
ochD2
, eftersom de har samma returtyp och parameterlista. MetodernaB.M2
,B.M3
ochB.M4
är inte kompatibla med de delegerade typernaD1
ochD2
, eftersom de har olika returtyper eller parameterlistor. MetodernaB.M5
ochB.M6
är båda kompatibla med ombudstypenD3
.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 ombudstypenPredicate<int>
och metodenX.G
är kompatibel med ombudstypenPredicate<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
Action<string>
eftersom alla anrop av ettAction<string>
ombud också skulle vara ett giltigt anrop avOm signaturen
Print(object value, bool prependTimestamp = false)
exempel skulleAction<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
ochcd2
instansieras kapslar de in en metod. Närcd3
instansieras har den en anropslista med två metoder ochM1
M2
, i den ordningen.cd4
"s anropslista innehållerM1
,M2
ochM1
, i den ordningen. Förcd5
innehållerM1
listan över anrop ,M2
,M1
,M1
ochM2
, 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åncd3
listan över anrop har bara en medlem, men den medlemmen är en lista över metodernaM1
ochM2
dessa metoder anropas avtd3
i samma ordning som de anropas avcd3
.td4
På samma sätt när instansieras dess anropslista har bara två poster, men den anropar de tre metodernaM1
,M2
ochM1
, i den ordningen precis somcd4
gör.Anropslistans struktur påverkar delegerade subtraktioner. Delegera
cd6
, som skapas genom att subtraheracd2
(som anroparM2
) fråncd4
(som anroparM1
,M2
ochM1
) anroparM1
ochM1
. Ombudettd6
, som skapas genom att subtraheracd2
(som anroparM2
) fråntd4
(som anropar ,M2
, ochM1
) anroparM1
M1
fortfarande ,M2
ochM1
, i den ordningen, eftersomM2
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 ombudetcd3
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
ECMA C# draft specification