Partager via


19 interfaces

19.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 différents types de membres, comme décrit dans le §19.4. L’interface elle-même peut fournir une implémentation pour certains ou tous les membres de la fonction qu’elle déclare. Les membres pour lesquels l’interface ne fournit pas d’implémentation sont abstraits. Leurs implémentations doivent être fournies par des classes ou des structs qui implémentent l’interface ou une interface dérivée qui fournissent une définition substituée.

Remarque : Historiquement, l’ajout d’un nouveau membre de fonction à une interface a affecté tous les consommateurs existants de ce type d’interface ; c’était un changement cassant. L’ajout d’implémentations de membres de fonction d’interface permet aux développeurs de mettre à niveau une interface tout en permettant aux implémenteurs de remplacer cette implémentation. Les utilisateurs de l’interface peuvent accepter l’implémentation en tant que modification non cassant ; toutefois, si leurs exigences sont différentes, elles peuvent remplacer les implémentations fournies. Note de fin

Déclarations d’interface 19.2

19.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 (§23), suivi d’un ensemble facultatif de interface_modifiers (§19.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 (§19.2.3), suivie d’une spécification interface_base facultative (§19.2.4), suivi d’une spécification facultative de type_parameter_constraints_clause(§15.2.5), suivie d’un interface_body (§19.3), éventuellement suivi d’un point-virgule.

Une déclaration d'interface ne doit pas fournir de clauses de contraintes de type_paramètres à moins qu'elle ne fournisse également une liste de paramètres de type_variante.

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 19.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 (§24.2) n’est disponible que dans le code non sécurisé (§24).

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 modificateurs public, protected, internal et 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 modificateurs public, protected, internal, et private), les règles dans §15.2.2 s’appliquent.

19.2.3 Listes de paramètres de type variant

19.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 listes de paramètres de type ordinaires réside dans l'annotation de variance facultative sur chaque paramètre de type.

variant_type_parameter_list
    : '<' variant_type_parameter (',' variant_type_parameter)* '>'
    ;

variant_type_parameter
    : 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.

19.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 de délégué construit à partir d’un type S<Xᵢ, ... Xₑ> générique où, pour au moins une Aᵢ, l’une des conditions suivantes est satisfaite :
    • 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'une des conditions suivantes est remplie :

  • 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 de délégué construit à partir d’un type S<Xᵢ, ... Xₑ> générique où, pour au moins une Aᵢ, l’une des conditions suivantes est satisfaite :
    • 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.

19.2.3.3 Conversion de variance

L'objectif des annotations de variance est de fournir des conversions plus souples (mais toujours sûres) vers les 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 à un type T<Bᵢ, ..., Bᵥ> si T est soit une interface, soit un type délégué déclaré avec des paramètres de type variant T<Xᵢ, ..., Xᵥ> et, pour chaque paramètre de type variant Xᵢ, l’un des éléments suivants est vrai :

  • 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 19.2.4

Une interface peut hériter de zéro ou plusieurs types d’interface, qui sont appelés les interfaces de base explicitesde 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.

Une interface dérivée peut déclarer de nouveaux membres qui masquent les membres hérités (§7.7.2.3) déclarés dans les interfaces de base ou implémentent explicitement des membres hérités (§19.6.2) déclarés dans les interfaces de base.

interface_base
    : ':' interface_type_list
    ;

