Ricerca…


Implementazione di un'interfaccia

Un'interfaccia viene utilizzata per forzare la presenza di un metodo in qualsiasi classe che lo "implementa". L'interfaccia è definita con l' interface per le parole chiave e una classe può "implementarla" aggiungendo : InterfaceName dopo il nome della classe. Una classe può implementare più interfacce separando ciascuna interfaccia con una virgola.
: 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";
    }
}

Poiché implementano INoiseMaker , sia il cat che il dog devono includere il metodo string MakeNoise() e non potranno compilare senza di esso.

Implementazione di più interfacce

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";
    }
}

Implementazione esplicita dell'interfaccia

L'implementazione esplicita dell'interfaccia è necessaria quando si implementano più interfacce che definiscono un metodo comune, ma sono necessarie implementazioni diverse a seconda dell'interfaccia utilizzata per chiamare il metodo (si noti che non sono necessarie implementazioni esplicite se più interfacce condividono lo stesso metodo e è possibile un'implementazione comune).

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'implementazione non può essere richiamata da nessun'altra parte se non utilizzando l'interfaccia:

public class Golfer : IGolfPlayer
{
    string IGolfPlayer.Drive()
    {
        return "Swinging hard...";
    }
    public void Swing()
    {
        Drive(); // Compiler error: No such method
    }
}

A causa di ciò, può essere vantaggioso inserire un codice di implementazione complesso di un'interfaccia esplicitamente implementata in un metodo privato separato.

Un'implementazione esplicita dell'interfaccia può naturalmente essere utilizzata solo per i metodi effettivamente esistenti per quell'interfaccia:

public class ProGolfer : IGolfPlayer
{
    string IGolfPlayer.Swear() // Error
    {
        return "The ball is in the pit";
    }
}

Allo stesso modo, l'utilizzo di un'implementazione esplicita dell'interfaccia senza dichiarare che l'interfaccia sulla classe causa anche un errore.

Suggerimento:

Implementare le interfacce in modo esplicito può anche essere usato per evitare il dead code. Quando un metodo non è più necessario e viene rimosso dall'interfaccia, il compilatore si lamenterà di ogni implementazione ancora esistente.

Nota:

I programmatori si aspettano che il contratto sia lo stesso indipendentemente dal contesto del tipo e l'implementazione esplicita non dovrebbe esporre un comportamento diverso quando viene chiamata. Quindi, a differenza dell'esempio sopra, IGolfPlayer.Drive e Drive dovrebbero fare la stessa cosa quando possibile.

Perché usiamo le interfacce

Un'interfaccia è una definizione di contratto tra l'utente dell'interfaccia e la classe che lo implementa. Un modo per pensare a un'interfaccia è come una dichiarazione che un oggetto può eseguire determinate funzioni.

Diciamo che definiamo un'interfaccia IShape per rappresentare diversi tipi di forme, ci aspettiamo che una forma abbia un'area, quindi definiremo un metodo per forzare le implementazioni dell'interfaccia a restituire la loro area:

public interface IShape
{
    double ComputeArea();
}

Vediamo che abbiamo le seguenti due forme: un Rectangle e 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;
    }
}

Ognuno di loro ha la propria definizione della propria area, ma entrambi sono forme. Quindi è logico vederli come IShape nel nostro programma:

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

Nozioni di base sull'interfaccia

La funzione di un'interfaccia nota come "contratto" di funzionalità. Significa che dichiara proprietà e metodi ma non li implementa.

Quindi, a differenza delle interfacce di classi:

  • Non può essere istanziato
  • Non può avere alcuna funzionalità
  • Può contenere solo metodi * (Proprietà ed Eventi sono metodi internamente)
  • L'ereditarietà di un'interfaccia si chiama "Implementazione"
  • È possibile ereditare da 1 classe, ma è possibile "Implementare" più interfacce
public interface ICanDoThis{
    void TheThingICanDo();
    int SomeValueProperty { get; set; }
}

Cose da notare:

  • Il prefisso "I" è una convenzione di denominazione utilizzata per le interfacce.
  • Il corpo della funzione viene sostituito con un punto e virgola ";".
  • Le proprietà sono anche permesse perché internamente sono anche metodi
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

