10. Interfaces

Une interface est similaire à une classe, mais avec les différences majeurs suivantes : Le polymorphisme est décrit comme la capacité à exécuter les mêmes opérations sur différents types, tant que chaque type partage un sous-ensemble de caractéristiques communes. le but d'une interface est précisément de définir un tel sous-ensemble de caractéristiques.
Une interface est composée d'une ensemble de membres : Ces membres sont toujours implicitement définis public et abstract.

10.1. Définir une interface

Une déclaration d'interface se fait comme une déclaration de classe, mais elle ne fournit pas d'implémentation pour ses membres puisqu'ils sont implicitement abstraits. Ces membres sont destinés à être implémentés par une classe ou une structure.
Voici une interface très simple qui définit une seul méthode :
public interface IDelete
{
    void Delete();
}

10.2. Implémenter une interface

Les classes ou les structures qui implémentent une interface doivent être dites "qui remplissent pleinement le contrat avec l'interface".
Code Résultat Remarques
using System;

namespace Exemples
{
    // définition de l'interface
    interface IDelete
    {
        void Delete();
    }
    // implémentation de l'interface
    // sur la class TextBox
    public class TextBox : IDelete
    {
        // champ
        private string text;
        // propriété
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
        // constructeur
        public TextBox(string text)
        {
            this.text = text;
        }
        // méthodes
        public void AfficheText()
        {
            Console.WriteLine("Text = '" + text + "'");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du texte");
            text = String.Empty;
        }
    }
    // partiellement sur la class TreeView...
    public class TreeView : IDelete
    {
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du TreeView");
        }
    }
    class ImplementerInterface1
    {
        static void Main(string[] args)
        {
            TextBox tb = new TextBox("Rudy");
            tb.AfficheText(); // affiche Text = 'Rudy'
            tb.Delete(); // affiche Effacement du texte
            tb.AfficheText(); // affiche Text = ''
            TreeView tv = new TreeView();
            tv.Delete(); // affiche Effacement du TreeView
            Console.ReadKey();
        }
    }
}
Text = 'Rudy'
Effacement du texte
Text = ''
Effacement du TreeView
Dans cet exemple, notre interface IDelete peut être implémentée par les contrôles graphiques (GUI) qui supportent le concept d'effacement, tels que TextBox, TreeView ou votre propre contrôle GUI personnalisé...
using System;

namespace Exemples
{
    interface ITranslation
    {
        void Translation(int dx, int dy);
    }
    // implémentation de l'interface
    // sur la structure Point
    struct Point : ITranslation
    {
        // champs
        public string nom;
        public int x, y;

        // constructeur
        public Point(string nom, int x, int y)
        {
            this.nom = nom;
            this.x = x;
            this.y = y;
        }

        // méthodes
        public void AffichePoint()
        {
            Console.WriteLine(nom + "(" + x + ", " + y + ")");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Translation(int dx, int dy)
        {
            // translation du point...
            Console.WriteLine("Translation de (" + dx + ", " + dy + ")");
            x += dx;
            y += dy;
        }
    }
    class ImplementerInterface2
    {
        static void Main(string[] args)
        {
            Point P = new Point("P", 0, 0);
            P.AffichePoint(); // affiche P(0, 0)
            P.Translation(33, 91);
            P.AffichePoint(); // affiche P(33, 91)
            Console.ReadKey();
        }
    }
}
P(0, 0)
Translation de (33, 91)
P(33, 91)
Ici, on voit l'implémentation de l'interface ITranslation sur la structure Point permettant de réaliser une translation d'un point donné d'un vecteur (dx, dy) passé en argument à la méthode Translation.
using System;
using System.Windows.Forms;

namespace Exemples
{
    // définition de l'interface
    interface IDelete
    {
        void Delete();
    }
    // implémentation de l'interface
    // sur la class TextBox qui hérite de la classe Control
    public class TextBox : Control, IDelete
    {
        // champ
        private string text;
        // propriété
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
        // constructeur
        public TextBox(string text)
        {
            this.text = text;
        }
        // méthodes
        public void AfficheText()
        {
            Console.WriteLine("Text = '" + text + "'");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du texte");
            text = String.Empty;
        }
    }
    class ImplementerInterface3
    {
        static void Main(string[] args)
        {
            TextBox tb = new TextBox("Rudy");
            tb.AfficheText(); // affiche Text = 'Rudy'
            tb.Delete(); // affiche Effacement du texte
            tb.AfficheText(); // affiche Text = ''
            Console.ReadKey();
        }
    }
}
Text = 'Rudy'
Effacement du texte
Text = ''
Si une classe hérite d'une classe de base, alors chaque interface implémentée doit apparaître après la classe de base (ici, la classe Control de l'assembly System.Windows.Forms dans System.Windows.Forms.dll à ajouter dans la liste des références du projet...).

