Partager via


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 publicmodificateurs et protectedinternalles modificateurs privatecontrô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 publicmodificateurs, etc protectedinternalprivate.), 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 et Z 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 contravariant
  • T est un type de tableau avec un type d’élément non sécurisé de sortie
  • T est un type Sᵢ,... Aₑ d’interface ou délégué construit à partir d’un type S<Xᵢ, ... Xₑ> générique dans lequel au moins une Aᵢ des conservations suivantes :
    • Xᵢ est covariant ou invariant et Aᵢ est non sécurisé en sortie.
    • Xᵢ est contravariant ou invariant et Aᵢ 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 covariant
  • T est un type de tableau avec un type d’élément non sécurisé d’entrée
  • T est un type S<Aᵢ,... Aₑ> d’interface ou délégué construit à partir d’un type S<Xᵢ, ... Xₑ> générique dans lequel au moins une Aᵢ des conservations suivantes :
    • Xᵢ est covariant ou invariant et Aᵢ est non sécurisé en entrée.
    • Xᵢ est contravariant ou invariant et Aᵢ 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 de Aᵢ à Bᵢ
  • Xᵢ est contravariant et une référence implicite ou une conversion d’identité existe de Bᵢ à Aᵢ
  • Xᵢ est invariant et une conversion d’identité existe de Aᵢ à 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 une internal interface dans la interface_base d’une public 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 sont IControl, ITextBoxet IListBox. En d’autres termes, l’interface IComboBox ci-dessus hérite des membres SetText et SetItems ainsi que Paint.

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 la Combine méthode après le remplacement du paramètre T de type par string[,].

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, outet ref.
  • 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 classe object 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 sur U 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 cela E dérive Dde , 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 CountIListCounter ambiguë. Comme illustré par l’exemple, l’ambiguïté est résolue en cas de conversion x 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 des IInteger.Add règles de résolution de surcharge de §12.6.4. De même, l’appel n.Add(1.0) sélectionne IDouble.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 le ILeft.F membre. L’appel d.F(1) sélectionne ILeft.Fdonc , même s’il IBase.F semble ne pas être masqué dans le chemin d’accès qui mène à travers IRight.

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 à masquer ILeftIBaseIBase.F , le membre est également masqué dans le chemin d’accès IDerived de vers .IRightIBase

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 PaintIControl.Paint et le nom qualifié de SetText est ITextBox.SetText. Dans l’exemple ci-dessus, il n’est pas possible de faire référence à PaintITextBox.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 sont ICloneable.Clone tous les deux System.ICloneable.Clone pour la Clone 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 fois IControl et ITextBox.

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 et IDictionary<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émentation DisposeIDisposable 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, car IComparable elle n’est pas répertoriée dans la liste des classes de Shape base et n’est pas une interface de base de ICloneable. De même, dans les déclarations

class 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.CloneEllipse la compilation, car ICloneable elle n’est pas explicitement répertoriée dans la liste de classes de base de Ellipse.

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 non ITextBox.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éclaration Cd’interface.
  • Ajoutez à L toutes les interfaces de base des interfaces déjà présentes L.
  • 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 en L, provoquent l’identique de deux interfaces L , puis la déclaration d’est C 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 liste L d’interfaces se compose et l<U>I<V>. La déclaration n’est pas valide, car n’importe quel type construit avec U et V é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émente I<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, car T: object il s’agit d’une contrainte object implicite sur tous les paramètres de type. La méthode C.G<T> implémente I<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éthode C.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émenter I<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 de T: 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.Md’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 correspond I et M, ce membre est l’implémentation de I.M.
  • Sinon, si S elle contient une déclaration d’un membre public non statique qui correspond M, ce membre est l’implémentation de I.M. Si plusieurs membres correspondent, il n’est pas spécifié quel membre est l’implémentation de I.M. Cette situation ne peut se produire que s’il S 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 AB d’interface lorsque :

  • A et B sont des méthodes, et le nom, le type et les listes de paramètres de A et B sont identiques.
  • A et B sont des propriétés, le nom et le type d’et AB sont identiques, et A possède les mêmes accesseurs que B (A est autorisé à avoir des accesseurs supplémentaires s’il n’est pas une implémentation de membre d’interface explicite).
  • A et B sont des événements, et le nom et le type d’et AB sont identiques.
  • A et B sont des indexeurs, le type et les listes de paramètres d’et AB sont identiques et A ont les mêmes accesseurs que B (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 de C devient l’implémentation de Clone « 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 deux IControl et IForm sont mappées sur la Paint méthode dans Page. 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 par ITextBoxet le IControl hérité par IListBox. En effet, il n’existe aucune notion d’identité distincte pour ces interfaces. Au lieu de cela, les implémentations et ITextBoxpartagent la même implémentation, IListBoxet IControl sont simplement considérées pour implémenter trois interfaces, ComboBoxet IControlITextBox.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 laquelle Class1 elle est utilisée dans Class2's l’implémentation de Interface1.

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 dans TextBox masque la Paint méthode dans Control, mais elle ne modifie pas le mappage de Control.Paint sur IControl.Paint, et les appels vers Paint via des instances de classe et des instances d’interface auront les effets suivants

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 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 en IControl.Paint substituant la PaintControl 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 mappages IControl.Paint sur Control.IControl.Paint n’affectent pas la ré-implémentation dans MyControl, qui mappe IControl.Paint sur MyControl.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 sur Derived, Derived.F, Base.IMethods.Get Derived.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éments IBase, le mappage IBase.F sur D.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 cartes F et G sur des méthodes abstraites, qui doivent être substituées dans les classes non abstraites qui dérivent de C.

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 remplacer FF et GG, fournissant ainsi l’implémentation réelle de IMethods.

exemple de fin