2010-11-05 9 views

risposta

132

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

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

    void IDoItSlow.Go() 
    { 
    } 
} 
+0

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

+9

Eccellente esempio. Adoro i nomi di interfaccia/classe!:-) –

+8

Non mi piace, due metodi con la stessa firma in una classe che fanno cose molto diverse? Questo è estremamente pericoloso e molto probabilmente causerà il caos in qualsiasi sviluppo di grandi dimensioni. Se hai un codice come questo, direi che la tua analisi e il design sono all'altezza del wahzoo. – Mick

7

E 'utile anche quando si dispone di due interfacce con lo stesso nome utente e la firma, ma vuole cambiare il comportamento di esso a seconda di come viene utilizzato. (Non consiglio scrittura di 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 
} 
+55

un esempio OO più strano che abbia mai visto: 'classe pubblica Animal: Cat, Dog' – mbx

+36

@mbx: Se Animal implementasse anche Parrot, sarebbe un animale da Polly-morphing. – RenniePet

+3

Ricordo un personaggio dei cartoni animati che era un gatto ad un'estremità e un cane all'altro capo ;-) –

5

Se si dispone di un'interfaccia interna e non si desidera implementare i membri sulla tua classe pubblicamente, li si implementa in modo esplicito. Le implementazioni implicite devono essere pubbliche.

+0

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

5

può mantenere il pulitore interfaccia pubblica di implementare in modo esplicito un'interfaccia, vale a dire la classe File potrebbe implementare IDisposable esplicitamente e fornire un metodo pubblico Close() che potrebbe rendere più senso per un consumatore rispetto Dispose().

F # solo offre un'implementazione esplicita dell'interfaccia, quindi è sempre necessario eseguire il cast sull'interfaccia specifica per accedere alla sua funzionalità, il che rende molto esplicito (nessun gioco di parole) l'uso dell'interfaccia.

+0

Penso che la maggior parte delle versioni di VB supporta anche solo definizioni di interfaccia 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 (la risposta più recente), è possibile implementare IDoItFast e IDoItSlow con i membri pubblici "GoFast" e "GoSlow", rispettivamente. –

+2

Non mi piace il tuo esempio particolare (IMHO, le uniche cose che dovrebbero nascondere 'Dispose' sono quelle che non richiedono mai la pulizia); un esempio migliore potrebbe essere qualcosa come un'implementazione di una collezione immutabile di 'IList .Add'. – supercat

60

È utile nascondere il membro non preferito. Ad esempio, se si implementano sia IComparable<T> e IComparable, in genere è meglio nascondere l'overload IComparable per non dare alle persone l'impressione di poter confrontare oggetti di tipi diversi. Allo stesso modo, alcune interfacce non sono compatibili con CLS, come IConvertible, quindi se non si implementa esplicitamente l'interfaccia, gli utenti finali di lingue che richiedono la conformità CLS non possono utilizzare l'oggetto. (Che sarebbe molto disastroso se gli implementatori di BCL non nascondessero i membri IConvertible dei primitivi :))

Un'altra nota interessante è che normalmente usando un tale costrutto significa che la struct che implementa esplicitamente un'interfaccia può solo invocarli per boxe al tipo di interfaccia. Si può ovviare a questo utilizzando i vincoli generici ::

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

non sarà casella un int quando si passa uno ad esso.

+1

Hai un refuso nel tuo vincolo. Per clerificare, il codice sopra funziona. Deve essere nella dichiarazione iniziale della firma del metodo nell'interfaccia. Il post originale non lo ha specificato. Inoltre, il formato corretto è "void SomeMehtod (T obj) dove T: IConvertible.Nota, c'è un ulteriore punto tra") "e" where "che non dovrebbe essere lì. Ancora, +1 per un uso intelligente di generici per evitare costosi boxe –

+1

Ciao Michael B. Allora, perché in attuazione della stringa nel .NET ci implementazione pubblica del IComparable: CompareTo (Object value) {public int se (valore == null) { return 1;} . se { tiro nuovo ArgumentException (Environment.GetResourceString ("Arg_MustBeString"));} ((il valore è String)!) ritorno String.Compare (questo valore, (String), StringComparison.CurrentCulture); } Grazie! – zzfima

+0

'stringa' era in giro prima di generici e questa pratica era in voga. Quando .net 2 è venuto fuori non volevano rompere l'interfaccia pubblica della 'stringa', così l'hanno lasciata in giro come era con la salvaguardia in atto. –

13