10.3. Utiliser une interface

Une interface est utile lorsque vous avez besoin de plusieurs classes pour partager des caractéristiques qui ne sont pas présentes dans la classe de base. De plus, une interface est un bon moyen de s'assurer que ces classes fournissent leur propre implémentation pour les membres de l'interface, puisque les membres d'interface sont implicitement abstaits.
Code Résultat Remarques
using System;
using System.Windows.Forms;

namespace Exemples
{
    // définition de l'interface
    interface IDelete
    {
        void Delete();
    }
    // implémentation de l'interface
    // sur la class TextBox
    public class TextBox : Control, IDelete
    {
        // champ
        private string text;
        // propriété
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
        // constructeur
        public TextBox(string text)
        {
            this.text = text;
        }
        // méthodes
        public void AfficheText()
        {
            Console.WriteLine("Text = '" + text + "'");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du texte");
            text = String.Empty;
        }
    }
    // partiellement sur la class TreeView...
    public class TreeView : Control, IDelete
    {
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du TreeView");
        }
    }
    // formulaire
    public class MonFormulaire
    {
        // champs
        public TextBox tb = new TextBox("Rudy");
        public TreeView tv = new TreeView();
        public Control activeControl; // contrôle ayant le focus...

        // méthodes
        public void Delete()
        {
            if (activeControl is IDelete)
                ((IDelete)activeControl).Delete();
        }
        public void AfficheContenu()
        {
            if (activeControl is TextBox)
            {
                Console.WriteLine("TextBox");
                ((TextBox)activeControl).AfficheText();
            }
            else
                Console.WriteLine("Impossible d'afficher le contenu");
        }
    }

    class UtiliserInterface
    {
        static void Main(string[] args)
        {
            MonFormulaire f = new MonFormulaire();
            // focus au TextBox
            f.activeControl = f.tb;
            f.AfficheContenu(); // affiche TextBox et Text = 'Rudy'
            f.Delete(); // affiche Effacement du texte
            f.AfficheContenu(); // affiche TextBox et Text = ''
            // focus au TreeView
            f.activeControl = f.tv;
            f.AfficheContenu(); // affiche Impossible d'afficher le contenu
            f.Delete(); // affiche Effacement du TreeView
            f.AfficheContenu(); // affiche Impossible d'afficher le contenu
            Console.ReadKey();
        }

    }
}
TextBox
Text = 'Rudy'
Effacement du texte
TextBox
Text = ''
Impossible d'afficher le contenu
Effacement du TreeView
Impossible d'afficher le contenu
Cet exemple suppose un formulaire contenant plusieurs contrôles GUI (comprenant un contrôle TextBox et un contrôle TreeView), pour lequel le contrôle ayant le focus est accédé avec la propriété activeControl. Lorsque l'utilisateur clique sur un élément de menu ou sur un bouton de la barre d'outil, la méthode Delete du formulaire est lancée. L'exemple effectue un test pour s'assurer qu'activeControl implémente IDelete (activeControl is IDelete); si oui, l'exmple lance la méthode Delete d'IDelete (avec un cast : ((IDelete)activeControl).Delete())...

10.4. Combiner des interfaces

Les interfaces peuvent être combinées entre elles.
Code Résultat Remarques
using System;

