Perché implementare l'interfaccia in modo esplicito?


122

Allora, qual è esattamente un buon caso d'uso per implementare esplicitamente un'interfaccia?

È solo così che le persone che usano la classe non devono guardare tutti quei metodi / proprietà in intellisense?

Risposte:


146

Se si implementano due interfacce, entrambe con lo stesso metodo e diverse implementazioni, è necessario implementarle esplicitamente.

public interface IDoItFast
{
    void Go();
}
public interface IDoItSlow
{
    void Go();
}
public class JustDoIt : IDoItFast, IDoItSlow
{
    void IDoItFast.Go()
    {
    }

    void IDoItSlow.Go()
    {
    }
}

Sì, esattamente questo è un caso che EIMI risolve. E altri punti sono coperti dalla risposta "Michael B".
criptato il

10
Ottimo esempio. Adoro i nomi dell'interfaccia / classe! :-)
Brian Rogers

11
Non mi piace, due metodi con la stessa firma in una classe che fanno cose molto diverse? Questa è roba estremamente pericolosa ed è molto probabile che causerà il caos in qualsiasi sviluppo di grandi dimensioni. Se hai un codice come questo, direi che la tua analisi e il tuo design sono il wahzoo.
Mick

4
@Mike Le interfacce potrebbero appartenere ad alcune API o a due API differenti. Forse l' amore è un po 'esagerato qui, ma almeno sarei felice che sia disponibile un'implementazione esplicita.
TobiMcNamobi

@BrianRogers E anche i nomi dei metodi ;-)
Sнаđошƒаӽ

66

È utile nascondere il membro non preferito. Ad esempio, se si implementano entrambi IComparable<T>e di IComparablesolito è meglio nascondere il IComparablesovraccarico per non dare alle persone l'impressione di poter confrontare oggetti di diverso tipo. Allo stesso modo, alcune interfacce non sono conformi a CLS IConvertible, quindi se non si implementa esplicitamente l'interfaccia, gli utenti finali dei linguaggi che richiedono la conformità a CLS non possono utilizzare l'oggetto. (Il che sarebbe molto disastroso se gli implementatori di BCL non nascondessero i membri IConvertible delle primitive :))

Un'altra nota interessante è che normalmente l'uso di un tale costrutto significa che la struttura che implementa esplicitamente un'interfaccia può solo invocarla inserendola nel tipo di interfaccia. Puoi aggirare questo problema utilizzando vincoli generici:

void SomeMethod<T>(T obj) where T:IConvertible

Non inscatolerà un int quando ne passi uno.


1
Hai un errore di battitura nel tuo vincolo. Per chiarire, il codice sopra funziona. Deve essere nella dichiarazione iniziale della firma del metodo nell'interfaccia. Il post originale non lo specificava. Inoltre, il formato corretto è "void SomeMehtod <T> (T obj) dove T: IConvertible. Nota, sono presenti due punti in più tra") "e" dove "che non dovrebbero essere lì. Ancora, +1 per un uso intelligente di farmaci generici per evitare costosi boxe.
Zack Jannsen

1
Ciao Michael B. Allora perché nell'implementazione della stringa in .NET c'è l'implementazione pubblica di IComparable: public int CompareTo (Object value) {if (value == null) {return 1; } if (! (value is String)) {throw new ArgumentException (Environment.GetResourceString ("Arg_MustBeString")); } return String.Compare (this, (String) value, StringComparison.CurrentCulture); } Grazie!
zzfima

stringera in giro prima dei generici e questa pratica era in voga. Quando .net 2 è uscito, non volevano interrompere l'interfaccia pubblica di, stringquindi l'hanno lasciato così com'era con la protezione in atto.
Michael B

37

Alcuni motivi aggiuntivi per implementare un'interfaccia in modo esplicito:

compatibilità con le versioni precedenti : nel caso in cui l' ICloneableinterfaccia cambi, l'implementazione dei membri della classe del metodo non deve modificare le firme del metodo.

codice più pulito : ci sarà un errore del compilatore se il Clonemetodo viene rimosso da ICloneable, tuttavia se implementi il ​​metodo implicitamente puoi ritrovarti con metodi pubblici 'orfani' non utilizzati

digitazione forte : per illustrare la storia di supercat con un esempio, questo sarebbe il mio codice di esempio preferito, l'implementazione ICloneableesplicita consente Clone()di essere fortemente tipizzato quando lo chiami direttamente come MyObjectmembro dell'istanza:

public class MyObject : ICloneable
{
  public MyObject Clone()
  {
    // my cloning logic;  
  }