Un'altra tecnica utile è di avere attuazione pubblico di una funzione di un metodo restituire un valore che è più preciso quanto specificato in un'interfaccia.

Ad esempio, un oggetto può implementare ICloneable, ma hanno ancora il suo metodo Clone pubblicamente visibile ritorno proprio tipo.

Allo stesso modo, un IAutomobileFactory potrebbe avere un metodo di fabbricazione che restituisce un automobile, ma un FordExplorerFactory, che implementa IAutomobileFactory, potrebbe avere il suo metodo Produzione restituire un Ford Explorer (che deriva da Automobile). Il codice che sa di avere un FordExplorerFactory potrebbe utilizzare le proprietà specifiche di FordExplorer su un oggetto restituito da un FordExplorerFactory senza dover digitare, mentre il codice che semplicemente sapeva che aveva un tipo di IAutomobileFactory avrebbe semplicemente gestito il suo ritorno come Automobile.

+2

+1 ... questo sarebbe il mio uso preferito delle implementazioni esplicite dell'interfaccia, anche se un piccolo esempio di codice sarebbe probabilmente un po 'più chiaro di questa storia :) –

+3

2 persone diventano cieche mentre leggi questa risposta. –

36

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

retrocompatibilità: Nel caso in cui le ICloneable cambiamenti di interfaccia, di attuazione i membri della classe di metodo non devono cambiare le loro firme del metodo.

codice più pulito: ci sarà un errore del compilatore se il metodo Clone viene rimosso dal ICloneable, se si implementa il metodo implicitamente si può finire con metodi pubblici 'orfani' inutilizzati

tipizzazione forte : per illustrare la storia di supercat con un esempio, questo sarebbe il mio codice di esempio preferito, l'implementazione ICloneable permette esplicitamente Clone() da fortemente tipizzato quando si chiama direttamente come membro MyObject esempio:

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

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

Per quello, preferirei l'interfaccia 'ICloneable {T Clone(); T self {get;}} '. Si noti che non esiste deliberatamente alcun vincolo 'ICloneable ' su T. Mentre un oggetto può generalmente essere clonato tranquillamente solo se la sua base può essere, si potrebbe desiderare di derivare da una classe base che potrebbe tranquillamente clonare un oggetto di una classe che può essere clonato 't. Per consentirmi di farlo, ti consiglio di evitare che le classi ereditabili espongano un metodo di clonazione pubblica. Invece hanno classi ereditabili con un metodo di clonazione "protetto" e classi sigillate che derivano da esse ed espongono la clonazione pubblica. – supercat

+0

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

4

Un altro motivo per l'implementazione esplicita è per la manutenzione .

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

Così migliora la "leggibilità" del codice.

+0

IMHO è più importante decidere se la classe dovrebbe o non dovrebbe esporre il metodo ai suoi clienti. Ciò spinge a essere esplicito o implicito.Per documentare che diversi metodi appartengono insieme, in questo caso perché soddisfano un contratto - questo è il significato di '# region', con una stringa del titolo appropriata. E un commento sul metodo. – ToolmakerSteve

0

Questo è come possiamo creare interfacce esplicite: Se abbiamo 2 interfaccia e sia l'interfaccia di avere lo stesso metodo e una sola classe erediterà questi 2 interfacce in modo che quando si chiama un metodo di interfaccia al compilatore un po 'confusa quale metodo per essere chiamato, così possiamo gestire questo problema usando l'interfaccia esplicita. 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();  
     } 
    } 
} 
0

In caso di interfacce esplicitamente definite, tutti i metodi sono automaticamente privati, non è possibile fornire ad essi un modificatore di accesso. Supponiamo:

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 
+0

"tutti i metodi sono automaticamente privati" - questo non è tecnicamente corretto, b/c se erano di fatto privati ​​quindi non sarebbero affatto chiamabili, casting o meno. – samosaris

0

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

Concretamente, ImmutableList<T> implementa IList<T> e quindi ICollection<T> (in order per consentire ImmutableList<T> da utilizzare più facilmente con il codice legacy), eppure void ICollection<T>.Add(T item) non ha alcun senso per un ImmutableList<T>: dal momento che l'aggiunta di un elemento a un elenco immutabile, non deve modificare l'elenco esistente , ImmutableList<T> deriva anche da IImmutableList<T> di cui IImmutableList<T> Add(T item) può essere utilizzato per elenchi immutabili.

Pertanto, nel caso di Add, le implementazioni in ImmutableList<T> finire in cerca 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(); 
Problemi correlati