Partager via


Interfaces : définir le comportement pour plusieurs types

Une interface contient des définitions pour un groupe de fonctionnalités connexes qu'une class non abstraite ou un struct doivent implémenter. Une interface peut définir des static méthodes. Une interface peut définir une implémentation par défaut pour les membres. Une interface ne peut pas déclarer de données d’instance telles que des champs, des propriétés implémentées automatiquement ou des événements de type propriété.

En utilisant des interfaces, vous pouvez, par exemple, inclure le comportement de plusieurs sources dans une classe. Cette fonctionnalité est importante en C#, car le langage ne prend pas en charge l'héritage multiple de classes. De plus, vous devez utiliser une interface si vous voulez simuler l'héritage pour des structs, car ils ne peuvent en fait pas hériter d'un autre struct ou d'une autre classe.

Vous définissez une interface à l'aide du mot clé interface, comme le montre l'exemple suivant.

interface IEquatable<T>
{
    bool Equals(T obj);
}

Le nom de l’interface doit être un nom d’identificateur C# valide. Par convention, les noms d’interface commencent par un I majuscule.

Toute classe ou tout struct qui implémentent l'interface IEquatable<T> doivent contenir une définition pour une méthode Equals qui correspond à la signature spécifiée par l'interface. Ainsi, vous pouvez compter sur une classe de type T qui implémente IEquatable<T> pour contenir une méthode Equals avec laquelle une instance de cette classe peut déterminer si elle est égale à une autre instance de la même classe.

La définition de IEquatable<T> ne fournit pas d'implémentation pour Equals. Une classe ou un struct peut implémenter plusieurs interfaces, mais une classe peut uniquement hériter d’une classe unique.

Pour plus d'informations sur les classes abstraites, consultez Classes abstract et sealed et membres de classe.

Les interfaces peuvent contenir des méthodes d’instance, propriétés, événements, indexeurs ou toute combinaison de ces quatre types de membres. Les interfaces peuvent contenir des constructeurs statiques, des champs, des constantes ou des opérateurs. À compter de C# 11, les membres de l’interface qui ne sont pas des champs peuvent être static abstract. Une interface ne peut pas contenir de champs d’instance, de constructeurs d’instance ou de finaliseurs. Les membres de l’interface sont publics par défaut et vous pouvez spécifier explicitement des modificateurs d’accessibilité, tels que public, protected, internal, private, protected internal ou private protected. Un membre private doit avoir une implémentation par défaut.

Pour implémenter un membre d’interface à l’aide de l’implémentation implicite, le membre correspondant de la classe d’implémentation doit être public, non statique et avoir le même nom et la même signature que le membre de l’interface. Toutefois, lorsqu’une interface est destinée à être interne uniquement ou utilise des types internes dans sa signature, vous pouvez utiliser l’implémentation d’interface explicite à la place, ce qui ne nécessite pas que le membre d’implémentation soit public.

Notes

Lorsqu’une interface déclare des membres statiques, un type implémentant cette interface peut également déclarer des membres statiques avec la même signature. Celles-ci sont distinctes et identifiées de manière unique par le type déclarant le membre. Le membre statique déclaré dans un type ne remplace pas le membre statique déclaré dans l’interface.

Une classe ou un struct qui implémente une interface doit fournir une implémentation pour tous les membres déclarés sans implémentation par défaut fournie par l’interface. Toutefois, si une classe de base implémente une interface, toute classe dérivée de la classe de base hérite de cette implémentation.

L'exemple suivant illustre une implémentation de l'interface IEquatable<T>. La classe d'implémentation, Car, doit fournir une implémentation de la méthode Equals.

public class Car : IEquatable<Car>
{
    public string? Make { get; set; }
    public string? Model { get; set; }
    public string? Year { get; set; }

    // Implementation of IEquatable<T> interface
    public bool Equals(Car? car)
    {
        return (this.Make, this.Model, this.Year) ==
            (car?.Make, car?.Model, car?.Year);
    }
}

Les propriétés et indexeurs d’une classe peuvent définir des accesseurs supplémentaires pour une propriété ou un indexeur qui est défini dans une interface. Par exemple, une interface peut déclarer une propriété qui a un accesseur get. La classe qui implémente l’interface peut déclarer la même propriété avec à la fois un accesseur get et un accesseur set. Toutefois, si la propriété ou l’indexeur utilisent une implémentation explicite, les accesseurs doivent correspondre. Pour plus d'informations sur l'implémentation explicite, consultez les pages Implémentation d'interface explicite et Propriétés d'interface.