Les interfaces de base explicites peuvent être construites de types d’interface (§8.4, §19.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, spécifier une interface private ou internal dans la interface_base d'une interface public constitue une erreur de compilation. Note de fin

Il s’agit d’une erreur de compilation lorsqu’une interface hérite directement ou indirectement d’elle-même.

Les interfaces de based’une interface sont les interfaces de base explicites et leurs interfaces de base. En d’autres termes, l’ensemble des 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 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 (§19.2.3.2).

Corps de l’interface 19.3

La interface_body d’une interface définit les membres de l’interface.

interface_body
    : '{' interface_member_declaration* '}'
    ;

19.4 Membres de l’interface

19.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
    : constant_declaration
    | field_declaration
    | method_declaration
    | property_declaration
    | event_declaration
    | indexer_declaration
    | static_constructor_declaration
    | operator_declaration
    | type_declaration
    ;

Cette clause augmente la description des membres dans les classes (§15.3) avec des restrictions pour les interfaces. Les membres de l’interface sont déclarés à l’aide de member_declarations avec les règles supplémentaires suivantes :

  • Une finalizer_declaration n’est pas autorisée.
  • Les constructeurs d’instances, constructor_declarations, ne sont pas autorisés.
  • Tous les membres de l’interface ont implicitement un accès public ; Toutefois, un modificateur d’accès explicite (§7.5.2) est autorisé, sauf sur les constructeurs statiques (§15.12).
  • Le abstract modificateur est implicite pour les membres de la fonction d’interface sans corps ; ce modificateur peut être donné explicitement.
  • Membre de fonction d’instance d’interface dont la déclaration inclut un corps est un membre implicitement virtual , sauf si le ou le sealedprivate modificateur est utilisé. Le virtual modificateur peut être donné explicitement.
  • Un private ou sealed un membre de fonction d’une interface doit avoir un corps.
  • Un private membre de fonction ne doit pas avoir le modificateur sealed.
  • Une interface dérivée peut remplacer un membre abstrait ou virtuel déclaré dans une interface de base.
  • Un membre de fonction implémenté explicitement n’a pas le modificateur sealed.

Certaines déclarations, telles que constant_declaration (§15.4), n’ont aucune restriction dans les interfaces.

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, on dit que le membre d’interface dérivé masque le membre de base. Le masquage d’un membre hérité n’est pas considéré comme une erreur, mais cela entraîne un avertissement (§7.7.2.3).

Si un modificateur new est inclus dans une déclaration qui ne cache pas un membre hérité, un problème est émis à cet effet.

Remarque : Les membres de la classe object ne sont pas, strictement parlant, membres d’une interface (§19.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.

Exemple : Considérez une interface IA avec une implémentation pour un membre M et une propriété P. Un type C d’implémentation ne fournit pas d’implémentation pour l’une ou l’autreM.P Ils doivent être accessibles via une référence dont le type au moment de la compilation est une interface qui est implicitement convertible en IA ou IB. Ces membres ne sont pas trouvés par le biais de la recherche de membre sur une variable de type C.

interface IA
{
    public int P { get { return 10; } }
    public void M()
    {
        Console.WriteLine("IA.M");
    }
}

interface IB : IA
{
    public new int P { get { return 20; } }
    void IA.M()
    {
        Console.WriteLine("IB.M");
    }
}

class C : IB { }

class Test
{
    public static void Main()
    {
        C c = new C();
        ((IA)c).M();                               // cast needed
        Console.WriteLine($"IA.P = {((IA)c).P}");  // cast needed
        Console.WriteLine($"IB.P = {((IB)c).P}");  // cast needed
    }
}

Dans les interfaces IA et IB, le membre M est accessible directement par nom. Toutefois, dans la méthode Main, nous ne pouvons pas écrire c.M() ou c.P, car ces noms ne sont pas visibles. Pour les trouver, les casts vers le type d’interface approprié sont nécessaires. La déclaration d’in M utilise la syntaxe d’implémentation d’interface IB explicite. Cela est nécessaire pour que cette méthode remplace celle dans IA; le modificateur override peut ne pas être appliqué à un membre de fonction. exemple de fin

Champs d’interface 19.4.2

Cette clause augmente la description des champs des classes §15.5 pour les champs déclarés dans les interfaces.

Les champs d’interface sont déclarés à l’aide de field_declarations (§15.5.1) avec les règles supplémentaires suivantes :

  • Il s’agit d’une erreur au moment de la compilation pour field_declaration de déclarer un champ d’instance.

Exemple : Le programme suivant contient des membres statiques de différents types :

public interface IX
{
    public const int Constant = 100;
    protected static int field;

    static IX()
    {
        Console.WriteLine("static members initialized");
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
        field = 50;
        Console.WriteLine("static constructor has run");
    }
}

public class Test: IX
{
    public static void Main()
    {
        Console.WriteLine($"constant = {IX.Constant}, field = {IX.field}");
    }
}

La sortie produite est

static members initialized
constant = 100, field = 0
static constructor has run
constant = 100, field = 50

exemple de fin

Consultez le §19.4.8 pour plus d’informations sur l’allocation et l’initialisation des champs statiques.

Méthodes d’interface 19.4.3

Cette clause augmente la description des méthodes dans les classes §15.6 pour les méthodes déclarées dans les interfaces.

Les méthodes d’interface sont déclarées à l’aide de method_declaration(§15.6)). 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. Les méthodes d’interface ont les règles supplémentaires suivantes :

  • method_modifier ne doit pas inclure override.

  • Méthode dont le corps est un point-virgule (;) est abstract; le abstract modificateur n’est pas obligatoire, mais est autorisé.

  • Déclaration de méthode d’interface qui a un corps de bloc ou un corps d’expression en tant que method_body est virtual; le virtual modificateur n’est pas obligatoire, mais est autorisé.

  • Un method_declaration n’a pas de type_parameter_constraints_clauses, sauf s’il a également un type_parameter_list.

  • La liste des conditions requises pour les combinaisons valides de modificateurs indiqués pour une méthode de classe est étendue, comme suit :

    • Une déclaration statique qui n’est pas extern doit avoir un corps de bloc ou un corps d’expression en tant que method_body.
    • Une déclaration virtuelle qui n’est pas extern doit avoir un corps de bloc ou un corps d’expression en tant que method_body.
    • Une déclaration privée qui n’est pas extern doit avoir un corps de bloc ou un corps d’expression en tant que method_body.
    • Une déclaration scellée qui n’est pas extern doit avoir un corps de bloc ou un corps d’expression en tant que method_body.
    • Une déclaration asynchrone doit avoir un corps de bloc ou un corps d’expression en tant que method_body.
  • Tous les types de paramètres d’une méthode d’interface doivent être sécurisés en entrée (§19.2.3.2), et le type de retour doit être soit void sécurisé par sortie.

  • Tous les types de paramètres de sortie ou de référence doivent également être sécurisés en sortie.

    Remarque : Les paramètres de sortie doivent être sécurisés en entrée en raison de restrictions d’implémentation courantes. Note de fin

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

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
}