Ciò è particolarmente utile quando si lavora con framework UI come WinForms o WPF perché è obbligatorio ereditare da una classe base per creare il controllo utente e si perde la possibilità di creare astrazioni su diversi tipi di controllo. Un esempio? In arrivo:

public class MyTextBlock : TextBlock {
    public void SetText(string str){
        this.Text = str;
    }
}

public class MyButton : Button {
    public void SetText(string str){
        this.Content = str;
    }
}

Il problema proposto è che entrambi contengono un concetto di "Testo" ma i nomi delle proprietà differiscono. E non puoi creare creare una classe base astratta perché hanno un'eredità obbligatoria per 2 classi diverse. Un'interfaccia può alleggerirlo

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; }
}

Ora MyButton e MyTextBlock sono intercambiabili.

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

   
}

"Nascondere" i membri con implementazione esplicita

Non lo odi quando le interfacce inquinano la tua classe con troppi membri di cui non ti importa nemmeno? Bene, ho una soluzione! Implementazioni esplicite

public interface IMessageService {
    void OnMessageRecieve();
    void SendMessage();
    string Result { get; set; }
    int Encoding { get; set; }
    // yadda yadda
}

Normalmente dovresti implementare la classe in questo modo.

public class MyObjectWithMessages : IMessageService {
     public void OnMessageRecieve(){

     }

     public void SendMessage(){

     }

     public string Result { get; set; }
     public int Encoding { get; set; }
}

Ogni membro è pubblico.

var obj = new MyObjectWithMessages();

// why would i want to call this function?
obj.OnMessageRecieve();

Risposta: non lo so Quindi nessuno dei due dovrebbe essere dichiarato pubblico, ma semplicemente dichiarare i membri come privati ​​farà sì che il compilatore lanci un errore

La soluzione è usare un'implementazione esplicita:

public class MyObjectWithMessages : IMessageService{
    void IMessageService.OnMessageRecieve() {
        
    }

    void IMessageService.SendMessage() {
        
    }

    string IMessageService.Result { get; set; }
    int IMessageService.Encoding { get; set; }
}

Quindi ora hai implementato i membri come richiesto e non espongono alcun membro come pubblico.

var obj = new MyObjectWithMessages();

/* error member does not exist on type MyObjectWithMessages. 
 * We've succesfully made it "private" */
obj.OnMessageRecieve();

Se si desidera ancora accedere seriamente al membro anche se è esplicitamente implementato tutto ciò che si deve fare è gettare l'oggetto nell'interfaccia e si è pronti per andare.

((IMessageService)obj).OnMessageRecieve();

IComparable come esempio di implementazione di un'interfaccia

Le interfacce possono sembrare astratte fino a quando non le appari in pratica. I IComparable<T> IComparable e IComparable<T> sono ottimi esempi del perché le interfacce possono essere utili a noi.

Diciamo che in un programma per un negozio online, abbiamo una varietà di oggetti che puoi comprare. Ogni articolo ha un nome, un numero identificativo e un prezzo.

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        

}

Abbiamo i nostri Item s memorizzati all'interno di un List<Item> e nel nostro programma da qualche parte, vogliamo ordinare il nostro elenco per numero ID dal più piccolo al più grande. Invece di scrivere il nostro algoritmo di ordinamento, possiamo invece usare il metodo Sort() che List<T> ha già. Tuttavia, poiché la nostra classe Item è in questo momento, non c'è modo per List<T> di capire quale ordine ordinare l'elenco. Qui è dove entra in IComparable interfaccia IComparable .

Per implementare correttamente il metodo CompareTo , CompareTo dovrebbe restituire un numero positivo se il parametro è "minore di" quello corrente, zero se sono uguali e un numero negativo se il parametro è "maggiore di".

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

Ecco l'esempio Item implementazione 's dell'interfaccia:

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    

}

A livello di superficie, il metodo CompareTo nel nostro articolo restituisce semplicemente la differenza nei loro numeri ID, ma cosa fa in pratica quanto sopra?

Ora, quando chiamiamo Sort() su un oggetto List<Item> , List chiamerà automaticamente il metodo CompareTo Item quando deve determinare in che ordine inserire gli oggetti. Inoltre, oltre a List<T> , qualsiasi altro oggetto che ha bisogno della capacità di confrontare due oggetti funzionerà con l' Item perché abbiamo definito la capacità di confrontare due Item diversi tra loro.



Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow