C# Language
Interfaces
Recherche…
Implémenter une interface
Une interface est utilisée pour imposer la présence d'une méthode dans toute classe qui l'implémente. L'interface est définie avec le mot clé interface
et une classe peut l'implémenter en ajoutant : InterfaceName
après le nom de la classe. Une classe peut implémenter plusieurs interfaces en séparant chaque interface par une virgule.
: InterfaceName, ISecondInterface
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : INoiseMaker
{
public string MakeNoise()
{
return "Nyan";
}
}
public class Dog : INoiseMaker
{
public string MakeNoise()
{
return "Woof";
}
}
Comme ils implémentent INoiseMaker
, cat
et dog
doivent tous deux inclure la string MakeNoise()
et ne pourront pas être compilés sans cette méthode.
Implémentation de plusieurs interfaces
public interface IAnimal
{
string Name { get; set; }
}
public interface INoiseMaker
{
string MakeNoise();
}
public class Cat : IAnimal, INoiseMaker
{
public Cat()
{
Name = "Cat";
}
public string Name { get; set; }
public string MakeNoise()
{
return "Nyan";
}
}
Implémentation d'interface explicite
Une implémentation d'interface explicite est nécessaire lorsque vous implémentez plusieurs interfaces qui définissent une méthode commune, mais différentes implémentations sont requises selon l'interface utilisée pour appeler la méthode (notez que vous n'avez pas besoin d'implémentations explicites si plusieurs interfaces partagent la même méthode et une implémentation commune est possible).
interface IChauffeur
{
string Drive();
}
interface IGolfPlayer
{
string Drive();
}
class GolfingChauffeur : IChauffeur, IGolfPlayer
{
public string Drive()
{
return "Vroom!";
}
string IGolfPlayer.Drive()
{
return "Took a swing...";
}
}
GolfingChauffeur obj = new GolfingChauffeur();
IChauffeur chauffeur = obj;
IGolfPlayer golfer = obj;
Console.WriteLine(obj.Drive()); // Vroom!
Console.WriteLine(chauffeur.Drive()); // Vroom!
Console.WriteLine(golfer.Drive()); // Took a swing...
L'implémentation ne peut pas être appelée de n'importe où sauf en utilisant l'interface:
public class Golfer : IGolfPlayer
{
string IGolfPlayer.Drive()
{
return "Swinging hard...";
}
public void Swing()
{
Drive(); // Compiler error: No such method
}
}
De ce fait, il peut être avantageux de placer le code d'implémentation complexe d'une interface explicitement implémentée dans une méthode séparée et privée.
Une implémentation d'interface explicite ne peut bien entendu être utilisée que pour les méthodes existantes pour cette interface:
public class ProGolfer : IGolfPlayer
{
string IGolfPlayer.Swear() // Error
{
return "The ball is in the pit";
}
}
De même, l'utilisation d'une implémentation d'interface explicite sans déclarer cette interface sur la classe provoque également une erreur.
Allusion:
L'implémentation d'interfaces peut également être utilisée pour éviter le code mort. Lorsqu'une méthode n'est plus nécessaire et est supprimée de l'interface, le compilateur se plaindra de chaque implémentation existante.
Remarque:
Les programmeurs s'attendent à ce que le contrat soit le même, quel que soit le contexte du type, et une implémentation explicite ne devrait pas exposer un comportement différent à l'appel. Donc, contrairement à l'exemple ci-dessus, IGolfPlayer.Drive
et Drive
devraient faire la même chose lorsque cela est possible.
Pourquoi nous utilisons des interfaces
Une interface est une définition d'un contrat entre l'utilisateur de l'interface et la classe qui l'implémente. Une façon de penser à une interface est de déclarer qu'un objet peut exécuter certaines fonctions.
Disons que nous définissons une interface IShape
pour représenter différents types de formes, nous attendons une forme pour avoir une zone, nous allons donc définir une méthode pour forcer les implémentations d'interface à renvoyer leur zone:
public interface IShape
{
double ComputeArea();
}
Disons que nous avons les deux formes suivantes: un Rectangle
et un Circle
public class Rectangle : IShape
{
private double length;
private double width;
public Rectangle(double length, double width)
{
this.length = length;
this.width = width;
}
public double ComputeArea()
{
return length * width;
}
}
public class Circle : IShape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}
public double ComputeArea()
{
return Math.Pow(radius, 2.0) * Math.PI;
}
}
Chacun d'entre eux a sa propre définition de son aire, mais les deux sont des formes. Il est donc logique de les voir comme IShape
dans notre programme:
private static void Main(string[] args)
{
var shapes = new List<IShape>() { new Rectangle(5, 10), new Circle(5) };
ComputeArea(shapes);
Console.ReadKey();
}
private static void ComputeArea(IEnumerable<IShape> shapes)
{
foreach (shape in shapes)
{
Console.WriteLine("Area: {0:N}, shape.ComputeArea());
}
}
// Output:
// Area : 50.00
// Area : 78.54
Bases de l'interface
Une fonction d'interface appelée "contrat" de fonctionnalité. Cela signifie qu'il déclare des propriétés et des méthodes mais ne les implémente pas.
Donc, contrairement aux classes Interfaces:
- Ne peut être instancié
- Ne peut avoir aucune fonctionnalité
- Ne peut contenir que des méthodes * (les propriétés et les événements sont des méthodes internes)
- L'héritage d'une interface s'appelle "Implementing"
- Vous pouvez hériter d'une classe, mais vous pouvez "implémenter" plusieurs interfaces
public interface ICanDoThis{
void TheThingICanDo();
int SomeValueProperty { get; set; }
}
Choses à noter:
- Le préfixe "I" est une convention de dénomination utilisée pour les interfaces.
- Le corps de la fonction est remplacé par un point-virgule ";".
- Les propriétés sont également autorisées car en interne elles sont aussi des méthodes
public class MyClass : ICanDoThis {
public void TheThingICanDo(){
// do the thing
}
public int SomeValueProperty { get; set; }
public int SomeValueNotImplemtingAnything { get; set; }
}
.
ICanDoThis obj = new MyClass();
// ok
obj.TheThingICanDo();
// ok
obj.SomeValueProperty = 5;
// Error, this member doesn't exist in the interface
obj.SomeValueNotImplemtingAnything = 5;
// in order to access the property in the class you must "down cast" it
((MyClass)obj).SomeValueNotImplemtingAnything = 5; // ok
Ceci est particulièrement utile lorsque vous travaillez avec des frameworks d'interface utilisateur tels que WinForms ou WPF car il est obligatoire d'hériter d'une classe de base pour créer un contrôle utilisateur et vous perdez la possibilité de créer des abstractions sur différents types de contrôles. Un exemple? À venir
public class MyTextBlock : TextBlock {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button {
public void SetText(string str){
this.Content = str;
}
}
Le problème proposé est que les deux contiennent un concept de "texte" mais les noms de propriété diffèrent. Et vous ne pouvez pas créer une classe de base abstraite car ils ont un héritage obligatoire pour 2 classes différentes. Une interface peut atténuer cela
public interface ITextControl{
void SetText(string str);
}
public class MyTextBlock : TextBlock, ITextControl {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button, ITextControl {
public void SetText(string str){
this.Content = str;
}
public int Clicks { get; set; }
}
Maintenant, MyButton et MyTextBlock sont interchangeables.
var controls = new List<ITextControls>{
new MyTextBlock(),
new MyButton()
};
foreach(var ctrl in controls){
ctrl.SetText("This text will be applied to both controls despite them being different");
// Compiler Error, no such member in interface
ctrl.Clicks = 0;
// Runtime Error because 1 class is in fact not a button which makes this cast invalid
((MyButton)ctrl).Clicks = 0;
/* the solution is to check the type first.
This is usually considered bad practice since
it's a symptom of poor abstraction */
var button = ctrl as MyButton;
if(button != null)
button.Clicks = 0; // no errors
}
"Cacher" les membres avec une implémentation explicite
Ne le détestez-vous pas lorsque les interfaces vous polluent la classe avec trop de membres dont vous ne vous souciez même pas? Eh bien, j'ai une solution! Implémentations explicites
public interface IMessageService {
void OnMessageRecieve();
void SendMessage();
string Result { get; set; }
int Encoding { get; set; }
// yadda yadda
}
Normalement, vous implémenteriez la classe comme ceci.
public class MyObjectWithMessages : IMessageService {
public void OnMessageRecieve(){
}
public void SendMessage(){
}
public string Result { get; set; }
public int Encoding { get; set; }
}
Chaque membre est public
var obj = new MyObjectWithMessages();
// why would i want to call this function?
obj.OnMessageRecieve();
Réponse: je ne sais pas Donc, il ne devrait pas non plus être déclaré public, mais le simple fait de déclarer les membres comme privés entraînera une erreur du compilateur.
La solution consiste à utiliser une implémentation explicite:
public class MyObjectWithMessages : IMessageService{
void IMessageService.OnMessageRecieve() {
}
void IMessageService.SendMessage() {
}
string IMessageService.Result { get; set; }
int IMessageService.Encoding { get; set; }
}
Alors maintenant, vous avez implémenté les membres comme il se doit et ils n'exposent aucun membre en tant que public.
var obj = new MyObjectWithMessages();
/* error member does not exist on type MyObjectWithMessages.
* We've succesfully made it "private" */
obj.OnMessageRecieve();
Si vous voulez sérieusement accéder au membre même s'il est explicitement implémenté, tout ce que vous avez à faire est de placer l'objet sur l'interface et de continuer.
((IMessageService)obj).OnMessageRecieve();
IComparable comme exemple d'implémentation d'une interface
Les interfaces peuvent sembler abstraites jusqu'à ce que vous les apparaissiez en pratique. IComparable
et IComparable<T>
sont d'excellents exemples de la raison pour laquelle les interfaces peuvent nous être utiles.
Disons que dans un programme pour une boutique en ligne, nous avons une variété d'articles que vous pouvez acheter. Chaque article a un nom, un numéro d'identification et un prix.
public class Item {
public string name; // though public variables are generally bad practice,
public int idNumber; // to keep this example simple we will use them instead
public decimal price; // of a property.
// body omitted for brevity
}
Nous avons nos Item
stockés dans une List<Item>
, et dans notre programme quelque part, nous voulons trier notre liste par numéro d'identification du plus petit au plus grand. Au lieu d'écrire notre propre algorithme de tri, nous pouvons utiliser la méthode Sort()
que List<T>
déjà. Cependant, comme notre classe Item
est en ce moment, la List<T>
pas de comprendre l'ordre de tri de la liste. Voici où l'interface IComparable
entre en jeu.
Pour implémenter correctement la méthode CompareTo
, CompareTo
doit renvoyer un nombre positif si le paramètre est "inférieur à" l'actuel, zéro s'il est égal et un nombre négatif si le paramètre est "supérieur à".
Item apple = new Item();
apple.idNumber = 15;
Item banana = new Item();
banana.idNumber = 4;
Item cow = new Item();
cow.idNumber = 15;
Item diamond = new Item();
diamond.idNumber = 18;
Console.WriteLine(apple.CompareTo(banana)); // 11
Console.WriteLine(apple.CompareTo(cow)); // 0
Console.WriteLine(apple.CompareTo(diamond)); // -3
Voici l'exemple Item
de mise en œuvre de l'interface »:
public class Item : IComparable<Item> {
private string name;
private int idNumber;
private decimal price;
public int CompareTo(Item otherItem) {
return (this.idNumber - otherItem.idNumber);
}
// rest of code omitted for brevity
}
Au niveau de la surface, la méthode CompareTo
de notre article renvoie simplement la différence entre leurs numéros d’identification, mais que fait-il dans la pratique?
Maintenant, quand nous appelons Sort()
sur une List<Item>
objet, la List
appellera automatiquement le Item
de CompareTo
méthode quand il a besoin de déterminer quel ordre de mettre des objets. De plus, en plus de la List<T>
, tout autre objet ceux qui ont besoin de la possibilité de comparer deux objets fonctionneront avec l' Item
car nous avons défini la possibilité de comparer deux Item
différents les uns avec les autres.