È consentito utilizzare l'implementazione esplicita dell'interfaccia per nascondere i membri in C #?


14

Capisco come lavorare con le interfacce e l'implementazione esplicita dell'interfaccia in C #, ma mi chiedevo se fosse considerata una cattiva forma per nascondere alcuni membri che non sarebbero stati usati frequentemente. Per esempio:

public interface IMyInterface
{
    int SomeValue { get; set; }
    int AnotherValue { get; set; }
    bool SomeFlag { get; set; }

    event EventHandler SomeValueChanged;
    event EventHandler AnotherValueChanged;
    event EventHandler SomeFlagChanged;
}

So in anticipo che gli eventi, sebbene utili, verrebbero usati molto raramente. Pertanto, ho pensato che avrebbe senso nasconderli lontano da una classe di implementazione tramite un'implementazione esplicita per evitare il disordine di IntelliSense.


3
Se fai riferimento alla tua istanza tramite l'interfaccia, non li nasconderebbe comunque, nasconderebbe solo se il tuo riferimento è a un tipo concreto.
Andy,

1
Ho lavorato con un ragazzo che lo faceva regolarmente. Mi ha fatto impazzire.
Joel Etherton,

... e inizia a usare l'aggregazione.
mikalai,

Risposte:


39

Sì, è generalmente in cattiva forma.

Pertanto, ho pensato che avrebbe senso nasconderli lontano da una classe di implementazione tramite un'implementazione esplicita per evitare il disordine di IntelliSense.

Se IntelliSense è troppo disordinato, la tua classe è troppo grande. Se la tua classe è troppo grande, probabilmente sta facendo troppe cose e / o mal progettato.

Risolvi il tuo problema, non il sintomo.


È appropriato usarlo per rimuovere gli avvisi del compilatore sui membri non utilizzati?
Gusdor,

11
"Risolvi il tuo problema, non il sintomo." +1
FreeAsInBeer,

11

Usa la funzione per quello a cui era destinata. Ridurre il disordine dello spazio dei nomi non è uno di quelli.

Le ragioni legittime non riguardano il "nascondersi" o l'organizzazione, ma per risolvere l'ambiguità e specializzarsi. Se si implementano due interfacce che definiscono "Foo", la semantica di Foo potrebbe essere diversa. Davvero non è un modo migliore per risolvere questo, tranne per le interfacce esplicite. L'alternativa è impedire l'implementazione di interfacce sovrapposte o dichiarare che le interfacce non possono associare la semantica (che comunque è solo una cosa concettuale). Entrambe sono alternative scadenti. A volte la praticità supera l'eleganza.

Alcuni autori / esperti lo considerano un kludge di una caratteristica (i metodi non fanno realmente parte del modello a oggetti del tipo). Ma come tutte le funzionalità, da qualche parte, qualcuno ne ha bisogno.

Ancora una volta, non lo userei per nascondermi o organizzazione. Ci sono modi per nascondere i membri dall'intellisense.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]

"Ci sono autori / esperti di spicco che lo considerano un kludge di una caratteristica." Chi è? Qual è l'alternativa proposta? Esiste almeno un problema (ovvero la specializzazione dei metodi di interfaccia in una sottointerfaccia / classe di implementazione) che richiederebbe l'aggiunta di qualche altra funzionalità a C # per risolverlo in assenza di un'implementazione esplicita (vedere ADO.NET e raccolte generiche) .
jhominal

@jhominal - CLR via C # di Jeffrey Richter usa specificamente la parola kludge. C ++ va d'accordo senza di essa, il programmatore deve fare esplicito riferimento all'implementazione del metodo di base per chiarire le ambiguità o usare un cast. Ho già detto che le alternative non sono molto interessanti. Ma è un aspetto di singola eredità e interfacce, non OOP in generale.
codenheim,

Avresti anche potuto menzionare che Java non ha neanche un'implementazione esplicita, nonostante abbia la stessa progettazione "singola eredità di implementazione + interfacce". Tuttavia, in Java, il tipo restituito di un metodo overriden può essere specializzato, eliminando parte della necessità di un'implementazione esplicita. (In C #, è stata presa una decisione di progettazione diversa, probabilmente in parte a causa dell'inclusione delle proprietà).
jhominal

@jhominal - Certo, quando avrò qualche minuto dopo aggiornerò. Grazie.
codenheim,

Nascondersi può essere del tutto appropriato in situazioni in cui una classe potrebbe implementare un metodo di interfaccia in modo conforme al contratto di interfaccia ma non utile . Ad esempio, mentre non mi piace l'idea di classi in cui IDisposable.Disposefa qualcosa ma a cui viene assegnato un nome diverso Close, lo considero perfettamente ragionevole per una classe il cui contratto nega espressamente che il codice può creare e abbandonare istanze senza chiamare Disposeper utilizzare l'implementazione esplicita dell'interfaccia per definire un IDisposable.Disposemetodo che non fa nulla.
supercat,

5

Potrei essere un segno di codice disordinato, ma a volte il mondo reale è disordinato. Un esempio di .NET framework è ReadOnlyColection che nasconde il setter mutante [], Aggiungi e Imposta.

IE ReadOnlyCollection implementa IList <T>. Vedi anche la mia domanda a SO diversi anni fa, quando ho riflettuto su questo.


Beh, è ​​anche la mia interfaccia che userò, quindi non ha senso farlo in modo sbagliato fin dall'inizio.
Kyle Baran,

2
Non direi che nasconde il setter mutante ma piuttosto che non lo fornisce affatto. Un esempio migliore sarebbe quello ConcurrentDictionaryche implementa IDictionary<T>esplicitamente i vari membri in modo che i consumatori diConcurrentDictionary A) non li chiamino direttamente e B) possano usare metodi che richiedono un'implementazione IDictionary<T>se assolutamente necessario. Ad esempio, gli utenti di ConcurrentDictionary<T> dovrebbero chiamare TryAddpiuttosto che Addevitare inutili eccezioni.
Brian,