  object ICloneable.Clone()
  {
    return this.Clone();
  }
}

Per quello, preferirei interface ICloneable<out T> { T Clone(); T self {get;} }. Nota che non c'è deliberatamente alcun ICloneable<T>vincolo su T. Mentre un oggetto può generalmente essere clonato in modo sicuro solo se la sua base può esserlo, si potrebbe desiderare di derivare da una classe base che potrebbe essere clonata in modo sicuro un oggetto di una classe che non può. Per consentire ciò, consiglierei di non avere classi ereditabili che espongono un metodo clone pubblico. Hanno invece classi ereditabili con un protectedmetodo di clonazione e classi sealed che derivano da esse ed espongono la clonazione pubblica.
supercat

Certo che sarebbe più bello, tranne che non esiste una versione covariante di ICloneable nel BCL, quindi dovresti crearne uno giusto?
Wiebe Tijsma

Tutti e 3 gli esempi dipendono da situazioni improbabili e dalle migliori pratiche delle interfacce.
MickyD

13

Un'altra tecnica utile è fare in modo che l'implementazione pubblica di una funzione di un metodo restituisca un valore più specifico di quello specificato in un'interfaccia.

Ad esempio, un oggetto può essere implementato ICloneable, ma il suo Clonemetodo visibile pubblicamente restituisce il proprio tipo.

Allo stesso modo, an IAutomobileFactorypotrebbe avere un Manufacturemetodo che restituisce an Automobile, ma a FordExplorerFactory, che implementa IAutomobileFactory, potrebbe avere il suo Manufacturemetodo restituire a FordExplorer(che deriva da Automobile). Codice che sa di avere una proprietà -specific che FordExplorerFactorypotrebbe usare FordExplorersu un oggetto restituito da a FordExplorerFactorysenza dover digitare il typecast, mentre il codice che semplicemente sapeva di avere un tipo di proprietà IAutomobileFactorysi occuperebbe semplicemente del suo ritorno come Automobile.


2
+1 ... questo sarebbe il mio utilizzo preferito delle implementazioni dell'interfaccia esplicita, anche se un piccolo esempio di codice sarebbe probabilmente un po 'più chiaro di questa storia :)
Wiebe Tijsma

7

È utile anche quando si hanno due interfacce con lo stesso nome e firma del membro, ma si desidera modificarne il comportamento a seconda di come vengono utilizzate. (Non consiglio di scrivere codice come questo):

interface Cat
{
    string Name {get;}
}

interface Dog
{
    string Name{get;}
}

public class Animal : Cat, Dog
{
    string Cat.Name
    {
        get
        {
            return "Cat";
        }
    }

    string Dog.Name
    {
        get
        {
            return "Dog";
        }
    }
}
static void Main(string[] args)
{
    Animal animal = new Animal();
    Cat cat = animal; //Note the use of the same instance of Animal. All we are doing is picking which interface implementation we want to use.
    Dog dog = animal;
    Console.WriteLine(cat.Name); //Prints Cat
    Console.WriteLine(dog.Name); //Prints Dog
}

60
il più strano esempio relativo all'OO che abbia mai visto:public class Animal : Cat, Dog
mbx

38
@mbx: Se Animal implementasse anche Parrot, sarebbe un animale che si trasforma in Polly.
RenniePet

3
Ricordo un personaggio dei cartoni animati che era un gatto da un lato e un cane dall'altro ;-)
George Birbilis

2
c'era una serie TV negli anni '80 .. "Manimal" .. dove un uomo poteva trasformarsi in un .. oh non
importa

I Morkies sembrano un po 'gatti, e sono anche gatti ninja.
samis

6

Può mantenere l'interfaccia pubblica più pulita per implementare esplicitamente un'interfaccia, cioè la tua Fileclasse potrebbe implementarla IDisposableesplicitamente e fornire un metodo pubblico Close()che potrebbe avere più senso per un consumatore di Dispose().

F # offre solo l' implementazione esplicita dell'interfaccia, quindi devi sempre eseguire il cast alla particolare interfaccia per accedere alla sua funzionalità, il che rende molto esplicito (senza giochi di parole) l'uso dell'interfaccia.


Penso che la maggior parte delle versioni di VB supportasse anche solo definizioni di interfacce esplicite.
Gabe

1
@Gabe: è più sottile di quello per VB: la denominazione e l'accessibilità dei membri che implementano un'interfaccia sono separate dall'indicare che fanno parte dell'implementazione. Quindi in VB, e guardando la risposta di @ Iain (attuale risposta principale), potresti implementare IDoItFast e IDoItSlow con i membri pubblici "GoFast" e "GoSlow", rispettivamente.
Damien_The_Unbeliever

