C# Language
interfaces
Zoeken…
Een interface implementeren
Een interface wordt gebruikt om de aanwezigheid van een methode af te dwingen in elke klasse die deze 'implementeert'. De interface wordt gedefinieerd met het trefwoord interface
en een klasse kan 'implementeren' door toevoeging van : InterfaceName
na de naam van de klasse. Een klasse kan meerdere interfaces implementeren door elke interface met een komma te scheiden.
: 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";
}
}
Omdat ze INoiseMaker
implementeren, moeten zowel cat
als dog
de string MakeNoise()
-methode opnemen en kunnen ze zonder deze niet compileren.
Implementeren van meerdere 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";
}
}
Expliciete interface-implementatie
Expliciete interface-implementatie is nodig wanneer u meerdere interfaces implementeert die een gemeenschappelijke methode definiëren, maar verschillende implementaties zijn vereist, afhankelijk van welke interface wordt gebruikt om de methode aan te roepen (merk op dat u geen expliciete implementaties nodig hebt als meerdere interfaces dezelfde methode delen en een gemeenschappelijke implementatie is mogelijk).
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...
De implementatie kan nergens anders worden aangeroepen, behalve via de interface:
public class Golfer : IGolfPlayer
{
string IGolfPlayer.Drive()
{
return "Swinging hard...";
}
public void Swing()
{
Drive(); // Compiler error: No such method
}
}
Hierdoor kan het voordelig zijn om complexe implementatiecode van een expliciet geïmplementeerde interface in een afzonderlijke, private methode te plaatsen.
Een expliciete interface-implementatie kan natuurlijk alleen worden gebruikt voor methoden die daadwerkelijk voor die interface bestaan:
public class ProGolfer : IGolfPlayer
{
string IGolfPlayer.Swear() // Error
{
return "The ball is in the pit";
}
}
Evenzo veroorzaakt het gebruik van een expliciete interface-implementatie zonder te verklaren dat die interface in de klasse een fout veroorzaakt.
Tip:
Het expliciet implementeren van interfaces kan ook worden gebruikt om dode code te voorkomen. Wanneer een methode niet langer nodig is en uit de interface wordt verwijderd, zal de compiler klagen over elke nog bestaande implementatie.
Notitie:
Programmeurs verwachten dat het contract hetzelfde is, ongeacht de context van het type en expliciete implementatie mag geen ander gedrag vertonen wanneer het wordt aangeroepen. Dus in tegenstelling tot het bovenstaande voorbeeld, moeten IGolfPlayer.Drive
en Drive
hetzelfde doen wanneer mogelijk.
Waarom we interfaces gebruiken
Een interface is een definitie van een contract tussen de gebruiker van de interface en de klasse die deze implementeert. Een manier om aan een interface te denken, is als een verklaring dat een object bepaalde functies kan uitvoeren.
Laten we zeggen dat we een interface IShape
definiëren die een ander type vormen vertegenwoordigt, we verwachten dat een vorm een gebied heeft, dus we zullen een methode definiëren om de interface-implementaties te dwingen hun gebied terug te geven:
public interface IShape
{
double ComputeArea();
}
Laten we de volgende twee vormen hebben: een Rectangle
en een 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;
}
}
Elk van hen heeft zijn eigen definitie van zijn gebied, maar beide zijn vormen. Het is dus logisch om ze als IShape
in ons programma te zien:
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
Interface Basics
De functie van een interface die bekend staat als een "contract" van functionaliteit. Het betekent dat het eigenschappen en methoden declareert, maar deze niet implementeert.
Dus in tegenstelling tot klassen Interfaces:
- Kan niet worden geïnstantieerd
- Kan geen functionaliteit hebben
- Kan alleen methoden bevatten * (eigenschappen en gebeurtenissen zijn intern methoden)
- Het overnemen van een interface wordt "Implementeren" genoemd
- U kunt erven van 1 klasse, maar u kunt meerdere interfaces "implementeren"
public interface ICanDoThis{
void TheThingICanDo();
int SomeValueProperty { get; set; }
}
Dingen om op te merken:
- Het voorvoegsel "I" is een naamgevingsconventie die wordt gebruikt voor interfaces.
- De functietekst wordt vervangen door een puntkomma ";".
- Eigenschappen zijn ook toegestaan omdat het intern ook methoden zijn
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
Dit is vooral handig als u met UI-frameworks zoals WinForms of WPF werkt, omdat het verplicht is om van een basisklasse te erven om gebruikersbeheer te maken en u de mogelijkheid verliest om abstractie over verschillende soorten besturingselementen te maken. Een voorbeeld? Binnenkort:
public class MyTextBlock : TextBlock {
public void SetText(string str){
this.Text = str;
}
}
public class MyButton : Button {
public void SetText(string str){
this.Content = str;
}
}
Het voorgestelde probleem is dat beide een concept van "tekst" bevatten, maar de eigenschapsnamen verschillen. En u kunt geen abstracte basisklasse maken, omdat ze een verplichte erfenis hebben voor 2 verschillende klassen. Een interface kan dat verlichten
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; }
}
Nu zijn MyButton en MyTextBlock uitwisselbaar.
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
}
"Verbergen" van leden met expliciete implementatie
Heb je er geen hekel aan als interfaces je klas vervuilen met te veel leden waar je niet eens om geeft? Nou ik heb een oplossing! Expliciete implementaties
public interface IMessageService {
void OnMessageRecieve();
void SendMessage();
string Result { get; set; }
int Encoding { get; set; }
// yadda yadda
}
Normaal zou je de klasse zo implementeren.
public class MyObjectWithMessages : IMessageService {
public void OnMessageRecieve(){
}
public void SendMessage(){
}
public string Result { get; set; }
public int Encoding { get; set; }
}
Elk lid is openbaar.
var obj = new MyObjectWithMessages();
// why would i want to call this function?
obj.OnMessageRecieve();
Antwoord: Ik niet. Het moet dus ook niet openbaar worden gemaakt, maar alleen als de leden als privé worden verklaard, zal de compiler een fout maken
De oplossing is om expliciete implementatie te gebruiken:
public class MyObjectWithMessages : IMessageService{
void IMessageService.OnMessageRecieve() {
}
void IMessageService.SendMessage() {
}
string IMessageService.Result { get; set; }
int IMessageService.Encoding { get; set; }
}
Dus nu hebt u de leden naar wens geïmplementeerd en zullen ze geen leden openbaar maken.
var obj = new MyObjectWithMessages();
/* error member does not exist on type MyObjectWithMessages.
* We've succesfully made it "private" */
obj.OnMessageRecieve();
Als je serieus nog steeds toegang wilt krijgen tot het lid, hoewel expliciet wordt geïmplementeerd, hoef je alleen maar het object naar de interface te casten en je bent klaar om te gaan.
((IMessageService)obj).OnMessageRecieve();
IComparable als een voorbeeld van het implementeren van een interface
Interfaces kunnen abstract lijken totdat u ze in de praktijk lijkt. De IComparable
en IComparable<T>
zijn geweldige voorbeelden van waarom interfaces ons kunnen helpen.
Laten we zeggen dat we in een programma voor een online winkel verschillende items hebben die u kunt kopen. Elk item heeft een naam, een ID-nummer en een prijs.
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
}
We hebben onze Item
opgeslagen in een List<Item>
, en in ons programma willen we onze lijst op ID-nummer sorteren van klein naar groot. In plaats van ons eigen sorteeralgoritme te schrijven, kunnen we in plaats daarvan de methode Sort()
gebruiken die List<T>
al heeft. Omdat onze Item
momenteel bestaat, kan de List<T>
niet begrijpen in welke volgorde de lijst moet worden gesorteerd. Hier komt de IComparable
interface binnen.
Om de CompareTo
methode correct te implementeren, moet CompareTo
een positief getal retourneren als de parameter "kleiner dan" is dan de huidige, nul als ze gelijk zijn en een negatief getal als de parameter "groter dan" is.
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
Hier is het voorbeeld van de implementatie van het Item
van de 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
}
Op oppervlakniveau CompareTo
methode CompareTo
in ons artikel eenvoudig het verschil in ID-nummer, maar wat doet het bovenstaande in de praktijk?
Nu, als we noemen Sort()
op een List<Item>
object, de List
zal automatisch de bel Item
's CompareTo
methode wanneer het nodig om te bepalen welke volgorde om objecten in te zetten. Bovendien, naast List<T>
, andere voorwerpen die de mogelijkheid nodig hebben om twee objecten te vergelijken, werken met het Item
omdat we de mogelijkheid hebben gedefinieerd om twee verschillende Item
s met elkaar te vergelijken.