Naturalmente, implementando Add esplicita riduce anche il disordine. TryAddpuò fare tutto ciò che Addpuò fare ( Addè implementato come una chiamata a TryAdd, quindi fornire entrambi sarebbe ridondante). Tuttavia, TryAddha necessariamente un nome / firma diverso, quindi non è possibile implementarlo IDictionarysenza questa ridondanza. L'implementazione esplicita risolve questo problema in modo chiaro.
Brian,

Bene dal punto di vista del consumatore i metodi sono nascosti.
Esben Skov Pedersen,

1
Scrivi di IReadonlyCollection <T> ma rimanda a ReadOnlyCollection <T>. Il primo in un'interfaccia, il secondo è una classe. IReadonlyCollection <T> non implementa IList <T> anche se ReadonlyCollection <T> lo fa.
Jørgen Fogh,

2

È una buona forma farlo quando il membro è inutile nel contesto. Ad esempio, se crei una raccolta di sola lettura che implementa IList<T>delegando a un oggetto interno _wrapped, potresti avere qualcosa del tipo:

public T this[int index]
{
  get
  {
     return _wrapped[index];
  }
}
T IList<T>.this[int index]
{
  get
  {
    return this[index];
  }
  set
  {
    throw new NotSupportedException("Collection is read-only.");
  }
}
public int Count
{
  get { return _wrapped.Count; }
}
bool ICollection<T>.IsReadOnly
{
  get
  {
    return true;
  }
}

Qui abbiamo quattro casi diversi.

public T this[int index]è definito dalla nostra classe piuttosto che dall'interfaccia, e quindi ovviamente non è un'implementazione esplicita, sebbene si noti che sembra essere simile alla lettura-scrittura T this[int index]definita nell'interfaccia ma è di sola lettura.

T IList<T>.this[int index]è esplicito perché una parte di esso (il getter) è perfettamente abbinata dalla proprietà sopra e l'altra parte genererà sempre un'eccezione. Mentre è vitale per qualcuno accedere a un'istanza di questa classe tramite l'interfaccia, è inutile che qualcuno la usi attraverso una variabile del tipo di classe.

Allo stesso modo perché bool ICollection<T>.IsReadOnly tornerà sempre vero, è assolutamente inutile il codice scritto contro il tipo di classe, ma potrebbe essere vitale per usarlo attraverso il tipo di interfaccia, e quindi lo implementiamo esplicitamente.

Al contrario, public int Countnon viene implementato esplicitamente perché potrebbe essere potenzialmente utile a qualcuno che utilizza un'istanza attraverso il proprio tipo.

Ma con il tuo caso "usato molto raramente", mi spingerei molto fortemente a non usare un'implementazione esplicita.

Nei casi in cui consiglio di utilizzare un'implementazione esplicita che chiama il metodo tramite una variabile del tipo di classe, si tratterebbe di un errore (tentativo di utilizzare il setter indicizzato) o inutile (controllo di un valore che sarà sempre lo stesso), quindi nascondersi li stai proteggendo l'utente da buggy o codice non ottimale. È significativamente diverso dal codice che ritieni possa essere usato raramente . Per questo potrei considerare di usare l' EditorBrowsableattributo per nascondere il membro dall'intellisense, anche se anche se ne sarei stanco; il cervello delle persone ha già il proprio software per filtrare ciò che non li interessa.


Se stai implementando un'interfaccia, implementa l'interfaccia. In caso contrario, si tratta di una chiara violazione del principio di sostituzione di Liskov.
Telastyn,

@Telastyn l'interfaccia è effettivamente implementata.
Jon Hanna,

Ma il contratto non è soddisfatto, in particolare "Questo è qualcosa che rappresenta una raccolta mutevole, ad accesso casuale".
Telastyn,

1
@Telastyn soddisfa il contratto documentato, in particolare alla luce di "Una raccolta di sola lettura non consente l'aggiunta, la rimozione o la modifica di elementi dopo la creazione della raccolta". Vedi msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna,

@Telastyn: se il contratto includesse la mutabilità, allora perché l'interfaccia dovrebbe contenere una IsReadOnlyproprietà?
Nikie,
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.