est mal formé, car l’utilisation de la contrainte sur le paramètre de type T appliquée à U n’est pas sûre pour l’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 :

interface I<out T>
{
    void M<U>() where U : T;
}
class B {}
class D : B {}
class E : B {}
class C : I<D>
{
    public void M<D>() {...} 
}

...

I<B> b = new C();
b.M<E>();

C’est en fait un appel à C.M<E>. Mais cet appel exige que E dérive de D, ce qui ferait que la sécurité de type serait violée ici.

exemple de fin

Remarque : Consultez le §19.4.2 pour obtenir un exemple qui montre non seulement une méthode statique avec une implémentation, mais comme cette méthode est appelée Main et a le type de retour et la signature appropriés, il s’agit également d’un point d’entrée. Note de fin

Une méthode virtuelle avec implémentation déclarée dans une interface peut être substituée pour être abstraite dans une interface dérivée. Il s’agit de la réabstraction.

Exemple :

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB: IA
{
    abstract void IA.M();    // reabstraction of M
}

Cela est utile dans les interfaces dérivées où l’implémentation d’une méthode est inappropriée et qu’une implémentation plus appropriée doit être fournie par l’implémentation de classes. exemple de fin

Propriétés de l’interface 19.4.4

Cette clause augmente la description des propriétés dans les classes §15.7 pour les propriétés déclarées dans les interfaces.