namespace Exemples
{
    // définition de l'interface
    interface IDelete
    {
        void Delete();
    }
    // définition de la super interface
    interface ISuperDelete : IDelete
    {
        bool CanDelete { get; }
        event EventHandler CanDeleteChanged;
    }
    // implémentation de la super interface
    // sur la class TextBox
    public class TextBox : ISuperDelete
    {
        // champs
        private string text;
        private bool canDelete = true;
        // événement
        // Implémentation de l'événement CanDeleteChanged de l'interface ISuperDelete
        public event EventHandler CanDeleteChanged;
        // propriétés
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
        // Implémentation de la propriété CanDelete de l'interface ISuperDelete
        public bool CanDelete
        {
            get { return canDelete; }
            set
            {
                if (canDelete != value)
                {
                    // Déclenchement de l'événement lorsque la propriété change
                    EventArgs e = new EventArgs();
                    CanDeleteChanged(this, e);
                }
                canDelete = value;
            }
        }
        // constructeur
        public TextBox(string text)
        {
            this.text = text;
        }
        // méthodes
        public void AfficheProprietes()
        {
            Console.WriteLine("Text = '" + text + "'");
            if (CanDelete)
                Console.WriteLine("CanDelete = True");
            else
                Console.WriteLine("CanDelete = False");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            if (canDelete)
            {
                Console.WriteLine("Effacement du texte");
                text = String.Empty;
            }
            else
            {
                Console.WriteLine("Effacement impossible");
            }
        }
    }
    class CombinerInterfaces
    {
        static void tb_canDeleteChanged(object source, EventArgs e)
        {
            Console.WriteLine("Changement des droits de suppression");
        }
        static void Main(string[] args)
        {
            TextBox tb = new TextBox("Rudy");
            tb.CanDeleteChanged += new EventHandler(tb_canDeleteChanged);
            tb.AfficheProprietes(); // affiche Text = 'Rudy' et CanDelete = True
            tb.Delete(); // affiche Effacement du texte
            tb.AfficheProprietes(); // affiche Text = '' et CanDelete = True
            tb.Text = "Eric";
            tb.AfficheProprietes(); // affiche Text = 'Eric' et CanDelete = True
            tb.CanDelete = false; // affiche Changement des droits de suppression
            tb.Delete(); // affiche Effacement impossible
            tb.AfficheProprietes(); // affiche Text = 'Eric' et CanDelete = False
            Console.ReadKey();
        }
    }
}
Text = 'Rudy'
CanDelete = True
Effacement du texte
Text = ''
CanDelete = True
Text = 'Eric'
CanDelete = True
Changement des droits de suppression
Effacement impossible
Text = 'Eric'
CanDelete = False
Dans cet exemple, le contrôle implémente la propriété CanDelete pour indiquer qu'il y a quelque chose à effacer qui n'est pas en lecture seule et implémente l'événement CanDeleteChanged pour démarrer un événement lorsque sa propriété CanDelete change. Le framwork permet ainsi à notre application de griser son élément de menu Delete et son bouton dans la barre d'outils lorsque activeControl ne peut pas effacer...

10.5. Implémenter explicitement des interfaces

S'il y a un conflit de noms entre un membre d'interface et un membre existant dans la classe, C# vous permet d'implémenter explicitement un membre d'interface pour le résoudre.
Code Résultat Remarques
using System;

namespace Exemples
{
    // définition des l'interfaces
    interface IDelete1
    {
        void Delete();
    }
    interface IDelete2
    {
        void Delete();
    }
    // implémentation des 2 interfaces sur la classe Objet
    public class Objet : IDelete1, IDelete2
    {
        void IDelete1.Delete()
        {
            Console.WriteLine("IDelete1.Delete");
        }
        void IDelete2.Delete()
        {
            Console.WriteLine("IDelete2.Delete");
        }
    }
    class ImplementerExplicitementInterfaces
    {
        static void Main(string[] args)
        {
            Objet o = new Objet();
            IDelete1 id1 = (IDelete1)o;
            IDelete2 id2 = (IDelete2)o;
            id1.Delete(); // affiche IDelete1.Delete
            id2.Delete(); // affiche IDelete2.Delete
            Console.ReadKey();
        }
    }
}
IDelete1.Delete
IDelete2.Delete
Dans cet exemple, nous résolvons un conflit lors de l'implémentation de 2 interfaces définissant toutes 2 une méthode Delete. Contrairement aux implémentations implicites d'interfaces, les implémentations explicites d'interfaces ne peuvent pas être déclarées avec les modificateurs abstract, virtual, override ou new. De plus, alors qu'une implémentation implicite requiert l'utilisation du modificateur public, une implémentation explicite n'a pas de modificateur d'accès. Toutefois, pour accéder à la méthode, la classe ou la structure doit d'abord être projetée à l'interface appropriée.