Des interfaces peuvent hériter d'une ou de plusieurs interfaces. L’interface dérivée hérite des membres de ses interfaces de base. Une classe qui implémente une interface dérivée doit implémenter tous les membres de l’interface dérivée, y compris tous les membres des interfaces de base de l’interface dérivée. Cette classe peut être convertie implicitement en l’interface dérivée ou l’une de ses interfaces de base. Une classe peut inclure une interface plusieurs fois par le biais de classes de base qu’elle hérite ou d’interfaces dont d’autres interfaces héritent. Toutefois, la classe peut fournir une implémentation d'une interface une seule fois et uniquement si la classe déclare l'interface dans le cadre de la définition de la classe (class ClassName : InterfaceName). Si l'interface est héritée car vous avez hérité d'une classe de base qui implémente l'interface, la classe de base fournit l'implémentation des membres de l'interface. Toutefois, la classe dérivée peut réimplémenter des membres d’interface virtuels au lieu d’utiliser l’implémentation héritée. Lorsque des interfaces déclarent une implémentation par défaut d’une méthode, toute classe implémentant cette interface hérite de cette implémentation (vous devez caster l’instance de classe vers le type d’interface pour accéder à l’implémentation par défaut sur le membre Interface).

Une classe de base peut également implémenter des membres d'interface à l'aide de membres virtuels. Dans ce cas, une classe dérivée peut modifier le comportement de l'interface en substituant les membres virtuels. Pour plus d'informations sur les membres virtuels, consultez la page Polymorphisme.

Utilisation d’interfaces internes

Une interface interne peut généralement être implémentée à l’aide d’une implémentation implicite avec des membres publics, tant que tous les types de la signature d’interface sont accessibles publiquement. Toutefois, lorsqu’une interface utilise des types internes dans ses signatures membres, l’implémentation implicite devient impossible, car le membre de classe d’implémentation doit être public tout en exposant des types internes. Dans ce cas, vous devez utiliser l’implémentation d’interface explicite.

L’exemple suivant montre les deux scénarios :

// Internal type that cannot be exposed publicly
internal class InternalConfiguration
{
    public string Setting { get; set; } = "";
}

// Internal interface that CAN be implemented with public members
// because it only uses public types in its signature
internal interface ILoggable
{
    void Log(string message); // string is public, so this works with implicit implementation
}

// Interface with internal accessibility using internal types
internal interface IConfigurable
{
    void Configure(InternalConfiguration config); // Internal type prevents implicit implementation
}

// This class shows both implicit and explicit interface implementation
public class ServiceImplementation : ILoggable, IConfigurable
{
    // Implicit implementation works for ILoggable because string is public
    public void Log(string message)
    {
        Console.WriteLine($"Log: {message}");
    }

    // Explicit implementation required for IConfigurable because it uses internal types
    void IConfigurable.Configure(InternalConfiguration config)
    {
        // Implementation here
        Console.WriteLine($"Configured with: {config.Setting}");
    }
    
    // If we tried implicit implementation for IConfigurable, this wouldn't compile:
    // public void Configure(InternalConfiguration config) // Error: cannot expose internal type
}

Dans l’exemple précédent, l’interface IConfigurable utilise un type InternalConfiguration interne dans sa signature de méthode. La ServiceImplementation classe ne peut pas utiliser l’implémentation implicite, car cela nécessiterait de rendre la Configure méthode publique, ce qui n’est pas autorisé lorsque la signature de méthode contient des types internes. Au lieu de cela, l’implémentation d’interface explicite est utilisée, qui n’a pas de modificateur d’accès et n’est accessible qu’via le type d’interface.

En revanche, l’interface ILoggable peut être implémentée implicitement avec les membres publics, car tous les types de sa signature (string) sont accessibles publiquement, même si l’interface elle-même est interne.

Pour plus d’informations sur l’implémentation d’interface explicite, consultez Implémentation d’interface explicite.

Résumé des interfaces

Une interface possède les propriétés suivantes :

  • Dans les versions C# antérieures à 8.0, une interface est comme une classe de base abstraite avec uniquement des membres abstraits. Une classe ou un struct qui implémente l'interface doivent implémenter tous ses membres.
  • À compter de C# 8.0, une interface peut définir des implémentations par défaut pour certains ou tous ses membres. Une classe ou un struct qui implémente l’interface n’a pas besoin d’implémenter les membres qui ont des implémentations par défaut. Pour plus d’informations, consultez Méthodes d’interface par défaut.
  • Une interface ne peut pas être instanciée directement. Ses membres sont implémentées par une classe ou un struct qui implémentent l'interface.
  • Une classe ou un struct peuvent implémenter plusieurs interfaces. Une classe peut hériter d'une classe de base et également implémenter une ou plusieurs interfaces.