Les propriétés d’interface sont déclarées à l’aide de property_declarations (§15.7.1) avec les règles supplémentaires suivantes :

  • property_modifier ne doit pas inclure override.

  • Une implémentation de membre d’interface explicite ne doit pas contenir de accessor_modifier (§15.7.3).

  • Une interface dérivée peut implémenter explicitement une propriété d’interface abstraite déclarée dans une interface de base.

    Remarque : Comme une interface ne peut pas contenir de champs d’instance, une propriété d’interface ne peut pas être une propriété automatique d’instance, car cela nécessiterait la déclaration de champs d’instance masqués implicites. Note de fin

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

  • Déclaration de méthode d’interface qui a un corps de bloc ou un corps d’expression en tant que method_body est virtual; le virtual modificateur n’est pas obligatoire, mais est autorisé.

  • Une instance property_declaration qui n’a aucune implémentation n’est abstract; le abstract modificateur n’est pas obligatoire, mais est autorisé. Il n’est jamais considéré comme une propriété implémentée automatiquement (§15.7.4).

Événements d’interface 19.4.5

Cette clause augmente la description des événements dans les classes §15.8 pour les événements déclarés dans les interfaces.

Les événements d’interface sont déclarés à l’aide de event_declarations (§15.8.1), avec les règles supplémentaires suivantes :

  • event_modifier ne doit pas inclure override.
  • Une interface dérivée peut implémenter un événement d’interface abstraite déclaré dans une interface de base (§15.8.5).
  • Il s’agit d’une erreur au moment de la compilation pour variable_declarators dans une instance event_declaration de contenir des variable_initializers.
  • Un événement d’instance avec les virtual ou sealed modificateurs doit déclarer des accesseurs. Il n’est jamais considéré comme un événement de type champ implémenté automatiquement (§15.8.2).
  • Un événement d’instance avec le abstract modificateur ne doit pas déclarer d’accesseurs.
  • Le type d’un événement d’interface doit être sécurisé en entrée.

Indexeurs d’interface 19.4.6

Cette clause augmente la description des indexeurs dans les classes §15.9 pour les indexeurs déclarés dans les interfaces.

Les indexeurs d’interface sont déclarés à l’aide de indexer_declarations (§15.9), avec les règles supplémentaires suivantes :

  • indexer_modifier ne doit pas inclure override.

  • Un indexer_declaration qui a un corps d’expression ou contient un accesseur avec un corps de bloc ou un corps d’expression est virtual; le virtual modificateur n’est pas obligatoire, mais est autorisé.

  • Un indexer_declaration dont les corps d’accesseur sont des points-virgules (;) est abstract; le abstract modificateur n’est pas obligatoire, mais est autorisé.

  • Tous les types de paramètres d’un indexeur d’interface doivent être input-safe (§19.2.3.2).

  • Tous les types de paramètres de sortie ou de référence doivent également être sécurisés en sortie.

    Remarque : Les paramètres de sortie doivent être sécurisés en 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.

Opérateurs d’interface 19.4.7

Cette clause augmente la description de operator_declaration membres dans les classes §15.10 pour les opérateurs déclarés dans les interfaces.

Une operator_declaration dans une interface est l’implémentation (§19.1).

Il s’agit d’une erreur au moment de la compilation d’une interface pour déclarer une conversion, une égalité ou un opérateur d’inégalité.

Constructeurs statiques d’interface 19.4.8

Cette clause augmente la description des constructeurs statiques dans les classes §15.12 pour les constructeurs statiques déclarés dans les interfaces.

Le constructeur statique pour une interface fermée (§8.4.3) s’exécute au plus une fois dans un domaine d’application donné. L’exécution d’un constructeur statique est déclenchée par la première des actions suivantes à effectuer dans un domaine d’application :

  • Tous les membres statiques de l’interface sont référencés.
  • Avant que la Main méthode soit appelée pour une interface contenant la Main méthode (§7.1) dans laquelle l’exécution commence.
  • Cette interface fournit une implémentation pour un membre et cette implémentation est accessible en tant qu’implémentation la plus spécifique (§19.4.10) pour ce membre.