10.6. Réimplémenter une interface

Si une classe de base implémente un membre d'interface avec le modificateur virtual (ou abstract), alors une classe héritée peut le surcharger. Sinon, la classe dérivée doit implémenter de nouveau l'interface pour surcharger ce membre.
Code Résultat Remarques
using System;

namespace Exemples
{
    // définition de l'interface
    interface IDelete
    {
        void Delete();
    }
    // implémentation de l'interface
    // sur la class TextBox
    public class TextBox : IDelete
    {
        // champ
        private string text;
        // propriété
        public string Text
        {
            get { return text; }
            set { text = value; }
        }
        // constructeur
        public TextBox(string text)
        {
            this.text = text;
        }
        // méthodes
        public void AfficheText()
        {
            Console.WriteLine("Text = '" + text + "'");
        }
        // implémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du texte");
            text = String.Empty;
        }
    }
    // réimplémentation de l'interface
    // sur la class RichTextBox
    public class RichTextBox : TextBox, IDelete
    {
        // constructeur
        public RichTextBox(string text)
            : base("")
        {
            Text = text;
        }
        // réimplémentation de la méthode Delete de l'interface IDelete
        public void Delete()
        {
            Console.WriteLine("Effacement du texte riche");
            Text = String.Empty;
        }
    }
    class ReimplementerInterface
    {
        static void Main(string[] args)
        {
            RichTextBox rtb = new RichTextBox("Rudy");
            rtb.AfficheText(); // affiche Text = 'Rudy'
            rtb.Delete(); // affiche Effacement du texte
            rtb.AfficheText(); // affiche Text = ''
            Console.ReadKey();
        }
    }
}
Text = 'Rudy'
Effacement du texte riche
Text = ''
Ceci nous permet d'utiliser un RichTextBox comme un IDelete et d'appeler la version Delete de RichTextBox.

10.7. Convertir une interface

Une classe ou une structure T peut être implicitement convertie en une interface I que T implémente. De même, une interface X peut être implicitement convertie en une interface Y pour laquelle X hérite. Une interface peut être convertie explicitement en une autre interface ou une classe non scellée (nonsealed). Toutefois, une conversion explicite d'une interface I en une classe scelée (sealed) ou une structure T n'est permise que s'il est possible que T puisse implémenter I.
Code Résultat Remarques
using System;

namespace Exemples
{
    // définition des l'interfaces
    interface IInterface1
    {
        void Methode1();
    }
    interface IInterface2
    {
        void Methode2();
    }
    // implémentation des 2 interfaces sur la classe Objet1
    public class Objet1 : IInterface1, IInterface2
    {
        public void Methode1()
        {
            Console.WriteLine("Objet1.Methode1");
        }
        public void Methode2()
        {
            Console.WriteLine("Objet1.Methode2");
        }
    }
    // implémentation de la première interfaces sur la classe Objet2
    public class Objet2 : IInterface1
    {
        public void Methode1()
        {
            Console.WriteLine("Objet2.Methode1");
        }
    }
    class ConvertirInterface
    {
        static void Main(string[] args)
        {
            Objet1 o1 = new Objet1();
            IInterface1 I1 = o1; // Convertion implicite
            IInterface2 I2 = (IInterface2)I1; // Conversion explicite
            Objet2 o2 = new Objet2();
            try
            {
                IInterface2 I22 = (IInterface2)o2;
            }
            catch (InvalidCastException e)
            {
                Console.WriteLine(e.Message);
            }
            Console.ReadKey();
        }
    }
}
Impossible d'effectuer un cast d'un objet de type 'Exemples.Objet2' en type 'Exemples.IInterface2'.
Voici un exemple de convertion d'interfaces...
Les conversion de boxing standard surviennent lors de la conversion entre des structures et des interfaces.