Domanda

Quali sono le differenze di implementazione di interfacce implicitamente e in modo esplicito in C#?

Quando si dovrebbe utilizzare implicita e quando si dovrebbe utilizzare esplicito?

Ci sono pro e/o contro l'uno o l'altro?


Microsoft linee guida ufficiali (dalla prima edizione Quadro Di Linee Guida Per La Progettazione) afferma che utilizzando esplicita le implementazioni non sono raccomandato, in quanto fornisce il codice di comportamento inatteso.

Penso che questa guida è molto valido in pre-Cio-tempo, quando non si passa le cose intorno come interfacce.

Qualcuno potrebbe toccare su tale aspetto?

È stato utile?

Soluzione

Implicito è quando definisci la tua interfaccia tramite un membro della tua classe. Esplicito è quando definisci i metodi all'interno della tua classe sull'interfaccia. So che sembra confuso, ma ecco cosa intendo: IList.CopyTo verrebbe implementato implicitamente come:

public void CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

ed esplicitamente come:

void ICollection.CopyTo(Array array, int index)
{
    throw new NotImplementedException();
}

La differenza è che è implicitamente accessibile attraverso la tua classe che hai creato quando è lanciata come quella classe e quando è lanciata come interfaccia. L'implementazione esplicita le consente di essere accessibile solo quando trasmessa come interfaccia stessa.

MyClass myClass = new MyClass(); // Declared as concrete class
myclass.CopyTo //invalid with explicit
((IList)myClass).CopyTo //valid with explicit.

Uso esplicitamente principalmente per mantenere pulita l'implementazione o quando ho bisogno di due implementazioni. Ma a prescindere lo uso raramente.

Sono sicuro che ci sono più motivi per usarlo / non usarlo che altri pubblicheranno.

Vedi prossimo post in questa discussione per un eccellente ragionamento dietro ciascuno.

Altri suggerimenti

La definizione implicita sarebbe semplicemente aggiungere i metodi / proprietà, ecc. richiesti dall'interfaccia direttamente alla classe come metodi pubblici.

La definizione esplicita impone che i membri vengano esposti solo quando si lavora direttamente con l'interfaccia e non l'implementazione sottostante. Questo è preferito nella maggior parte dei casi.

  1. Lavorando direttamente con l'interfaccia, non stai riconoscendo, e accoppiando il codice all'implementazione sottostante.
  2. Nel caso in cui tu abbia già, per esempio, un nome di proprietà pubblica in il tuo codice e vuoi implementare un'interfaccia che ha anche un Nome proprietà, farlo esplicitamente manterrà i due separati. Anche se avessero fatto la stessa cosa, avrei comunque delegato l'esplicito chiama la proprietà Name. Non lo sai mai, potresti voler cambiare come Name funziona per la classe normale e come Name, l'interfaccia proprietà funziona in seguito.
  3. Se si implementa un'interfaccia in modo implicito, la classe ora espone nuovi comportamenti che potrebbero essere rilevanti solo per un cliente del interfaccia e significa che non stai mantenendo le lezioni succinte abbastanza (la mia opinione).

Oltre alle eccellenti risposte già fornite, ci sono alcuni casi in cui è RICHIESTA un'implementazione esplicita affinché il compilatore sia in grado di capire cosa è richiesto. Dai un'occhiata a IEnumerable<T> come primo esempio che probabilmente verrà pubblicato abbastanza spesso.

Ecco un esempio:

public abstract class StringList : IEnumerable<string>
{
    private string[] _list = new string[] {"foo", "bar", "baz"};

    // ...

    #region IEnumerable<string> Members
    public IEnumerator<string> GetEnumerator()
    {
        foreach (string s in _list)
        { yield return s; }
    }
    #endregion

    #region IEnumerable Members
    IEnumerator IEnumerable.GetEnumerator()
    {
        return this.GetEnumerator();
    }
    #endregion
}