Remarque : Dans le cas où aucune des actions précédentes n’a lieu, le constructeur statique d’une interface peut ne pas s’exécuter pour un programme où les instances de types qui implémentent l’interface sont créées et utilisées. Note de fin

Pour initialiser un nouveau type d’interface fermée, commencez par créer un nouvel ensemble de champs statiques pour ce type fermé particulier. Chacun des champs statiques est initialisé à sa valeur par défaut. Ensuite, les initialiseurs de champs statiques sont exécutés pour ces champs statiques. Enfin, le constructeur statique est exécuté.

Remarque : Consultez le §19.4.2 pour obtenir un exemple d’utilisation de différents types de membres statiques (y compris une méthode Main) déclarés dans une interface. Note de fin

19.4.9 Types imbriqués d’interface

Cette clause augmente la description des types imbriqués dans les classes §15.3.9 pour les types imbriqués déclarés dans les interfaces.

Il s’agit d’une erreur pour déclarer un type de classe, un type de struct ou un type d’énumération dans l’étendue d’un paramètre de type déclaré avec un variance_annotation (§19.2.3.1).

Exemple : la déclaration ci-dessous C est une erreur.

interface IOuter<out T>
{
    class C { } // error: class declaration within scope of variant type parameter 'T'
}

exemple de fin

19.4.10 implémentation la plus spécifique

Chaque classe et struct doit avoir une implémentation la plus spécifique pour chaque membre virtuel déclaré dans toutes les interfaces implémentées par ce type parmi les implémentations apparaissant dans le type ou ses interfaces directes et indirectes. L’implémentation la plus spécifique est une implémentation unique qui est plus spécifique que toutes les autres implémentations.

Remarque : La règle d’implémentation la plus spécifique garantit qu’une ambiguïté résultant de l’héritage de l’interface de diamant est résolue explicitement par le programmeur au moment où se produit le conflit. Note de fin

Pour un type T qui est un struct ou une classe qui implémente des interfaces I2 et , où I3 et I2I3 à la fois dérivent directement ou indirectement de l’interface I qui déclare un membre M, l’implémentation la plus spécifique est M :

  • Si T elle déclare une implémentation de I.M, cette implémentation est l’implémentation la plus spécifique.
  • Sinon, s’il s’agit T d’une classe et d’une classe de base directe ou indirecte déclare une implémentation de , la classe de I.Mbase la plus dérivée est T l’implémentation la plus spécifique.
  • Sinon, si et sont des interfaces implémentées par I2 et I3 dérivent de directement T ou indirectement, I3 est une implémentation plus spécifique que I2.I3.MI2.M
  • Sinon, ni ne I2.MI3.M sont plus spécifiques et une erreur se produit.

Exemple :

interface IA
{
    void M() { Console.WriteLine("IA.M"); }
}

interface IB : IA
{
    void IA.M() { Console.WriteLine("IB.M"); }
}

interface IC: IA
{
    void IA.M() { Console.WriteLine("IC.M"); }
}

abstract class C: IB, IC { } // error: no most specific implementation for 'IA.M'

abstract class D: IA, IB, IC // OK
{
    public abstract void M();
}

La règle d’implémentation la plus spécifique garantit qu’un conflit (c’est-à-dire une ambiguïté résultant de l’héritage du diamant) est résolu explicitement par le programmeur au moment où le conflit se produit. exemple de fin

Accès aux membres de l’interface 19.4.11

Les membres de l’interface sont accessibles via les expressions d’accès aux membres (§12.8.7) et d’accès indexeur (§12.8.12.4) du formulaire I.M et I[A], où I il s’agit d’un type d’interface, M est une constante, un champ, une méthode, une propriété ou un événement de ce type d’interface, et A est une liste d’arguments d’indexeur.

Dans une classe D, avec une classe Bde base directe ou indirecte, où B implémente directement ou indirectement une interface I et I définit une méthode M(), l’expression base.M() est valide uniquement si base.M() statiquement (§12.3) se lie à une implémentation d’un type de M() classe.

