18 interfaces
18.1 Général
Une interface définit un contrat. Une classe ou un struct qui implémente une interface doit respecter son contrat. Une interface peut hériter de plusieurs interfaces de base, et une classe ou un struct peut implémenter plusieurs interfaces.
Les interfaces peuvent contenir des méthodes, des propriétés, des événements et des indexeurs. L’interface elle-même ne fournit pas d’implémentations pour les membres qu’elle déclare. L’interface spécifie simplement les membres qui doivent être fournis par des classes ou des structs qui implémentent l’interface.
Déclarations d’interface 18.2
18.2.1 Général
Un interface_declaration est un type_declaration (§14.7) qui déclare un nouveau type d’interface.
interface_declaration
: attributes? interface_modifier* 'partial'? 'interface'
identifier variant_type_parameter_list? interface_base?
type_parameter_constraints_clause* interface_body ';'?
;
Un interface_declaration se compose d’un ensemble facultatif d’attributs(§22), suivi d’un ensemble facultatif de interface_modifiers (§18.2.2), suivi d’un modificateur partiel facultatif (§15.2.7), suivi du mot clé interface
et d’un identificateur qui nomme l’interface, suivi d’une spécification variant_type_parameter_list facultative (§18.2.3), suivi d’un interface_base facultatif spécification (§18.2.4), suivie d’une spécification facultative de type_parameter_constraints_clause(§15.2.5), suivie d’un interface_body (§18.3), éventuellement suivie d’un point-virgule.
Une déclaration d’interface ne fournit pas de type_parameter_constraints_clauses, sauf s’il fournit également un variant_type_parameter_list.
Une déclaration d’interface qui fournit un variant_type_parameter_list est une déclaration d’interface générique. En outre, toute interface imbriquée à l’intérieur d’une déclaration de classe générique ou d’une déclaration de struct générique est elle-même une déclaration d’interface générique, car les arguments de type pour le type conteneur doivent être fournis pour créer un type construit (§8.4).
Modificateurs d’interface 18.2.2
Une interface_declaration peut éventuellement inclure une séquence de modificateurs d’interface :
interface_modifier
: 'new'
| 'public'
| 'protected'
| 'internal'
| 'private'
| unsafe_modifier // unsafe code support
;
unsafe_modifier (§23.2) est disponible uniquement dans le code non sécurisé (§23).
Il s’agit d’une erreur au moment de la compilation pour que le même modificateur apparaisse plusieurs fois dans une déclaration d’interface.
Le new
modificateur est autorisé uniquement sur les interfaces définies dans une classe. Il spécifie que l’interface masque un membre hérité du même nom, comme décrit dans le §15.3.5.
Les public
modificateurs et protected
internal
les modificateurs private
contrôlent l’accessibilité de l’interface. Selon le contexte dans lequel la déclaration d’interface se produit, seuls certains de ces modificateurs peuvent être autorisés (§7.5.2). Lorsqu’une déclaration de type partielle (§15.2.7) inclut une spécification d’accessibilité (via les public
modificateurs, etc protected
internal
private
.), les règles de l’article 15.2.2 s’appliquent.
18.2.3 Listes de paramètres de type variant
18.2.3.1 Général
Les listes de paramètres de type variant peuvent uniquement se produire sur les types d’interface et de délégué. La différence par rapport aux type_parameter_list ordinaires est la variance_annotation facultative sur chaque paramètre detype.
variant_type_parameter_list
: '<' variant_type_parameters '>'
;
variant_type_parameters
: attributes? variance_annotation? type_parameter
| variant_type_parameters ',' attributes? variance_annotation?
type_parameter
;
variance_annotation
: 'in'
| 'out'
;
Si l’annotation de variance est out
, le paramètre de type est dit covariant. Si l’annotation de variance est in
, le paramètre de type est dit contravariant. S’il n’existe aucune annotation de variance, le paramètre de type est dit invariant.
Exemple : Dans les éléments suivants :
interface C<out X, in Y, Z> { X M(Y y); Z P { get; set; } }
X
est covariant,Y
est contravariant etZ
est invariant.exemple de fin
Si une interface générique est déclarée dans plusieurs parties (§15.2.3), chaque déclaration partielle doit spécifier la même variance pour chaque paramètre de type.
18.2.3.2 Sécurité de variance
L’occurrence d’annotations de variance dans la liste de paramètres de type d’un type limite les emplacements où les types peuvent se produire dans la déclaration de type.
Un type T est non sécurisé en sortie si l’un des éléments suivants contient :
T
est un paramètre de type contravariantT
est un type de tableau avec un type d’élément non sécurisé de sortieT
est un typeSᵢ,... Aₑ
d’interface ou délégué construit à partir d’un typeS<Xᵢ, ... Xₑ>
générique dans lequel au moins uneAᵢ
des conservations suivantes :Xᵢ
est covariant ou invariant etAᵢ
est non sécurisé en sortie.Xᵢ
est contravariant ou invariant etAᵢ
est non sécurisé en entrée.
Un type T est non sécurisé en entrée si l’un des éléments suivants contient :
T
est un paramètre de type covariantT
est un type de tableau avec un type d’élément non sécurisé d’entréeT
est un typeS<Aᵢ,... Aₑ>
d’interface ou délégué construit à partir d’un typeS<Xᵢ, ... Xₑ>
générique dans lequel au moins uneAᵢ
des conservations suivantes :Xᵢ
est covariant ou invariant etAᵢ
est non sécurisé en entrée.Xᵢ
est contravariant ou invariant etAᵢ
est non sécurisé en sortie.
Intuitivement, un type non sécurisé de sortie est interdit dans une position de sortie et un type d’entrée non sécurisé est interdit dans une position d’entrée.
Un type est output-safe s’il n’est pas non sécurisé en sortie, et input-safe s’il n’est pas non sécurisé en entrée.
18.2.3.3 Conversion de variance
L’objectif des annotations de variance est de fournir des conversions plus lénientes (mais toujours sécurisées) en types d’interface et de délégué. À cette fin, les définitions des conversions implicites (§10.2) et explicites (§10.3) utilisent la notion de convertibilité de variance, qui est définie comme suit :
Un type T<Aᵢ, ..., Aᵥ>
est convertible en variance-convertible en type T<Bᵢ, ..., Bᵥ>
s’il T
s’agit d’une interface ou d’un type délégué déclaré avec les paramètres T<Xᵢ, ..., Xᵥ>
de type variant, et pour chaque paramètre Xᵢ
de type variant l’un des éléments suivants :
Xᵢ
est covariant et une référence implicite ou une conversion d’identité existe deAᵢ
àBᵢ
Xᵢ
est contravariant et une référence implicite ou une conversion d’identité existe deBᵢ
àAᵢ
Xᵢ
est invariant et une conversion d’identité existe deAᵢ
àBᵢ
Interfaces de base 18.2.4
Une interface peut hériter de zéro ou plusieurs types d’interface, appelés interfaces de base explicites de l’interface. Lorsqu’une interface a une ou plusieurs interfaces de base explicites, puis dans la déclaration de cette interface, l’identificateur d’interface est suivi d’un signe deux-points et d’une liste séparée par des virgules des types d’interface de base.
interface_base
: ':' interface_type_list
;
Les interfaces de base explicites peuvent être construites de types d’interface (§8.4, §18.2). Une interface de base ne peut pas être un paramètre de type seul, bien qu’elle puisse impliquer les paramètres de type qui se trouvent dans l’étendue.
Pour un type d’interface construit, les interfaces de base explicites sont formées en prenant les déclarations d’interface de base explicites sur la déclaration de type générique et en remplaçant, pour chaque type_parameter dans la déclaration d’interface de base, le type_argument correspondant du type construit.
Les interfaces de base explicites d’une interface doivent être au moins aussi accessibles que l’interface elle-même (§7.5.5).
Remarque : Par exemple, il s’agit d’une erreur au moment de la compilation pour spécifier une ou
private
uneinternal
interface dans la interface_base d’unepublic
interface. Note de fin
Il s’agit d’une erreur au moment de la compilation pour qu’une interface hérite directement ou indirectement d’elle-même.
Les interfaces de base d’une interface sont les interfaces de base explicites et leurs interfaces de base. En d’autres termes, l’ensemble d’interfaces de base est la fermeture transitive complète des interfaces de base explicites, leurs interfaces de base explicites, et ainsi de suite. Une interface hérite de tous les membres de ses interfaces de base.
Exemple : dans le code suivant
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } interface IComboBox: ITextBox, IListBox {}
les interfaces de base de
IComboBox
sontIControl
,ITextBox
etIListBox
. En d’autres termes, l’interfaceIComboBox
ci-dessus hérite des membresSetText
etSetItems
ainsi quePaint
.exemple de fin
Les membres hérités d’un type générique construit sont hérités après la substitution de type. Autrement dit, tous les types constituants du membre ont les paramètres de type de la déclaration de classe de base remplacés par les arguments de type correspondants utilisés dans la spécification class_base .
Exemple : dans le code suivant
interface IBase<T> { T[] Combine(T a, T b); } interface IDerived : IBase<string[,]> { // Inherited: string[][,] Combine(string[,] a, string[,] b); }
l’interface
IDerived
hérite de laCombine
méthode après le remplacement du paramètreT
de type parstring[,]
.exemple de fin
Classe ou struct qui implémente une interface implémente également implicitement toutes les interfaces de base de l’interface.
La gestion des interfaces sur plusieurs parties d’une déclaration d’interface partielle (§15.2.7) est abordée plus loin dans le §15.2.4.3.
Chaque interface de base d’une interface doit être sécurisée par sortie (§18.2.3.2).
Corps de l’interface 18.3
La interface_body d’une interface définit les membres de l’interface.
interface_body
: '{' interface_member_declaration* '}'
;
18.4 Membres de l’interface
18.4.1 Général
Les membres d’une interface sont les membres hérités des interfaces de base et les membres déclarés par l’interface elle-même.
interface_member_declaration
: interface_method_declaration
| interface_property_declaration
| interface_event_declaration
| interface_indexer_declaration
;
Une déclaration d’interface déclare zéro ou plusieurs membres. Les membres d’une interface doivent être des méthodes, des propriétés, des événements ou des indexeurs. Une interface ne peut pas contenir de constantes, de champs, d’opérateurs, de constructeurs d’instances, de finaliseurs ou de types, ni d’interface ne peut contenir de membres statiques de tout type.
Tous les membres de l’interface ont implicitement un accès public. Il s’agit d’une erreur au moment de la compilation pour que les déclarations de membre d’interface incluent tous les modificateurs.
Un interface_declaration crée un espace de déclaration (§7.3) et les paramètres de type et les interface_member_declarationimmédiatement contenus par l’interface_declaration introduisent de nouveaux membres dans cet espace de déclaration. Les règles suivantes s’appliquent aux interface_member_declarations :
- Le nom d’un paramètre de type dans la variant_type_parameter_list d’une déclaration d’interface diffère des noms de tous les autres paramètres de type dans la même variant_type_parameter_list et diffère des noms de tous les membres de l’interface.
- Le nom d’une méthode diffère des noms de toutes les propriétés et événements déclarés dans la même interface. En outre, la signature (§7.6) d’une méthode diffère des signatures de toutes les autres méthodes déclarées dans la même interface, et deux méthodes déclarées dans la même interface ne doivent pas avoir de signatures qui diffèrent uniquement par
in
,out
etref
. - Le nom d’une propriété ou d’un événement diffère des noms de tous les autres membres déclarés dans la même interface.
- La signature d’un indexeur diffère des signatures de tous les autres indexeurs déclarés dans la même interface.
Les membres hérités d’une interface ne font spécifiquement pas partie de l’espace de déclaration de l’interface. Par conséquent, une interface est autorisée à déclarer un membre portant le même nom ou la même signature qu’un membre hérité. Lorsque cela se produit, le membre d’interface dérivé est dit pour masquer le membre de l’interface de base. Le masquage d’un membre hérité n’est pas considéré comme une erreur, mais il provoque l’émission d’un avertissement par un compilateur. Pour supprimer l’avertissement, la déclaration du membre d’interface dérivé doit inclure un new
modificateur pour indiquer que le membre dérivé est destiné à masquer le membre de base. Cette rubrique est abordée plus loin dans le §7.7.2.3.
Si un new
modificateur est inclus dans une déclaration qui ne masque pas un membre hérité, un avertissement est émis à cet effet. Cet avertissement est supprimé en supprimant le new
modificateur.
Remarque : Les membres de la classe
object
ne sont pas, strictement parlant, membres d’une interface (§18.4). Toutefois, les membres de la classeobject
sont disponibles via la recherche de membre dans n’importe quel type d’interface (§12.5). Note de fin
L’ensemble de membres d’une interface déclarée dans plusieurs parties (§15.2.7) est l’union des membres déclarés dans chaque partie. Les corps de toutes les parties de la déclaration d’interface partagent le même espace de déclaration (§7.3) et l’étendue de chaque membre (§7.7) s’étend aux corps de toutes les parties.
Méthodes d’interface 18.4.2
Les méthodes d’interface sont déclarées à l’aide de interface_method_declarations :
interface_method_declaration
: attributes? 'new'? return_type interface_method_header
| attributes? 'new'? ref_kind ref_return_type interface_method_header
;
interface_method_header
: identifier '(' parameter_list? ')' ';'
| identifier type_parameter_list '(' parameter_list? ')'
type_parameter_constraints_clause* ';'
;
Les attributs, return_type, ref_return_type, identificateur et parameter_list d’une déclaration de méthode d’interface ont la même signification que celles d’une déclaration de méthode dans une classe (§15.6). Une déclaration de méthode d’interface n’est pas autorisée à spécifier un corps de méthode et la déclaration se termine donc toujours par un point-virgule.
Tous les types de paramètres d’une méthode d’interface doivent être input-safe (§18.2.3.2), et le type de retour doit être soit void
sécurisé par sortie. En outre, tous les types de paramètres de sortie ou de référence doivent également être sécurisés.
Remarque : Les paramètres de sortie doivent être sécurisés en entrée en raison de restrictions d’implémentation courantes. Note de fin
En outre, chaque contrainte de type de classe, contrainte de type d’interface et contrainte de paramètre de type sur tous les paramètres de type de la méthode doit être entrée-safe.
En outre, chaque contrainte de type de classe, contrainte de type d’interface et contrainte de paramètre de type sur n’importe quel paramètre de type de la méthode doit être entrée-safe.
Ces règles garantissent que toute utilisation covariante ou contravariante de l’interface reste typesafe.
Exemple :
interface I<out T> { void M<U>() where U : T; // Error }
n’est pas formé, car l’utilisation de la contrainte de
T
paramètre de type surU
n’est pas sécurisée en entrée.Cette restriction n’était-elle pas en place, il serait possible de violer la sécurité de type de la manière suivante :
class B {} class D : B {} class E : B {} class C : I<D> { public void M<U>() {...} } ... I<B> b = new C(); b.M<E>();
C’est en fait un appel à
C.M<E>
. Mais cet appel exige que celaE
dériveD
de , de sorte que la sécurité de type serait violée ici.exemple de fin
Propriétés de l’interface 18.4.3
Les propriétés de l’interface sont déclarées à l’aide de interface_property_declarations :
interface_property_declaration
: attributes? 'new'? type identifier '{' interface_accessors '}'
| attributes? 'new'? ref_kind type identifier '{' ref_interface_accessor '}'
;
interface_accessors
: attributes? 'get' ';'
| attributes? 'set' ';'
| attributes? 'get' ';' attributes? 'set' ';'
| attributes? 'set' ';' attributes? 'get' ';'
;
ref_interface_accessor
: attributes? 'get' ';'
;
Les attributs, le type et l’identificateur d’une déclaration de propriété d’interface ont la même signification que ceux d’une déclaration de propriété dans une classe (§15.7).
Les accesseurs d’une déclaration de propriété d’interface correspondent aux accesseurs d’une déclaration de propriété de classe (§15.7.3), sauf que le accessor_body doit toujours être un point-virgule. Ainsi, les accesseurs indiquent simplement si la propriété est en lecture-écriture, en lecture seule ou en écriture seule.
Le type d’une propriété d’interface doit être sécurisé en sortie s’il existe un accesseur get et doit être sécurisé en entrée s’il existe un accesseur défini.
Événements d’interface 18.4.4
Les événements d’interface sont déclarés à l’aide de interface_event_declarations :
interface_event_declaration
: attributes? 'new'? 'event' type identifier ';'
;
Les attributs, le type et l’identificateur d’une déclaration d’événement d’interface ont la même signification que ceux d’une déclaration d’événement dans une classe (§15.8).
Le type d’un événement d’interface doit être sécurisé en entrée.
Indexeurs d’interface 18.4.5
Les indexeurs d’interface sont déclarés à l’aide de interface_indexer_declarations :
interface_indexer_declaration
: attributes? 'new'? type 'this' '[' parameter_list ']'
'{' interface_accessors '}'
| attributes? 'new'? ref_kind type 'this' '[' parameter_list ']'
'{' ref_interface_accessor '}'
;
Les attributs, les types et les parameter_list d’une déclaration d’indexeur d’interface ont la même signification que celles d’une déclaration d’indexeur dans une classe (§15.9).
Les accesseurs d’une déclaration d’indexeur d’interface correspondent aux accesseurs d’une déclaration d’indexeur de classe (§15.9), sauf que le accessor_body doit toujours être un point-virgule. Ainsi, les accesseurs indiquent simplement si l’indexeur est en lecture-écriture, en lecture seule ou en écriture seule.
Tous les types de paramètres d’un indexeur d’interface 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.
Remarque : Les paramètres de sortie doivent être sécurisés en entrée en raison de restrictions d’implémentation courantes. Note de fin
Le type d’un indexeur d’interface doit être sécurisé en sortie s’il existe un accesseur get et doit être sécurisé en entrée s’il existe un accesseur défini.
Accès aux membres de l’interface 18.4.6
Les membres de l’interface sont accessibles via l’accès aux membres (§12.8.7) et l’accès à l’indexeur (§12.8.12.3) du formulaire I.M
et I[A]
, où I
est un type d’interface, M
est une méthode, une propriété ou un événement de ce type d’interface, et A
est une liste d’arguments d’indexeur.
Pour les interfaces strictement à héritage unique (chaque interface de la chaîne d’héritage a exactement zéro ou une interface de base directe), les effets de la recherche membre (§12.5), l’appel de méthode (§12.8.10.2), et l’accès de l’indexeur (§12.8.12.3) sont exactement les mêmes que pour les classes et les structs : les membres dérivés masquent les membres moins dérivés portant le même nom ou la même signature. Toutefois, pour les interfaces d’héritage multiple, des ambiguïtés peuvent se produire lorsque deux interfaces de base ou plus non liées déclarent des membres portant le même nom ou la même signature. Cette sous-clause présente plusieurs exemples, dont certains entraînent des ambiguïtés et d’autres qui ne le font pas. Dans tous les cas, des casts explicites peuvent être utilisés pour résoudre les ambiguïtés.
Exemple : dans le code suivant
interface IList { int Count { get; set; } } interface ICounter { void Count(int i); } interface IListCounter : IList, ICounter {} class C { void Test(IListCounter x) { x.Count(1); // Error x.Count = 1; // Error ((IList)x).Count = 1; // Ok, invokes IList.Count.set ((ICounter)x).Count(1); // Ok, invokes ICounter.Count } }
les deux premières instructions provoquent des erreurs au moment de la compilation, car la recherche membre (§12.5) est
Count
IListCounter
ambiguë. Comme illustré par l’exemple, l’ambiguïté est résolue en cas de conversionx
en type d’interface de base approprié. Ces casts n’ont pas de coûts d’exécution : ils se composent simplement de l’affichage de l’instance comme un type moins dérivé au moment de la compilation.exemple de fin
Exemple : dans le code suivant
interface IInteger { void Add(int i); } interface IDouble { void Add(double d); } interface INumber : IInteger, IDouble {} class C { void Test(INumber n) { n.Add(1); // Invokes IInteger.Add n.Add(1.0); // Only IDouble.Add is applicable ((IInteger)n).Add(1); // Only IInteger.Add is a candidate ((IDouble)n).Add(1); // Only IDouble.Add is a candidate } }
l’appel
n.Add(1)
sélectionne en appliquant desIInteger.Add
règles de résolution de surcharge de §12.6.4. De même, l’appeln.Add(1.0)
sélectionneIDouble.Add
. Lorsque des casts explicites sont insérés, il n’existe qu’une seule méthode candidate, et donc aucune ambiguïté.exemple de fin
Exemple : dans le code suivant
interface IBase { void F(int i); } interface ILeft : IBase { new void F(int i); } interface IRight : IBase { void G(); } interface IDerived : ILeft, IRight {} class A { void Test(IDerived d) { d.F(1); // Invokes ILeft.F ((IBase)d).F(1); // Invokes IBase.F ((ILeft)d).F(1); // Invokes ILeft.F ((IRight)d).F(1); // Invokes IBase.F } }
le
IBase.F
membre est masqué par leILeft.F
membre. L’appeld.F(1)
sélectionneILeft.F
donc , même s’ilIBase.F
semble ne pas être masqué dans le chemin d’accès qui mène à traversIRight
.La règle intuitive de masquage dans les interfaces d’héritage multiple est simplement la suivante : si un membre est masqué dans n’importe quel chemin d’accès, il est masqué dans tous les chemins d’accès. Étant donné que le chemin d’accès
IDerived
à masquerILeft
IBase
IBase.F
, le membre est également masqué dans le chemin d’accèsIDerived
de vers .IRight
IBase
exemple de fin
18.5 Noms de membres d’interface qualifiés
Un membre d’interface est parfois référencé par son nom de membre d’interface qualifié. Le nom qualifié d’un membre d’interface se compose du nom de l’interface dans laquelle le membre est déclaré, suivi d’un point, suivi du nom du membre. Le nom qualifié d’un membre fait référence à l’interface dans laquelle le membre est déclaré.
Exemple : compte tenu des déclarations
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); }
le nom qualifié est
Paint
IControl.Paint
et le nom qualifié de SetText estITextBox.SetText
. Dans l’exemple ci-dessus, il n’est pas possible de faire référence àPaint
ITextBox.Paint
.exemple de fin
Lorsqu’une interface fait partie d’un espace de noms, un nom de membre d’interface qualifié peut inclure le nom de l’espace de noms.
Exemple :
namespace System { public interface ICloneable { object Clone(); } }
Dans l’espace
System
de noms, les noms des membres d’interface qualifiés sontICloneable.Clone
tous les deuxSystem.ICloneable.Clone
pour laClone
méthode.exemple de fin
Implémentations d’interface 18.6
18.6.1 Général
Les interfaces peuvent être implémentées par des classes et des structs. Pour indiquer qu’une classe ou un struct implémente directement une interface, l’interface est incluse dans la liste de classes de base de la classe ou du struct.
Exemple :
interface ICloneable { object Clone(); } interface IComparable { int CompareTo(object other); } class ListEntry : ICloneable, IComparable { public object Clone() {...} public int CompareTo(object other) {...} }
exemple de fin
Classe ou struct qui implémente directement une interface implémente implicitement toutes les interfaces de base de l’interface. Cela est vrai même si la classe ou le struct ne répertorie pas explicitement toutes les interfaces de base de la liste de classes de base.
Exemple :
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { public void Paint() {...} public void SetText(string text) {...} }
Ici, la classe
TextBox
implémente à la foisIControl
etITextBox
.exemple de fin
Lorsqu’une classe C
implémente directement une interface, toutes les classes dérivées de C
l’interface implémentent également implicitement l’interface.
Les interfaces de base spécifiées dans une déclaration de classe peuvent être construites de types d’interface (§8.4, §18.2).
Exemple : Le code suivant illustre comment une classe peut implémenter des types d’interface construits :
class C<U, V> {} interface I1<V> {} class D : C<string, int>, I1<string> {} class E<T> : C<int, T>, I1<T> {}
exemple de fin
Les interfaces de base d’une déclaration de classe générique répondent à la règle d’unicité décrite dans le §18.6.3.
18.6.2 Implémentations de membres d’interface explicites
À des fins d’implémentation d’interfaces, une classe ou un struct peut déclarer des implémentations de membres d’interface explicites. Une implémentation de membre d’interface explicite est une méthode, une propriété, un événement ou une déclaration d’indexeur qui fait référence à un nom de membre d’interface qualifié.
Exemple :
interface IList<T> { T[] GetElements(); } interface IDictionary<K, V> { V this[K key] { get; } void Add(K key, V value); } class List<T> : IList<T>, IDictionary<int, T> { public T[] GetElements() {...} T IDictionary<int, T>.this[int index] {...} void IDictionary<int, T>.Add(int index, T value) {...} }
Voici
IDictionary<int,T>.this
etIDictionary<int,T>.Add
sont des implémentations de membres d’interface explicites.exemple de fin
Exemple : Dans certains cas, le nom d’un membre d’interface peut ne pas convenir à la classe d’implémentation, auquel cas, le membre de l’interface peut être implémenté à l’aide de l’implémentation explicite de membre d’interface. Une classe implémentant une abstraction de fichier, par exemple, implémente probablement une
Close
fonction membre qui a l’effet de libérer la ressource de fichier et implémenter la méthode de l’interface à l’aide de l’implémentationDispose
IDisposable
explicite de membre d’interface :interface IDisposable { void Dispose(); } class MyFile : IDisposable { void IDisposable.Dispose() => Close(); public void Close() { // Do what's necessary to close the file System.GC.SuppressFinalize(this); } }
exemple de fin
Il n’est pas possible d’accéder à une implémentation explicite d’un membre d’interface via son nom de membre d’interface qualifié dans un appel de méthode, l’accès aux propriétés, l’accès aux événements ou l’accès à l’indexeur. Une implémentation de membre d’interface explicite est accessible uniquement par le biais d’une instance d’interface et est dans ce cas référencée simplement par son nom de membre.
Il s’agit d’une erreur au moment de la compilation pour une implémentation explicite d’un membre d’interface afin d’inclure tous les modificateurs (§15.6) autres que extern
ou async
.
Il s’agit d’une erreur au moment de la compilation d’une implémentation de méthode d’interface explicite pour inclure des type_parameter_constraints_clauses. Les contraintes d’une implémentation de méthode d’interface explicite générique sont héritées de la méthode d’interface.
Remarque : Les implémentations de membres d’interface explicites ont des caractéristiques d’accessibilité différentes de celles des autres membres. Étant donné que les implémentations de membres d’interface explicites ne sont jamais accessibles par le biais d’un nom de membre d’interface qualifié dans un appel de méthode ou d’un accès à une propriété, elles sont dans un sens privé. Toutefois, étant donné qu’elles sont accessibles via l’interface, elles sont également aussi publiques que l’interface dans laquelle elles sont déclarées. Les implémentations de membres d’interface explicites servent deux objectifs principaux :
- Étant donné que les implémentations de membres d’interface explicites ne sont pas accessibles via des instances de classe ou de struct, elles permettent aux implémentations d’interface d’être exclues de l’interface publique d’une classe ou d’un struct. Cela est particulièrement utile lorsqu’une classe ou un struct implémente une interface interne qui n’est pas intéressante pour un consommateur de cette classe ou de ce struct.
- Les implémentations de membres d’interface explicites autorisent l’ambiguïté des membres de l’interface avec la même signature. Sans implémentations de membres d’interface explicites, il serait impossible pour une classe ou un struct d’avoir différentes implémentations des membres d’interface avec la même signature et le même type de retour, car il serait impossible pour une classe ou un struct d’avoir une implémentation à tous les membres de l’interface avec la même signature, mais avec des types de retour différents.
Note de fin
Pour qu’une implémentation de membre d’interface explicite soit valide, la classe ou le struct nomme une interface dans sa liste de classes de base qui contient un membre dont le nom de membre d’interface qualifié, le type, le nombre de paramètres de type et les types de paramètres correspondent exactement à ceux de l’implémentation de membre d’interface explicite. Si un membre de fonction d’interface a un tableau de paramètres, le paramètre correspondant d’une implémentation de membre d’interface explicite associée est autorisé, mais pas obligatoire, à avoir le params
modificateur. Si le membre de la fonction d’interface n’a pas de tableau de paramètres, une implémentation de membre d’interface explicite associée n’a pas de tableau de paramètres.
Exemple : Ainsi, dans la classe suivante
class Shape : ICloneable { object ICloneable.Clone() {...} int IComparable.CompareTo(object other) {...} // invalid }
la déclaration des résultats d’une erreur au moment de
IComparable.CompareTo
la compilation, carIComparable
elle n’est pas répertoriée dans la liste des classes deShape
base et n’est pas une interface de base deICloneable
. De même, dans les déclarationsclass Shape : ICloneable { object ICloneable.Clone() {...} } class Ellipse : Shape { object ICloneable.Clone() {...} // invalid }
la déclaration d’un résultat d’erreur au moment de
ICloneable.Clone
Ellipse
la compilation, carICloneable
elle n’est pas explicitement répertoriée dans la liste de classes de base deEllipse
.exemple de fin
Le nom de membre d’interface qualifié d’une implémentation de membre d’interface explicite fait référence à l’interface dans laquelle le membre a été déclaré.
Exemple : Ainsi, dans les déclarations
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } class TextBox : ITextBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} }
l’implémentation explicite du membre de l’interface de Paint doit être écrite en tant que
IControl.Paint
, et nonITextBox.Paint
.exemple de fin
18.6.3 Unicité des interfaces implémentées
Les interfaces implémentées par une déclaration de type générique restent uniques pour tous les types construits possibles. Sans cette règle, il serait impossible de déterminer la méthode correcte à appeler pour certains types construits.
Exemple : Supposons qu’une déclaration de classe générique ait été autorisée à être écrite comme suit :
interface I<T> { void F(); } class X<U ,V> : I<U>, I<V> // Error: I<U> and I<V> conflict { void I<U>.F() {...} void I<V>.F() {...} }
Si cela était autorisé, il serait impossible de déterminer le code à exécuter dans le cas suivant :
I<int> x = new X<int, int>(); x.F();
exemple de fin
Pour déterminer si la liste d’interface d’une déclaration de type générique est valide, les étapes suivantes sont effectuées :
- Supposons
L
que la liste des interfaces soit directement spécifiée dans une classe générique, un struct ou une déclarationC
d’interface. - Ajoutez à
L
toutes les interfaces de base des interfaces déjà présentesL
. - Supprimez les doublons de
L
. - Si un type construit possible créé à partir
C
de le serait, une fois que les arguments de type sont substitués enL
, provoquent l’identique de deux interfacesL
, puis la déclaration d’estC
non valide. Les déclarations de contrainte ne sont pas prises en compte lors de la détermination de tous les types construits possibles.
Remarque : Dans la déclaration
X
de classe ci-dessus, la listeL
d’interfaces se compose etl<U>
I<V>
. La déclaration n’est pas valide, car n’importe quel type construit avecU
etV
étant le même type, ces deux interfaces sont des types identiques. Note de fin
Il est possible que les interfaces spécifiées à différents niveaux d’héritage unifient :
interface I<T>
{
void F();
}
class Base<U> : I<U>
{
void I<U>.F() {...}
}
class Derived<U, V> : Base<U>, I<V> // Ok
{
void I<V>.F() {...}
}
Ce code est valide même si Derived<U,V>
implémente les deux I<U>
et I<V>
. Code
I<int> x = new Derived<int, int>();
x.F();
appelle la méthode dans Derived
, car Derived<int,int>'
re-implémente I<int>
efficacement (§18.6.7).
18.6.4 Implémentation de méthodes génériques
Lorsqu’une méthode générique implémente implicitement une méthode d’interface, les contraintes données pour chaque paramètre de type de méthode doivent être équivalentes dans les deux déclarations (après tout paramètre de type d’interface remplacé par les arguments de type appropriés), où les paramètres de type de méthode sont identifiés par des positions ordinales, de gauche à droite.
Exemple : Dans le code suivant :
interface I<X, Y, Z> { void F<T>(T t) where T : X; void G<T>(T t) where T : Y; void H<T>(T t) where T : Z; } class C : I<object, C, string> { public void F<T>(T t) {...} // Ok public void G<T>(T t) where T : C {...} // Ok public void H<T>(T t) where T : string {...} // Error }
la méthode
C.F<T>
implémenteI<object,C,string>.F<T>
implicitement . Dans ce cas,C.F<T>
il n’est pas nécessaire (ni autorisé) de spécifier la contrainte, carT: object
il s’agit d’une contrainteobject
implicite sur tous les paramètres de type. La méthodeC.G<T>
implémenteI<object,C,string>.G<T>
implicitement, car les contraintes correspondent à celles de l’interface, une fois les paramètres de type d’interface remplacés par les arguments de type correspondants. La contrainte de méthodeC.H<T>
est une erreur, car les types scellés (string
dans ce cas) ne peuvent pas être utilisés comme contraintes. L’omission de la contrainte est également une erreur, car les contraintes des implémentations de méthode d’interface implicite sont requises pour correspondre. Ainsi, il est impossible d’implémenterI<object,C,string>.H<T>
implicitement . Cette méthode d’interface ne peut être implémentée qu’à l’aide d’une implémentation de membre d’interface explicite :class C : I<object, C, string> { ... public void H<U>(U u) where U : class {...} void I<object, C, string>.H<T>(T t) { string s = t; // Ok H<T>(t); } }
Dans ce cas, l’implémentation de membre d’interface explicite appelle une méthode publique ayant des contraintes strictement plus faibles. L’affectation de t à s est valide, car
T
hérite d’une contrainte deT: string
, même si cette contrainte n’est pas expressible dans le code source. exemple de fin
Remarque : Lorsqu’une méthode générique implémente explicitement une méthode d’interface, aucune contrainte n’est autorisée sur la méthode d’implémentation (§15.7.1, §18.6.2). Note de fin
Mappage d’interface 18.6.5
Une classe ou un struct fournit des implémentations de tous les membres des interfaces répertoriées dans la liste de classes de base de la classe ou du struct. Le processus de localisation des implémentations des membres d’interface dans une classe ou un struct d’implémentation est appelé mappage d’interface.
Le mappage d’interface pour une classe ou un struct C
localise une implémentation pour chaque membre de chaque interface spécifiée dans la liste de classes de base de C
. L’implémentation d’un membre I.M
d’interface particulier, où I
est l’interface dans laquelle le membre M
est déclaré, est déterminée en examinant chaque classe ou struct S
, en commençant par C
et en répétant pour chaque classe de base successive de C
, jusqu’à ce qu’une correspondance se trouve :
- Si
S
contient une déclaration d’une implémentation de membre d’interface explicite qui correspondI
etM
, ce membre est l’implémentation deI.M
. - Sinon, si
S
elle contient une déclaration d’un membre public non statique qui correspondM
, ce membre est l’implémentation deI.M
. Si plusieurs membres correspondent, il n’est pas spécifié quel membre est l’implémentation deI.M
. Cette situation ne peut se produire que s’ilS
s’agit d’un type construit où les deux membres déclarés dans le type générique ont des signatures différentes, mais les arguments de type rendent leurs signatures identiques.
Une erreur au moment de la compilation se produit si les implémentations ne peuvent pas être situées pour tous les membres de toutes les interfaces spécifiées dans la liste de classes de base de C
. Les membres d’une interface incluent les membres hérités des interfaces de base.
Les membres d’un type d’interface construit sont considérés comme ayant remplacé tous les paramètres de type par les arguments de type correspondants, comme spécifié dans le §15.3.3.
Exemple : Par exemple, étant donné la déclaration d’interface générique :
interface I<T> { T F(int x, T[,] y); T this[int y] { get; } }
l’interface
I<string[]>
construite a les membres :string[] F(int x, string[,][] y); string[] this[int y] { get; }
exemple de fin
À des fins de mappage d’interface, un membre de classe ou de struct correspond à un membre A
B
d’interface lorsque :
A
etB
sont des méthodes, et le nom, le type et les listes de paramètres deA
etB
sont identiques.A
etB
sont des propriétés, le nom et le type d’etA
B
sont identiques, etA
possède les mêmes accesseurs queB
(A
est autorisé à avoir des accesseurs supplémentaires s’il n’est pas une implémentation de membre d’interface explicite).A
etB
sont des événements, et le nom et le type d’etA
B
sont identiques.A
etB
sont des indexeurs, le type et les listes de paramètres d’etA
B
sont identiques etA
ont les mêmes accesseurs queB
(A
est autorisé à avoir des accesseurs supplémentaires s’il ne s’agit pas d’une implémentation de membre d’interface explicite).
Les implications notables de l’algorithme de mappage d’interface sont les suivantes :
- Les implémentations de membres d’interface explicites sont prioritaires sur les autres membres de la même classe ou struct lors de la détermination du membre de classe ou de struct qui implémente un membre d’interface.
- Ni les membres non publics ni statiques ne participent au mappage d’interface.
Exemple : dans le code suivant
interface ICloneable { object Clone(); } class C : ICloneable { object ICloneable.Clone() {...} public object Clone() {...} }
le
ICloneable.Clone
membre deC
devient l’implémentation deClone
« ICloneable », car les implémentations de membres d’interface explicites sont prioritaires sur d’autres membres.exemple de fin
Si une classe ou un struct implémente deux interfaces ou plus contenant un membre portant le même nom, le même type et les types de paramètres, il est possible de mapper chacun de ces membres d’interface sur un seul membre de classe ou de struct.
Exemple :
interface IControl { void Paint(); } interface IForm { void Paint(); } class Page : IControl, IForm { public void Paint() {...} }
Ici, les
Paint
méthodes des deuxIControl
etIForm
sont mappées sur laPaint
méthode dansPage
. Bien entendu, il est également possible d’avoir des implémentations de membres d’interface explicites distinctes pour les deux méthodes.exemple de fin
Si une classe ou un struct implémente une interface qui contient des membres masqués, certains membres peuvent avoir besoin d’être implémentés via des implémentations de membres d’interface explicites.
Exemple :
interface IBase { int P { get; } } interface IDerived : IBase { new int P(); }
Une implémentation de cette interface nécessite au moins une implémentation de membre d’interface explicite et prend l’une des formes suivantes
class C1 : IDerived { int IBase.P { get; } int IDerived.P() {...} } class C2 : IDerived { public int P { get; } int IDerived.P() {...} } class C3 : IDerived { int IBase.P { get; } public int P() {...} }
exemple de fin
Lorsqu’une classe implémente plusieurs interfaces qui ont la même interface de base, il ne peut y avoir qu’une seule implémentation de l’interface de base.
Exemple : dans le code suivant
interface IControl { void Paint(); } interface ITextBox : IControl { void SetText(string text); } interface IListBox : IControl { void SetItems(string[] items); } class ComboBox : IControl, ITextBox, IListBox { void IControl.Paint() {...} void ITextBox.SetText(string text) {...} void IListBox.SetItems(string[] items) {...} }
il n’est pas possible d’avoir des implémentations distinctes pour le
IControl
nom dans la liste de classes de base, l’héritéIControl
parITextBox
et leIControl
hérité parIListBox
. En effet, il n’existe aucune notion d’identité distincte pour ces interfaces. Au lieu de cela, les implémentations etITextBox
partagent la même implémentation,IListBox
etIControl
sont simplement considérées pour implémenter trois interfaces,ComboBox
etIControl
ITextBox
.IListBox
exemple de fin
Les membres d’une classe de base participent au mappage d’interface.
Exemple : dans le code suivant
interface Interface1 { void F(); } class Class1 { public void F() {} public void G() {} } class Class2 : Class1, Interface1 { public new void G() {} }
la méthode
F
dans laquelleClass1
elle est utilisée dansClass2's
l’implémentation deInterface1
.exemple de fin
Héritage de l’implémentation de l’interface 18.6.6
Une classe hérite de toutes les implémentations d’interface fournies par ses classes de base.
Sans réinscrire explicitement une interface, une classe dérivée ne peut en aucun cas modifier les mappages d’interface qu’elle hérite de ses classes de base.
Exemple : dans les déclarations
interface IControl { void Paint(); } class Control : IControl { public void Paint() {...} } class TextBox : Control { public new void Paint() {...} }
la
Paint
méthode dansTextBox
masque laPaint
méthode dansControl
, mais elle ne modifie pas le mappage deControl.Paint
surIControl.Paint
, et les appels versPaint
via des instances de classe et des instances d’interface auront les effets suivantsControl c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes Control.Paint();
exemple de fin
Toutefois, lorsqu’une méthode d’interface est mappée sur une méthode virtuelle dans une classe, il est possible que les classes dérivées remplacent la méthode virtuelle et modifient l’implémentation de l’interface.
Exemple : réécriture des déclarations ci-dessus
interface IControl { void Paint(); } class Control : IControl { public virtual void Paint() {...} } class TextBox : Control { public override void Paint() {...} }
les effets suivants seront désormais observés
Control c = new Control(); TextBox t = new TextBox(); IControl ic = c; IControl it = t; c.Paint(); // invokes Control.Paint(); t.Paint(); // invokes TextBox.Paint(); ic.Paint(); // invokes Control.Paint(); it.Paint(); // invokes TextBox.Paint();
exemple de fin
Étant donné que les implémentations de membres d’interface explicites ne peuvent pas être déclarées virtuelles, il n’est pas possible de remplacer une implémentation de membre d’interface explicite. Toutefois, il est parfaitement valide pour une implémentation de membre d’interface explicite pour appeler une autre méthode, et que d’autres méthodes peuvent être déclarées virtuelles pour permettre aux classes dérivées de les remplacer.
Exemple :
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() { PaintControl(); } protected virtual void PaintControl() {...} } class TextBox : Control { protected override void PaintControl() {...} }
Ici, les classes dérivées de
Control
peuvent spécialiser l’implémentation enIControl.Paint
substituant laPaintControl
méthode.exemple de fin
18.6.7 Ré-implémentation de l’interface
Une classe qui hérite d’une implémentation d’interface est autorisée à réinscrire l’interface en l’incluant dans la liste de classes de base.
Une nouvelle implémentation d’une interface suit exactement les mêmes règles de mappage d’interface qu’une implémentation initiale d’une interface. Par conséquent, le mappage d’interface hérité n’a aucun effet sur le mappage d’interface établi pour la réécriture de l’interface.
Exemple : dans les déclarations
interface IControl { void Paint(); } class Control : IControl { void IControl.Paint() {...} } class MyControl : Control, IControl { public void Paint() {} }
le fait que
Control
les mappagesIControl.Paint
surControl.IControl.Paint
n’affectent pas la ré-implémentation dansMyControl
, qui mappeIControl.Paint
surMyControl.Paint
.exemple de fin
Les déclarations de membre public héritées et les déclarations de membre d’interface explicite héritées participent au processus de mappage d’interface pour les interfaces réinscrites.
Exemple :
interface IMethods { void F(); void G(); void H(); void I(); } class Base : IMethods { void IMethods.F() {} void IMethods.G() {} public void H() {} public void I() {} } class Derived : Base, IMethods { public void F() {} void IMethods.H() {} }
Ici, l’implémentation d’un mappage des méthodes d’interface
IMethods
surDerived
,Derived.F
,Base.IMethods.G
etDerived.IMethods.H
.Base.I
exemple de fin
Lorsqu’une classe implémente une interface, elle implémente implicitement toutes les interfaces de base de cette interface. De même, une nouvelle implémentation d’une interface est également implicitement une ré-implémentation de toutes les interfaces de base de l’interface.
Exemple :
interface IBase { void F(); } interface IDerived : IBase { void G(); } class C : IDerived { void IBase.F() {...} void IDerived.G() {...} } class D : C, IDerived { public void F() {...} public void G() {...} }
Ici, la réécriture des
IDerived
réapplémentsIBase
, le mappageIBase.F
surD.F
.exemple de fin
18.6.8 Classes et interfaces abstraites
Comme une classe non abstraite, une classe abstraite fournit des implémentations de tous les membres des interfaces répertoriées dans la liste de classes de base de la classe. Toutefois, une classe abstraite est autorisée à mapper des méthodes d’interface sur des méthodes abstraites.
Exemple :
interface IMethods { void F(); void G(); } abstract class C : IMethods { public abstract void F(); public abstract void G(); }
Ici, l’implémentation de
IMethods
cartesF
etG
sur des méthodes abstraites, qui doivent être substituées dans les classes non abstraites qui dérivent deC
.exemple de fin
Les implémentations de membres d’interface explicites ne peuvent pas être abstraites, mais les implémentations de membres d’interface explicites sont bien sûr autorisées à appeler des méthodes abstraites.
Exemple :
interface IMethods { void F(); void G(); } abstract class C: IMethods { void IMethods.F() { FF(); } void IMethods.G() { GG(); } protected abstract void FF(); protected abstract void GG(); }
Ici, les classes non abstraites dérivées
C
seraient requises pour remplacerFF
etGG
, fournissant ainsi l’implémentation réelle deIMethods
.exemple de fin
ECMA C# draft specification