¿Está permitido usar una implementación de interfaz explícita para ocultar miembros en C #?

14

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.

Kyle Baran
fuente
3
Si hace referencia a su instancia a través de la interfaz, no los ocultará de todos modos, solo se ocultará si su referencia es a un tipo concreto.
Andy
1
Trabajé con un chico que hacía esto regularmente. Me volvió absolutamente loco.
Joel Etherton
... y comienza a usar la agregación.
mikalai

Respuestas:

39

Sí, generalmente es una mala forma.

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.

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.

Telastyn
fuente
¿Es apropiado usarlo para eliminar las advertencias del compilador sobre los miembros no utilizados?
Gusdor
11
"Arregla tu problema, no tu síntoma". +1
FreeAsInBeer
11

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.

[System.ComponentModel.EditorBrowsable(System.ComponentModel.EditorBrowsableState.Never)]
Codenheim
fuente
"Hay autores / expertos destacados que lo consideran un error de una característica". ¿Quien es? ¿Cuál es la alternativa propuesta? Hay al menos un problema (es decir, especialización de métodos de interfaz en una subinterfaz / clase de implementación) que requeriría que se agregue alguna otra característica a C # para resolverlo en ausencia de una implementación explícita (ver ADO.NET y colecciones genéricas) .
jhominal
@jhominal - CLR a través de C # por Jeffrey Richter usa específicamente la palabra kludge. C ++ se lleva bien sin él, el programador tiene que referirse explícitamente a la implementación del método base para desambiguar, o usar un cast. Ya mencioné que las alternativas no son muy atractivas. Pero es un aspecto de la herencia única y las interfaces, no OOP en general.
Codenheim
También podría haber mencionado que Java tampoco tiene una implementación explícita, a pesar de tener el mismo diseño de "herencia única de implementación + interfaces". Sin embargo, en Java, el tipo de retorno de un método reemplazado puede ser especializado, obviando parte de la necesidad de una implementación explícita. (En C #, se tomó una decisión de diseño diferente, probablemente en parte debido a la inclusión de propiedades).
jhominal
@jhominal - Claro, cuando tenga unos minutos más tarde actualizaré. Gracias.
Codenheim
Ocultar puede ser completamente apropiado en situaciones en las que una clase podría implementar un método de interfaz de una manera que cumpla con el contrato de interfaz pero no sea útil . Por ejemplo, aunque no me gusta la idea de las clases donde IDisposable.Disposehace algo pero se le da un nombre diferente Close, lo consideraría perfectamente razonable para una clase cuyo contrato niega expresamente que el código pueda crear y abandonar instancias sin llamar Disposea usar una implementación de interfaz explícita para define un IDisposable.Disposemétodo que no hace nada.
supercat
5

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.

Esben Skov Pedersen
fuente
Bueno, también usaré mi propia interfaz, así que no tiene sentido hacerlo mal desde el principio.
Kyle Baran
2
No diría que oculta el setter mutante, sino que no lo proporciona en absoluto. Un mejor ejemplo sería ConcurrentDictionary, que implementa varios miembros IDictionary<T>explícitamente para que los consumidores de ConcurrentDictionaryA) no los llamen directamente y B) puedan usar métodos que requieren una implementación IDictionary<T>si es absolutamente necesario. Por ejemplo, los usuarios de ConcurrentDictionary<T> deben llamar en TryAddlugar de Addevitar la necesidad de excepciones innecesarias.
Brian
Por supuesto, la implementación Addexplícita también reduce el desorden. TryAddpuede hacer cualquier cosa que Addpueda hacer ( Addse implementa como una llamada a TryAdd, por lo que proporcionar ambas sería redundante). Sin embargo, TryAddnecesariamente tiene un nombre / firma diferente, por lo que no es posible implementarlo IDictionarysin esta redundancia. La implementación explícita resuelve este problema limpiamente.
Brian
Bueno, desde el punto de vista del consumidor, los métodos están ocultos.
Esben Skov Pedersen
1
Escribe sobre IReadonlyCollection <T> pero enlaza con ReadOnlyCollection <T>. El primero en una interfaz, el segundo es una clase. IReadonlyCollection <T> no implementa IList <T> aunque ReadonlyCollection <T> sí lo hace.
Jørgen Fogh
2

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, _wrappedentonces podría tener algo como:

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;
  }
}

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-escritura T 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>.IsReadOnlyque 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 Countno 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 EditorBrowsableatributo 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.

Jon Hanna
fuente
Si está implementando una interfaz, implemente la interfaz. De lo contrario, esta es una clara violación del Principio de sustitución de Liskov.
Telastyn
@Telastyn la interfaz está efectivamente implementada.
Jon Hanna
Pero el contrato no está satisfecho, específicamente, "Esto es algo que representa una colección mutable de acceso aleatorio".
Telastyn
1
@Telastyn cumple con el contrato documentado, particularmente a la luz de "Una colección que es de solo lectura no permite la adición, eliminación o modificación de elementos después de la creación de la colección". Ver msdn.microsoft.com/en-us/library/0cfatk9t%28v=vs.110%29.aspx
Jon Hanna
@Telastyn: Si el contrato incluía mutabilidad, ¿por qué la interfaz contendría una IsReadOnlypropiedad?
nikie