Pour les interfaces qui sont 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 de membre (§12.5), l’appel de méthode (§12.8.10.2) et les règles d’accès indexeur (§12.8.12.4) sont exactement les mêmes que pour les classes et les structs : les membres dérivés masquent les membres moins dérivés avec 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 moulages 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
{
    int Count { get; set; }
}

interface IListCounter : IList, ICounter {}

class C
{
    void Test(IListCounter x)
    {
        x.Count = 1;             // Error
        ((IList)x).Count = 1;    // Ok, invokes IList.Count.set
        ((ICounter)x).Count = 1; // Ok, invokes ICounter.Count
    }
}

la première instruction provoque une erreur au moment de la compilation, car la recherche de membre (§12.5) de Count dans IListCounter est 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'invocation n.Add(1) sélectionne IInteger.Add en appliquant les 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'y a 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 membre IBase.F est caché par le membre ILeft.F. L'invocation d.F(1) sélectionne donc ILeft.F, même si IBase.F semble ne pas être caché dans le chemin d'accès qui passe par 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 de IDerived à ILeft à IBase masque IBase.F, le membre est également masqué dans le chemin d’accès de IDerived à IRight à IBase.

exemple de fin

19.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é de Paint est IControl.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 GraphicsLib
{
    interface IPolygon
    {
        void CalculateArea();
    }
}

Dans l'espace de noms GraphicsLib, IPolygon.CalculateArea et GraphicsLib.IPolygon.CalculateArea sont les noms des membres d'interface qualifiés pour la méthode CalculateArea.

exemple de fin

Implémentations d’interface 19.6

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

Une classe ou un struct C qui implémente une interface I doit fournir ou hériter d’une implémentation pour chaque membre déclaré dans I ce type d’accès C . Les membres publics de I peuvent être définis dans les membres publics de C. Les membres non publics déclarés dans I ce qui sont accessibles C peuvent être définis à C l’aide de l’implémentation d’interface explicite (§19.6.2).

Un membre d’un type dérivé qui satisfait au mappage d’interface (§19.6.5) mais n’implémente pas le membre d’interface de base correspondant introduit un nouveau membre. Cela se produit lorsque l’implémentation d’interface explicite est requise pour définir le membre de l’interface.

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, §19.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 doivent satisfaire à la règle d’unicité décrite dans le §19.6.3.

Implémentations de membres d’interface explicites 19.6.2

À des fins d’implémentation d’interfaces, une classe, un struct ou une interface 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é. Une classe ou un struct qui implémente un membre non public dans une interface de base doit déclarer une implémentation de membre d’interface explicite. Une interface qui implémente un membre dans une interface de base doit déclarer une implémentation de membre d’interface explicite.

Un membre d’interface dérivé qui satisfait au mappage d’interface (§19.6.5) masque le membre de l’interface de base (§7.7.2). Le compilateur émet un avertissement, sauf si le new modificateur est présent.

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émenterait probablement une Close fonction membre ayant pour effet de libérer la ressource de fichier, et implémenter la méthode Dispose de l'interface IDisposable en utilisant une implémentation 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’instance d’interface explicite est accessible uniquement via une instance d’interface et est dans ce cas référencée simplement par son nom de membre. Une implémentation de membre statique d’interface explicite est accessible uniquement via le nom de l’interface.

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.

Une implémentation explicite de méthode d'interface hérite des contraintes des paramètres de type définies dans l'interface.

Une type_parameter_constraints_clause sur une implémentation de méthode d’interface explicite peut être constituée uniquement des class ou structprimary_constraints appliquées à type_parameters qui sont connues en fonction des contraintes héritées pour être des types référence ou valeur respectivement. Tout type du formulaire T? dans la signature de l’implémentation de la méthode d’interface explicite, où T est un paramètre de type, est interprété comme suit :

  • Si une class contrainte est ajoutée pour le paramètre de type T, alors T? est un type de référence nullable ; sinon,
  • S'il n'existe aucune contrainte ajoutée ou si une struct contrainte est ajoutée, alors pour le paramètre de type TT?, il s'agit d'un type valeur nul.