Qui, IEnumerable<string> implementa IEnumerable, quindi dobbiamo farlo anche noi. Ma aspetta, sia la versione generica che quella normale implementano entrambe le funzioni con la stessa firma del metodo (C # ignora il tipo restituito per questo). Questo è completamente legale e va bene. Come risolve il compilatore quale utilizzare? Ti costringe ad avere solo, al massimo, una definizione implicita, quindi può risolvere tutto ciò di cui ha bisogno.

es.

StringList sl = new StringList();

// uses the implicit definition.
IEnumerator<string> enumerableString = sl.GetEnumerator();
// same as above, only a little more explicit.
IEnumerator<string> enumerableString2 = ((IEnumerable<string>)sl).GetEnumerator();
// returns the same as above, but via the explicit definition
IEnumerator enumerableStuff = ((IEnumerable)sl).GetEnumerator();

PS: il piccolo pezzo di riferimento indiretto nella definizione esplicita di IEnumerable funziona perché all'interno della funzione il compilatore sa che il tipo effettivo della variabile è un StringList, ed è così che risolve la chiamata di funzione. Un piccolo fatto per l'implementazione di alcuni dei livelli di astrazione che alcune delle interfacce core di .NET sembrano aver accumulato.

Motivo n. 1

Tendo a usare l'implementazione esplicita dell'interfaccia quando desidero scoraggiare " programmare in un'implementazione " ( Principi di progettazione da motivi di progettazione ).

Ad esempio, in un MVP applicazione web basata su:

public interface INavigator {
    void Redirect(string url);
}

public sealed class StandardNavigator : INavigator {
    void INavigator.Redirect(string url) {
        Response.Redirect(url);
    }
}

Ora un'altra classe (come un presentatore ) è meno probabile che dipenda dall'implementazione di StandardNavigator e che sia più probabile che dipenda dall'interfaccia INavigator (poiché l'implementazione dovrebbe essere trasmessa a un'interfaccia per utilizzare il metodo Redirect).

Motivo n. 2

Un altro motivo per cui potrei andare con un'implementazione esplicita dell'interfaccia sarebbe quello di mantenere una quot! & di una classe; quot & di default; pulitore di interfaccia. Ad esempio, se stavo sviluppando un ASP.NET controllo server, potrei volere due interfacce :

  1. L'interfaccia principale della classe, utilizzata dagli sviluppatori di pagine Web; e
  2. A " nascosto " interfaccia utilizzata dal presentatore che sviluppo per gestire la logica del controllo

Segue un semplice esempio. È un controllo casella combinata che elenca i clienti. In questo esempio, lo sviluppatore di pagine Web non è interessato a popolare l'elenco; invece, vogliono solo essere in grado di selezionare un cliente dal GUID o di ottenere il GUID del cliente selezionato. Un relatore popolerà la casella al primo caricamento della pagina e questo relatore è incapsulato dal controllo.

public sealed class CustomerComboBox : ComboBox, ICustomerComboBox {
    private readonly CustomerComboBoxPresenter presenter;

    public CustomerComboBox() {
        presenter = new CustomerComboBoxPresenter(this);
    }

    protected override void OnLoad() {
        if (!Page.IsPostBack) presenter.HandleFirstLoad();
    }

    // Primary interface used by web page developers
    public Guid ClientId {
        get { return new Guid(SelectedItem.Value); }
        set { SelectedItem.Value = value.ToString(); }
    }

    // "Hidden" interface used by presenter
    IEnumerable<CustomerDto> ICustomerComboBox.DataSource { set; }
}

Il relatore popola l'origine dati e lo sviluppatore di pagine Web non deve mai essere consapevole della sua esistenza.

Ma non è una palla di cannone d'argento

Non consiglierei di utilizzare sempre implementazioni di interfaccia esplicite. Questi sono solo due esempi in cui potrebbero essere utili.

Per citare Jeffrey Richter da CLR via C #
( EIMI significa E xplicit I nterface M etodo I comprensione)

  

È di fondamentale importanza per te   capire alcune ramificazioni che   esiste quando si utilizzano EIMI. E a causa di   queste ramificazioni, dovresti provare a farlo   evitare il più possibile gli EIMI.   Fortunatamente, le interfacce generiche aiutano   eviti abbastanza EIMI. Ma lì   potrebbero essere ancora momenti in cui ne avrai bisogno   per usarli (come implementarne due   metodi di interfaccia con lo stesso nome   e firma). Ecco i grandi   problemi con gli EIMI:

     
      
  • Non c'è documentazione che spieghi come un tipo in particolare   implementa un metodo EIMI, e lì   non è Microsoft Visual Studio    Supporto IntelliSense .
  •   
  • Le istanze del tipo di valore vengono inscatolate quando vengono trasmesse a un'interfaccia.
  •   
  • Un EIMI non può essere chiamato da un tipo derivato.
  •   

Se si utilizza un riferimento all'interfaccia QUALSIASI catena virtuale può essere esplicitamente sostituita con EIMI su qualsiasi classe derivata e quando un oggetto di tale tipo viene trasmesso all'interfaccia, la catena virtuale viene ignorata e viene chiamata l'implementazione esplicita. È tutt'altro che polimorfismo.

Le EIMI possono anche essere usate per nascondere i membri dell'interfaccia non tipizzati in modo forte dalle implementazioni delle interfacce Framework di base come IEnumerable < T > quindi la tua classe non espone direttamente un metodo non fortemente tipizzato, ma è sintatticamente corretta.

Oltre alle ragioni già detto, questa è la situazione in cui una classe è l'implementazione di due diverse interfacce che hanno una proprietà/metodo con lo stesso nome e la firma.

/// <summary>
/// This is a Book
/// </summary>
interface IBook
{
    string Title { get; }
    string ISBN { get; }
}

/// <summary>
/// This is a Person
/// </summary>
interface IPerson
{
    string Title { get; }
    string Forename { get; }
    string Surname { get; }
}

/// <summary>
/// This is some freaky book-person.
/// </summary>
class Class1 : IBook, IPerson
{
    /// <summary>
    /// This method is shared by both Book and Person
    /// </summary>
    public string Title
    {
        get
        {
            string personTitle = "Mr";
            string bookTitle = "The Hitchhikers Guide to the Galaxy";

            // What do we do here?
            return null;
        }
    }

    #region IPerson Members

    public string Forename
    {
        get { return "Lee"; }
    }

    public string Surname
    {
        get { return "Oades"; }
    }

    #endregion

    #region IBook Members

    public string ISBN
    {
        get { return "1-904048-46-3"; }
    }

    #endregion
}

Questo codice viene compilato ed eseguito OK, ma il Titolo di proprietà è condivisa.

Chiaramente, noi vogliamo il valore del Titolo tornato dipendere dal fatto che stavamo trattando Class1 come un Libro o di una Persona.Questo è quando si può usare l'interfaccia esplicita.

string IBook.Title
{
    get
    {
        return "The Hitchhikers Guide to the Galaxy";
    }
}

string IPerson.Title
{
    get
    {
        return "Mr";
    }
}

public string Title
{
    get { return "Still shared"; }
}

Si noti che l'interfaccia esplicita definizioni vengono dedotti Pubblico - e, quindi, non è possibile dichiarare loro di essere pubblico (o altro) in modo esplicito.

Si noti inoltre che è ancora possibile avere una "condivisa" versione (come mostrato sopra), ma mentre questo è possibile, l'esistenza di una tale proprietà è discutibile.Forse potrebbe essere usato come una implementazione di default del Titolo, in modo che il codice esistente non dovrà essere modificato per lanciare Class1 IBook o IPerson.

Se non si definisce il "condiviso" (implicita) del Titolo, i consumatori di Class1 deve il cast esplicito istanze di Class1 per IBook o IPerson di prima, altrimenti, il codice non viene compilato.

Uso l'implementazione esplicita dell'interfaccia per la maggior parte del tempo. Ecco i motivi principali.

Il refactoring è più sicuro

Quando si cambia un'interfaccia, è meglio se il compilatore può controllarla. Questo è più difficile con implementazioni implicite.

Mi vengono in mente due casi comuni:

  • Aggiunta di una funzione a un'interfaccia, in cui una classe esistente che implementa questa interfaccia ha già un metodo con la stessa firma di quella nuova . Questo può portare a comportamenti inaspettati e mi ha morso parecchie volte. È difficile & Quot; vedere & Quot; durante il debug perché quella funzione probabilmente non si trova con gli altri metodi di interfaccia nel file (il problema di auto-documentazione menzionato di seguito).

  • Rimozione di una funzione da un'interfaccia . I metodi implementati implicitamente saranno improvvisamente codice morto, ma i metodi implementati esplicitamente verranno colti da errori di compilazione. Anche se il codice morto è buono da tenere in giro, voglio essere costretto a rivederlo e promuoverlo.

È un peccato che C # non abbia una parola chiave che ci costringe a contrassegnare un metodo come implementazione implicita, quindi il compilatore potrebbe fare i controlli extra. I metodi virtuali non presentano nessuno dei problemi sopra descritti a causa dell'uso obbligatorio di "override" e "new".

Nota: per interfacce fisse o che cambiano raramente (in genere dalle API del fornitore), questo non è un problema. Per le mie interfacce, tuttavia, non posso prevedere quando / come cambieranno.

È auto-documentante

Se vedo 'public bool Execute ()' in una classe, ci vorrà del lavoro extra per capire che fa parte di un'interfaccia. Qualcuno probabilmente dovrà commentarlo dicendo così, o metterlo in un gruppo di altre implementazioni di interfaccia, tutte sotto una regione o commento di raggruppamento dicendo & Quot; implementazione di ITask & Quot ;. Ovviamente, funziona solo se l'intestazione del gruppo non è fuori schermo ..

Considerando che: 'bool ITask.Execute ()' è chiaro e inequivocabile.

Chiara separazione dell'implementazione dell'interfaccia

Penso che le interfacce siano più "pubbliche" dei metodi pubblici perché sono progettate per esporre solo un po 'della superficie del tipo concreto. Riducono il tipo a una capacità, un comportamento, una serie di tratti, ecc. E nell'implementazione, penso che sia utile mantenere questa separazione.

Mentre guardo il codice di una classe, quando trovo implementazioni esplicite dell'interfaccia, il mio cervello si sposta in " code contract " modalità. Spesso queste implementazioni semplicemente passano ad altri metodi, ma a volte eseguiranno un ulteriore controllo dello stato / dei parametri, la conversione dei parametri in entrata per soddisfare meglio i requisiti interni o persino la traduzione per scopi di versioning (cioè più generazioni di interfacce tutte puntate verso implementazioni comuni).

(Mi rendo conto che anche i pubblici sono contratti di codice, ma le interfacce sono molto più forti, specialmente in una base di codici basata sull'interfaccia in cui l'uso diretto di tipi concreti di solito è un segno di codice solo interno.)

Correlato: Motivo 2 sopra di Jon .

E così via

Inoltre i vantaggi già menzionati in altre risposte qui:

Problemi

Non è tutto divertimento e felicità. Ci sono alcuni casi in cui mi attengo impliciti:

  • Tipi di valore, perché ciò richiederà boxe e perf inferiore. Questa non è una regola rigorosa e dipende dall'interfaccia e da come deve essereUsato. IComparable? Implicito. IFormattable? Probabilmente esplicito.
  • Interfacce di sistema di prova che hanno metodi che vengono spesso chiamati direttamente (come IDisposable.Dispose).

Inoltre, può essere una seccatura eseguire il casting quando si ha effettivamente il tipo concreto e si desidera chiamare un metodo di interfaccia esplicita. Mi occupo di questo in due modi:

  1. Aggiungi i pubblici e chiedi loro i metodi di interfaccia per l'implementazione. In genere accade con interfacce più semplici quando si lavora internamente.
  2. (Il mio metodo preferito) Aggiungi un public IMyInterface I { get { return this; } } (che dovrebbe essere integrato) e chiama foo.I.InterfaceMethod(). Se più interfacce che richiedono questa capacità, espandi il nome oltre me (nella mia esperienza è raro che io abbia questa necessità).

Se si implementa in modo esplicito, sarà possibile fare riferimento ai membri dell'interfaccia solo tramite un riferimento che è del tipo di interfaccia. Un riferimento che è il tipo della classe di implementazione non esporrà quei membri dell'interfaccia.

Se la classe di implementazione non è pubblica, ad eccezione del metodo utilizzato per creare la classe (che potrebbe essere una factory o IoC container) e, ad eccezione dei metodi di interfaccia (ovviamente), non vedo alcun vantaggio nell'implementazione esplicita di interfacce.

Altrimenti, l'implementazione esplicita delle interfacce garantisce che i riferimenti alla propria classe di implementazione concreta non vengano utilizzati, consentendo di modificare tale implementazione in un secondo momento. " Si assicura che " ;, suppongo, sia il " vantaggio " ;. Un'implementazione ben ponderata può raggiungere questo obiettivo senza un'implementazione esplicita.

Lo svantaggio, a mio avviso, è che ti ritroverai a trasmettere tipi da / verso l'interfaccia nel codice di implementazione che ha accesso a membri non pubblici.

Come molte altre cose, il vantaggio è lo svantaggio (e viceversa). L'implementazione esplicita delle interfacce assicurerà che il codice di implementazione della tua classe concreta non sia esposto.

Un'implementazione implicita dell'interfaccia è dove hai un metodo con la stessa firma dell'interfaccia.

Un'implementazione esplicita dell'interfaccia è dove dichiari esplicitamente a quale interfaccia appartiene il metodo.

interface I1
{
    void implicitExample();
}

interface I2
{
    void explicitExample();
}


class C : I1, I2
{
    void implicitExample()
    {
        Console.WriteLine("I1.implicitExample()");
    }


    void I2.explicitExample()
    {
        Console.WriteLine("I2.explicitExample()");
    }
}

MSDN: implementazioni di interfaccia implicite ed esplicite

Ogni membro della classe che implementa un'interfaccia, le esportazioni di una dichiarazione, che è semanticamente simile al modo in cui VB.NET dichiarazioni di interfaccia sono scritti, ad es.

Public Overridable Function Foo() As Integer Implements IFoo.Foo

Anche se il nome del membro della classe spesso corrisponde a quella del membro di interfaccia, e il membro della classe sarà spesso pubbliche, né di quelle cose che è necessario.Uno può anche dichiarare:

Protected Overridable Function IFoo_Foo() As Integer Implements IFoo.Foo

Nel qual caso la classe e i suoi derivati che avrebbero permesso l'accesso ai membri di una classe che utilizza il nome IFoo_Foo, ma il mondo esterno sarebbe solo essere in grado di accedere a quel particolare membro di casting per IFoo.Un tale approccio è spesso buona, nei casi in cui un metodo di interfaccia avrà specificato il comportamento di tutte le implementazioni, ma utile comportamento solo con alcuni [es.il comportamento specificato per una sola lettura della collezione IList<T>.Add metodo è quello di lanciare NotSupportedException].Purtroppo, l'unico modo corretto di implementare l'interfaccia in C# è:

int IFoo.Foo() { return IFoo_Foo(); }
protected virtual int IFoo_Foo() { ... real code goes here ... }

Non così bello.

Un uso importante dell'implementazione esplicita dell'interfaccia è quando è necessario implementare interfacce con visibilità mista .

Il problema e la soluzione sono ben spiegati nell'articolo Interfaccia interna C # .

Ad esempio, se si desidera proteggere la perdita di oggetti tra i livelli dell'applicazione, questa tecnica consente di specificare una diversa visibilità dei membri che potrebbero causare la perdita.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top