Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
20.1 Général
Une déclaration de délégué définit une classe dérivée de la classe System.Delegate
. Une instance de délégué encapsule une liste d’appel, qui répertorie une ou plusieurs méthodes, appelées chacune entité joignable. Par exemple, une entité joignable se compose d’une instance et d’une méthode sur cette instance. Pour les méthodes statiques, une entité joignable ne se compose que d’une méthode. L’appel d’une instance de délégué avec un jeu d’arguments approprié entraîne l’appel de chacune des entités joignables par le délégué avec le jeu d’arguments donné.
Remarque : une propriété intéressante et utile d’une instance de délégué est qu’elle ne connaît pas ou ne s’intéresse pas aux classes des méthodes qu’elle encapsule. Tout ce qui importe est que ces méthodes soient compatibles (§ 20.4) avec le type du délégué. Les délégués sont ainsi parfaitement adaptés à l’appel « anonyme ». fin de la remarque
20.2 Déclarations de délégués
Une déclaration de délégué, ou delegate_declaration, est une déclaration de type, ou type_declaration, (§ 14.7) qui déclare un nouveau type de délégué.
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 est défini dans § 23.2.
L’affichage du même modificateur plusieurs fois dans une déclaration de délégué est une erreur de compilation.
Une déclaration de délégué qui fournit un objet variant_type_parameter_list est une déclaration de délégué générique. En outre, tout délégué imbriqué dans une déclaration de classe générique ou une déclaration de struct générique est lui-même une déclaration de délégué générique, car les arguments de type pour le type conteneur doivent être fournis pour créer un type construit (§ 8.4).
Le modificateur new
n’est autorisé que sur les délégués déclarés dans un autre type, auquel cas il spécifie que ce délégué masque un membre hérité du même nom, comme décrit dans le § 15.3.5.
Les modificateurs public
, protected
, internal
et private
contrôlent l’accessibilité du type de délégué. Selon le contexte dans lequel la déclaration de délégué a lieu, certains de ces modificateurs peuvent ne pas être autorisés (§ 7.5.2).
Le nom du type du délégué est l’identificateur.
Comme pour les méthodes (§ 15.6.1), si ref
est présent, la méthode de délégué returns-by-ref est utilisée. Sinon, si return_type est void
, la méthode de délégué returns-no-value est utilisée. Sinon, la méthode de délégué returns-by-value est utilisée.
L’attribut parameter_list facultatif spécifie les paramètres du délégué.
L'attribut return_type d’une déclaration de délégué returns-by-value ou returns-no-value spécifie le type du résultat, le cas échéant, retourné par le délégué.
L'attribut ref_return_type d’une déclaration de délégué returns-by-ref spécifie le type de la variable référencée par l'attribut variable_reference (§ 9.5) retourné par le délégué.
L'attribut facultatif variant_type_parameter_list (§ 18.2.3) spécifie les paramètres de type pour le délégué lui-même.
Le type de retour d’un type de délégué doit être void
ou sécurisé en sortie (§ 18.2.3.2).
Tous les types de paramètres d’un type de délégué doivent être input-safe (§ 18.2.3.2). En outre, tous les types de paramètres de sortie ou de référence doivent également être sécurisés en sortie.
Remarque : les paramètres de sortie doivent être sécurisés en entée en raison de restrictions d’implémentation courantes. fin de la remarque
En outre, chaque contrainte de type de classe, de type d’interface et de paramètre de type appliquée à un paramètre de type d’un délégué doit être sécurisée en entrée.
Les types de délégués en C# ont des noms équivalents, mais ne sont pas équivalents structurellement.
Exemple :
delegate int D1(int i, double d); delegate int D2(int c, double d);
Les types de délégués
D1
etD2
sont différents et ne sont donc pas interchangeables, malgré leurs signatures identiques.exemple de fin
Comme d’autres déclarations de type générique, les arguments de type doivent être donnés pour créer un type de délégué construit. Les types de paramètres et le type de retour d’un type de délégué construit sont déterminés en substituant, pour chaque paramètre de type défini dans la déclaration du délégué, l’argument de type correspondant du type de délégué construit.
La seule façon de déclarer un type de délégué est via un delegate_declaration. Chaque type de délégué est un type de référence dérivé de System.Delegate
. Les membres requis pour chaque type de délégué sont détaillés dans le § 20.3. Les types de délégués sont implicitement sealed
, il n’est donc pas possible de dériver un type à partir d'un type de délégué. Il n’est pas non plus possible de déclarer un type de classe non délégué dérivé de System.Delegate
.
System.Delegate
n’est en soi un type de délégué ; il s’agit d’un type de classe à partir duquel tous les types de délégués sont dérivés.
20.3 Membres délégués
Chaque type de délégué hérite des membres de la classe Delegate
, comme décrit dans le § 15.3.4. En outre, chaque type de délégué doit fournir une méthode Invoke
non générique dont la liste de paramètres correspond à celle spécifiée dans la déclaration du délégué (parameter_list) dont le type de retour correspond à l'attribut return_type ou ref_return_type de cette déclaration, et, pour les délégués returns-by-ref, dont l'attribut ref_kind correspond également à celui indiqué dans la déclaration. La méthode Invoke
doit être au moins aussi accessible que le type de délégué qui la contient. Appeler la méthode Invoke
sur un type de délégué équivaut sémantiquement à utiliser la syntaxe d’appel de délégué (§ 20.6) .
Les implémentations peuvent définir des membres supplémentaires dans le type de délégué.
À l’exception de l’instanciation, toute opération qui peut être appliquée à une classe ou une instance de classe peut également l'être à une classe ou une instance déléguée, respectivement. En particulier, il est possible d’accéder aux membres du type System.Delegate
via la syntaxe d’accès aux membres habituelle.
20.4 Compatibilité des délégués
Une méthode ou un type de délégué M
est compatible avec un type de délégué D
si toutes les conditions suivantes sont remplies :
-
D
etM
ont le même nombre de paramètres, et chaque paramètre dansD
a le même modificateur de paramètre par référence que le paramètre correspondant dansM
. - Pour chaque paramètre de valeur, il existe une conversion d’identité (§ 10.2.2) ou une conversion de référence implicite (§ 10.2.8) entre le type de paramètre dans
D
et le type de paramètre correspondant dansM
. - Pour chaque paramètre par référence, le type de paramètre dans
D
est identique au type de paramètre dansM
. - Une des conditions suivantes est remplie :
-
D
etM
sont tous deux de type returns-no-value. -
D
etM
sont des returns-by-value (§ 15.6.1, § 20.2), et il existe une conversion d'identité ou une conversion de référence implicite entre le type de retour deM
et le type de retour deD
. -
D
etM
sont des returns-by-ref, il existe une conversion d'identité entre le type de retour deM
et le type de retour deD
, et les deux ont le même ref_kind.
-
Cette définition de la compatibilité autorise la covariance dans le type de retour et la contravariance dans les types de paramètres.
Exemple :
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) {...} }
Les méthodes
A.M1
etB.M1
sont compatibles avec les deux types de déléguésD1
etD2
, car elles ont le même type de retour et la même liste de paramètres. Les méthodesB.M2
,B.M3
etB.M4
sont incompatibles avec les types de déléguésD1
etD2
, car elles ont des types de retour ou des listes de paramètres différents. Les méthodesB.M5
etB.M6
sont compatibles avec le type de déléguéD3
.exemple de fin
Exemple :
delegate bool Predicate<T>(T value); class X { static bool F(int i) {...} static bool G(string s) {...} }
La méthode
X.F
est compatible avec le type de déléguéPredicate<int>
, et la méthodeX.G
est compatible avec le type de déléguéPredicate<string>
.exemple de fin
Remarque : la compatibilité des délégués signifie intuitivement qu'une méthode est compatible avec un type de délégué si chaque appel du délégué peut être remplacée par un appel de la méthode sans enfreindre la sécurité des types, en traitant les paramètres facultatifs et les tableaux de paramètres comme des paramètres explicites. Par exemple, dans le code suivant :
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"); } }
La méthode
Action<string>
, car tout appel d’un déléguéAction<string>
serait également un appel valide de la méthodeSi la signature de la méthode
Print(object value, bool prependTimestamp = false)
, la méthodeAction<string>
conformément aux règles de cette sous-clause.fin de la remarque
20.5 Instanciation des délégués
Une instance d’un délégué est créée par un delegate_creation_expression (§12.8.17.5), une conversion en type délégué, combinaison de délégués ou suppression de délégué. La nouvelle instance de délégué fait alors référence à un ou plusieurs des éléments suivants :
- La méthode statique référencée dans l'expression delegate_creation_expression, ou
- L'objet cible (qui ne peut pas être
null
) et la méthode d’instance référencés dans l'expression delegate_creation_expression, ou - Autre délégué (§12.8.17.5).
Exemple :
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 } }
exemple de fin
L’ensemble de méthodes encapsulées par une instance de délégué est appelé liste d'appel. Lorsqu’une instance de délégué est créée à partir d’une seule méthode, elle encapsule cette méthode et sa liste d’appel ne contient qu’une seule entrée. Toutefois, lorsque deux instances de délégués non-null
sont combinées, leurs listes d’appel sont concaténées (dans l’ordre suivant : opérande de gauche, puis opérande de droite) pour former une nouvelle liste d’appel, qui contient deux entrées ou plus.
Lorsqu’un délégué est créé à partir d’un seul délégué, la liste d’appel résultante n’a qu’une seule entrée, qui est le délégué source (§12.8.17.5).
Les délégués sont combinés à l’aide des opérateurs binaires +
(§ 12.10.5) et +=
(§ 12.21.4). Un délégué peut être supprimé d’une combinaison de délégués à l’aide des opérateurs binaires -
(§ 12.10.6) et -=
(§ 12.21.4). Les délégués peuvent être comparés pour vérifier leur égalité (§ 12.12.9).
Exemple : l’exemple suivant montre l’instanciation d’un certain nombre de délégués et leurs listes d’appel correspondantes
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. } }
Lorsque
cd1
etcd2
sont instanciés, ils encapsulent chacun une méthode. Lorsquecd3
est instancié, il dispose d'une liste d'appel de deux méthodes,M1
etM2
, dans cet ordre. La liste d'appel decd4
contientM1
,M2
etM1
, dans cet ordre. Pourcd5
, la liste d’appel contientM1
,M2
,M1
,M1
etM2
, dans cet ordre.Lorsque vous créez un délégué à partir d'un autre délégué avec une expression delegate_creation_expression, le résultat est une liste d'appels avec une structure différente de l'originale, mais qui aboutit aux mêmes méthodes appelées dans le même ordre. Lorsque
td3
est créé à partir decd3
, sa liste d'appel ne comporte qu'un seul membre, mais ce membre est une liste des méthodesM1
etM2
, et ces méthodes sont appelées partd3
dans le même ordre que celles appelées parcd3
. De même, lorsquetd4
est instancié, sa liste d'appels ne comporte que deux entrées, mais il appelle les trois méthodesM1
,M2
etM1
, dans cet ordre, tout commecd4
.La structure de la liste d’appel affecte la soustraction des délégués. Le délégué
cd6
, créé en soustrayantcd2
(qui appelleM2
) decd4
(qui appelleM1
,M2
etM1
), appelleM1
etM1
. Toutefois, le déléguétd6
, créé en soustrayantcd2
(qui appelleM2
) detd4
(qui appelleM1
,M2
etM1
), appelle toujoursM1
,M2
etM1
, dans cet ordre, carM2
entrée unique dans la liste, mais un membre d'une liste imbriquée. Pour obtenir d’autres exemples de combinaison (ainsi que de suppression) de délégués, consultez le § 20.6.exemple de fin
Une fois instanciée, une instance de délégué fait toujours référence à la même liste d’appel.
Remarque : n'oubliez pas que lorsque deux délégués sont combinés ou qu'un délégué est supprimé d'un autre, un nouveau délégué est créé avec sa propre liste d'appel ; les listes d'appel des délégués combinés ou supprimés restent inchangées. fin de la remarque
20.6 Appel de délégué
C# fournit une syntaxe spéciale pour appeler un délégué. Lorsqu'une instance de délégué non-null
dont la liste d'invocation contient une entrée est appelée, elle appelle la méthode unique avec les mêmes arguments qui lui ont été donnés et retourne la même valeur que la méthode référencée. (Consultez §12.8.10.4 pour obtenir des informations détaillées sur l’appel de délégué.) Si une exception se produit pendant l’appel d’un tel délégué et que cette exception n’est pas interceptée dans la méthode appelée, la recherche d’une clause catch d’exception se poursuit dans la méthode qui a appelé le délégué, comme si cette méthode avait directement appelé la méthode à laquelle ce délégué a fait référence.
L’appel d’une instance de délégué dont la liste d’appel contient plusieurs entrées se poursuit en appelant chacune des méthodes de la liste d’appel de manière synchrone, dans l’ordre. Chaque méthode ainsi nommée reçoit le même ensemble d'arguments que celui qui a été fourni à l'instance déléguée. Si cet appel de délégué inclut des paramètres de référence (§ 15.6.2.3.3), chaque appel de méthode se produira avec une référence à la même variable ; les modifications apportées à cette variable par une méthode dans la liste d'appel seront visibles pour les méthodes situées plus bas dans la liste d'appel. Si l’appel de délégué inclut des paramètres de sortie ou une valeur de retour, leur valeur finale provient de l’appel du dernier délégué de la liste. Si une exception se produit pendant le traitement de cet appel de délégué et que cette exception n’est pas interceptée dans la méthode appelée, la recherche d'une clause d'interception d'exception se poursuit dans la méthode qui a appelé le délégué, et aucune autre méthode située plus bas dans la liste d'appel n'est appelée.
Toute tentative d’appel d’une instance de délégué dont la valeur est null
entraîne une exception de type System.NullReferenceException
.
Exemple : l’exemple suivant montre comment instancier, combiner, supprimer et appeler des délégués.
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 } }
Comme indiqué dans l’instruction
cd3 += cd1;
, un délégué peut apparaître plusieurs fois dans une liste d’appel. Dans ce cas, il est simplement appelé une fois par occurrence. Dans une liste d'appel telle que celle-ci, lorsque ce délégué est supprimé, c'est la dernière occurrence dans la liste d'appel qui est réellement supprimée.Immédiatement avant l’exécution de l’instruction finale,
cd3 -= cd1
, le déléguécd3
fait référence à une liste d’appel vide. Tenter de supprimer un délégué d'une liste vide (ou de supprimer un délégué inexistant d'une liste non vide) n'est pas une erreur.La sortie produite est la suivante :
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
exemple de fin
ECMA C# draft specification