Entiendo cómo trabajar con interfaces y la implementación explícita de interfaces en C #, pero me preguntaba si se considera una mala forma ocultar a ciertos miembros que no se usarían con frecuencia. Por ejemplo:
public interface IMyInterface
{
int SomeValue { get; set; }
int AnotherValue { get; set; }
bool SomeFlag { get; set; }
event EventHandler SomeValueChanged;
event EventHandler AnotherValueChanged;
event EventHandler SomeFlagChanged;
}
Sé de antemano que los eventos, aunque útiles, rara vez se utilizarían. Por lo tanto, pensé que tendría sentido ocultarlos de una clase de implementación a través de una implementación explícita para evitar el desorden de IntelliSense.
c#
interfaces
implementations
Kyle Baran
fuente
fuente
Respuestas:
Sí, generalmente es una mala forma.
Si su IntelliSense está demasiado abarrotado, su clase es demasiado grande. Si su clase es demasiado grande, es probable que esté haciendo demasiadas cosas y / o mal diseñada.
Arregla tu problema, no tu síntoma.
fuente
Use la función para lo que fue diseñada. Reducir el desorden del espacio de nombres no es uno de esos.
Las razones legítimas no se tratan de "ocultarse" u organizarse, sino de resolver la ambigüedad y especializarse. Si implementa dos interfaces que definen "Foo", la semántica para Foo puede diferir. Realmente no es una mejor manera de resolver esto, excepto para interfaces explícitas. La alternativa es no permitir la implementación de interfaces superpuestas, o declarar que las interfaces no pueden asociar la semántica (que de todos modos es solo una cuestión conceptual). Ambas son alternativas pobres. A veces la practicidad triunfa sobre la elegancia.
Algunos autores / expertos lo consideran un error de una característica (los métodos no son realmente parte del modelo de objetos del tipo). Pero como todas las características, en algún lugar, alguien lo necesita.
Una vez más, yo no utilizarlo para ocultar u organización. Hay formas de ocultar miembros de intellisense.
fuente
IDisposable.Dispose
hace algo pero se le da un nombre diferenteClose
, lo consideraría perfectamente razonable para una clase cuyo contrato niega expresamente que el código pueda crear y abandonar instancias sin llamarDispose
a usar una implementación de interfaz explícita para define unIDisposable.Dispose
método que no hace nada.Podría ser un signo de código desordenado, pero a veces el mundo real es desordenado. Un ejemplo de .NET framework es ReadOnlyColection que oculta el setter mutante [], Add and Set.
IE ReadOnlyCollection implementa IList <T>. Vea también mi pregunta a SO hace varios años cuando reflexioné sobre esto.
fuente
ConcurrentDictionary
, que implementa varios miembrosIDictionary<T>
explícitamente para que los consumidores deConcurrentDictionary
A) no los llamen directamente y B) puedan usar métodos que requieren una implementaciónIDictionary<T>
si es absolutamente necesario. Por ejemplo, los usuarios deConcurrentDictionary<T>
deben llamar enTryAdd
lugar deAdd
evitar la necesidad de excepciones innecesarias.Add
explícita también reduce el desorden.TryAdd
puede hacer cualquier cosa queAdd
pueda hacer (Add
se implementa como una llamada aTryAdd
, por lo que proporcionar ambas sería redundante). Sin embargo,TryAdd
necesariamente tiene un nombre / firma diferente, por lo que no es posible implementarloIDictionary
sin esta redundancia. La implementación explícita resuelve este problema limpiamente.Es una buena forma de hacerlo cuando el miembro no tiene sentido en su contexto. Por ejemplo, si crea una colección de solo lectura que se implementa
IList<T>
delegando a un objeto interno,_wrapped
entonces podría tener algo como:Aquí tenemos cuatro casos diferentes.
public T this[int index]
está definido por nuestra clase en lugar de la interfaz y, por lo tanto, no es una implementación explícita, aunque tenga en cuenta que es similar a la lectura-escrituraT this[int index]
definida en la interfaz, pero es de solo lectura.T IList<T>.this[int index]
es explícito porque una parte de él (el captador) coincide perfectamente con la propiedad anterior, y la otra parte siempre arrojará una excepción. Si bien es vital para alguien que accede a una instancia de esta clase a través de la interfaz, no tiene sentido que alguien la use a través de una variable del tipo de la clase.De manera similar, dado
bool ICollection<T>.IsReadOnly
que siempre va a devolver verdadero, no tiene sentido codificar el código escrito en contra del tipo de la clase, pero podría ser vital para usarlo a través del tipo de interfaz y, por lo tanto, lo implementamos explícitamente.Por el contrario,
public int Count
no se implementa explícitamente porque podría ser útil para alguien que usa una instancia a través de su propio tipo.Pero con su caso de "uso muy poco frecuente", me inclinaría mucho por no utilizar una implementación explícita.
En los casos en los que recomiendo usar una implementación explícita que llame al método a través de una variable del tipo de la clase, podría ser un error (intentar usar el setter indexado) o no tener sentido (verificar un valor que siempre será el mismo) para ocultar si está protegiendo al usuario del código defectuoso o subóptimo. Eso es significativamente diferente al código que crees que es probable que rara vez se use. Para eso, podría considerar usar el
EditorBrowsable
atributo para ocultar el miembro de intellisense, aunque incluso eso me cansaría; los cerebros de las personas ya tienen su propio software para filtrar lo que no les interesa.fuente
IsReadOnly
propiedad?