2
Non mi piace il tuo esempio particolare (IMHO, le uniche cose che dovrebbero nascondere Disposesono quelle che non richiederanno mai la pulizia); un esempio migliore sarebbe qualcosa come l'implementazione di una raccolta immutabile di IList<T>.Add.
supercat

5

Se si dispone di un'interfaccia interna e non si desidera implementare pubblicamente i membri della classe, è necessario implementarli in modo esplicito. Le implementazioni implicite devono essere pubbliche.


Ok, questo spiega perché il progetto non sarebbe stato compilato con un'implementazione implicita.
pauloya

4

Un altro motivo per l'implementazione esplicita è per la manutenibilità .

Quando una classe diventa "impegnata" - sì, succede, non tutti abbiamo il lusso di refactoring del codice degli altri membri del team - quindi avere un'implementazione esplicita rende chiaro che c'è un metodo per soddisfare un contratto di interfaccia.

Quindi migliora la "leggibilità" del codice.


Secondo me è più importante decidere se la classe debba o meno esporre il metodo ai propri clienti. Ciò determina se essere esplicito o implicito. Per documentare che diversi metodi appartengono insieme, in questo caso perché soddisfano un contratto - questo #regionè ciò che serve, con una stringa del titolo appropriata. E un commento sul metodo.
ToolmakerSteve

1

Un esempio diverso è fornito da System.Collections.Immutable, in cui gli autori hanno scelto di utilizzare la tecnica per preservare un'API familiare per i tipi di raccolta mentre raschiavano via le parti dell'interfaccia che non hanno alcun significato per i loro nuovi tipi.

Concretamente, ImmutableList<T>attrezzi IList<T>e quindi ICollection<T>( al fine di consentire ImmutableList<T>un utilizzo più facilmente con il codice legacy), ma void ICollection<T>.Add(T item)non ha senso per ImmutableList<T>: poiché l'aggiunta di un elemento di una lista immutabile non deve modificare l'elenco esistente, ImmutableList<T>anche deriva da IImmutableList<T>cui IImmutableList<T> Add(T item)può essere utilizzato per elenchi immutabili.

Quindi, nel caso di Add, le implementazioni ImmutableList<T>finiscono per apparire come segue:

public ImmutableList<T> Add(T item)
{
    // Create a new list with the added item
}

IImmutableList<T> IImmutableList<T>.Add(T value) => this.Add(value);

void ICollection<T>.Add(T item) => throw new NotSupportedException();

int IList.Add(object value) => throw new NotSupportedException();

0

In caso di interfacce definite in modo esplicito, tutti i metodi sono automaticamente privati, non è possibile fornire loro un modificatore di accesso pubblico. Supponiamo che:

interface Iphone{

   void Money();

}

interface Ipen{

   void Price();
}


class Demo : Iphone, Ipen{

  void Iphone.Money(){    //it is private you can't give public               

      Console.WriteLine("You have no money");
  }

  void Ipen.Price(){    //it is private you can't give public

      Console.WriteLine("You have to paid 3$");
  }

}


// So you have to cast to call the method


    class Program
    {
        static void Main(string[] args)
        {
            Demo d = new Demo();

            Iphone i1 = (Iphone)d;

            i1.Money();

            ((Ipen)i1).Price();

            Console.ReadKey();
        }
    }

  // You can't call methods by direct class object

1
"tutti i metodi sono automaticamente privati" - questo non è tecnicamente corretto, b / c se fossero in realtà privati ​​non sarebbero affatto richiamabili, casting o meno.
samis

0

Questo è il modo in cui possiamo creare un'interfaccia esplicita: se abbiamo 2 interfacce ed entrambe le interfacce hanno lo stesso metodo e una singola classe eredita queste 2 interfacce quindi quando chiamiamo un metodo di interfaccia il compilatore si è confuso quale metodo deve essere chiamato, quindi possiamo gestire questo problema utilizzando Explicit Interface. Ecco un esempio che ho fornito di seguito.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace oops3
{
    interface I5
    {
        void getdata();    
    }
    interface I6
    {
        void getdata();    
    }

    class MyClass:I5,I6
    {
        void I5.getdata()
        {
           Console.WriteLine("I5 getdata called");
        }
        void I6.getdata()
        {
            Console.WriteLine("I6 getdata called");
        }
        static void Main(string[] args)
        {
            MyClass obj = new MyClass();
            ((I5)obj).getdata();                     

            Console.ReadLine();    
        }
    }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.