Exemple : L’exemple suivant montre comment fonctionnent les règles lorsque les paramètres de type sont impliqués :

#nullable enable
interface I
{
    void Foo<T>(T? value) where T : class;
    void Foo<T>(T? value) where T : struct;
}

class C : I
{
    void I.Foo<T>(T? value) where T : class { }
    void I.Foo<T>(T? value) where T : struct { }
}

Sans la contrainte de paramètre de type where T : class, la méthode de base avec le paramètre de type référence ne peut pas être remplacée. exemple de fin

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 explicites des membres de l'interface permettent de désambiguïser les membres de l'interface ayant la même signature. Sans implémentations de membres d’interface explicites, il serait impossible pour une classe, un struct ou une interface d’avoir différentes implémentations de membres d’interface avec la même signature et le même type de retour, car il serait impossible pour une classe, un struct ou une interface 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, le struct ou l’interface doit nommer une interface dans sa classe de base ou liste d’interface 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 de IComparable.CompareTo entraîne une erreur à la compilation car IComparable ne figure pas dans la liste des classes de base de Shape 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 de ICloneable.Clone dans Ellipse entraîne une erreur de compilation, car ICloneable n'est pas explicitement répertorié dans la liste des 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

19.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 de C devait, après substitution des arguments de type dans L, faire en sorte que deux interfaces dans L soient identiques, alors la déclaration de C n'est pas 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 de l<U> et 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>. Le 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 (§19.6.7).

19.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> n'est pas nécessaire (ni autorisé) pour spécifier la contrainte T: object, car object est une contrainte 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 explicite du membre de l'interface invoque 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, §19.6.2). Note de fin

Mappage d’interface 19.6.5

Une classe ou un struct fournit des implémentations de tous les membres abstraits 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, interface ou struct S, en commençant par C et en répétant pour chaque classe de base successive et l’interface implémentée , Cjusqu’à ce qu’une correspondance soit située :

  • 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 contient une déclaration d’un membre public non statique qui correspond à M, ce membre est l’implémentation de I.M. Si plus d'un membre correspond, il n'est pas précisé lequel 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 construite I<string[]> possède 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, d’interface 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 membre ICloneable.Clone de C devient l’implémentation de Clone dans ICloneable, car les implémentations explicites des membres de l’interface 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 méthodes de Paint et de IControl sont associées à la méthode IForm dans Paint. 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 nommé dans la liste de classes de base, le IControl hérité par ITextBox, et le IControl hérité par IListBox. En effet, il n’existe aucune notion d’identité distincte pour ces interfaces. Au contraire, les implémentations de ITextBox et IListBox partagent la même implémentation de IControl, et ComboBox est simplement considéré comme implémentant trois interfaces : IControl, ITextBox et 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() {}
}

Dans F, la méthode Class1 est utilisée dans l'implémentation de Class2'sInterface1.

exemple de fin

Héritage de l’implémentation de l’interface 19.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 d'appeler une autre méthode, laquelle peut être déclarée virtuelle pour permettre aux classes dérivées de la 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 de IControl.Paint en remplaçant la méthode PaintControl.

exemple de fin

19.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 mappe IControl.Paint sur Control.IControl.Paint n’affecte pas la ré-implémentation dans MyControl, qui mappe IControl.Paint sur MyControl.Paint.

exemple de fin

Les déclarations de membres publics hérités et les déclarations de membres explicites d'interfaces héritées participent au processus de mappage des interfaces réimplémentées.

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 de IMethods dans Derived mappe les méthodes de l'interface sur Derived.F, Base.IMethods.G, Derived.IMethods.H, et 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éimplémentation de IDerived réimplémente également IBase, en mappant IBase.F sur D.F.

exemple de fin

19.6.8 Classes et interfaces abstraites

Comme une classe non abstraite, une classe abstraite fournit des implémentations de tous les membres abstraits 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 mappe F et G sur des méthodes abstraites, qui doivent être surchargé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