c# oop design-patterns .net-4.5 c#-5.0

c# - ¿Por qué ICollection genérico no implementa IReadOnlyCollection en.NET 4.5?



oop design-patterns (6)

Cuando leí por primera vez tu pregunta, pensé "¡Oye, tiene razón! Eso tendría más sentido". Pero el objetivo de una interfaz es describir un contrato: una descripción del comportamiento. Si su clase implementa IReadOnlyCollection , debe ser de solo lectura. Collection<T> no es. Entonces, para una Collection<T> implementar una interfaz que implique solo lectura podría ocasionar algunos problemas bastante desagradables. Los consumidores de un IReadOnlyCollection van a hacer ciertas suposiciones sobre esa colección subyacente, como iterar sobre ella es segura porque nadie puede modificarla, etc. etc. ¡Si fue estructurada como sugieres que podría no ser cierto!

En .NET 4.5 / C # 5, IReadOnlyCollection<T> se declara con una propiedad Count :

public interface IReadOnlyCollection<out T> : IEnumerable<T>, IEnumerable { int Count { get; } }

Me pregunto, ¿no hubiera tenido sentido para ICollection<T> implementar la IReadOnlyCollection<T> también?

public interface ICollection<T> : IEnumerable<T>, IEnumerable, *IReadOnlyCollection<T>*

Esto hubiera significado que las clases implementando ICollection<T> hubieran implementado automáticamente IReadOnlyCollection<T> . Esto me parece razonable.

La abstracción de ICollection<T> se puede ver como una extensión de la IReadOnlyCollection<T> . Tenga en cuenta que List<T> , por ejemplo, implementa tanto ICollection<T> como IReadOnlyCollection<T> .

Sin embargo, no ha sido diseñado de esa manera.

¿Que me estoy perdiendo aqui? ¿Por qué se habría elegido la implementación actual en su lugar?

ACTUALIZAR

Estoy buscando una respuesta que use el razonamiento de diseño orientado a objetos para explicar por qué:

  • Una clase concreta como List<T> implementa tanto IReadOnlyCollection<T> como ICollection<T>

es un mejor diseño que:

  • ICollection<T> implementa IReadOnlyCollection<T> directamente

También tenga en cuenta que esta es esencialmente la misma pregunta que:

  1. ¿Por qué IList<T> implementa IReadOnlyList<T> ?
  2. ¿Por qué IDictionary<T> implementa IReadOnlyDictionary<T> ?

El razonamiento de diseño orientado a objetos se derivaría de la relación "Es A" entre una clase y cualquier interfaz que implemente la clase.

En .NET 4.5 / c # 5.0 List<T> es tanto un ICollection<T> como IReadOnlyCollection<T> . Puede instanciar una List<T> como cualquier tipo dependiendo de cómo quiera que se use su colección.

Mientras que tener ICollection<T> implementa IReadOnlyCollection<T> le daría la capacidad de tener List<T> ya sea una ICollection<T> o IReadOnlyCollection<T> , hacerlo sería decir que ICollection<T> "Is A" IReadOnlyCollection<T> que no es.

Actualizado

Si cada clase que heredó de ICollection<T> implementó IReadOnlyCollection<T> entonces yo diría que tiene más sentido hacer que ICollection<T> implemente la interfaz. Como ese no es el caso, piense cómo sería si ICollection<T> implementara IReadOnlyCollection<T> :

public interface IReadOnlyCollection<T> : IEnumerable<T>, IEnumerable{}
public interface ICollection<T> : IReadOnlyCollection<T> {}

Si mirara la firma para ICollection<T> , parece que IS-A colección de solo lectura. Espera, veamos IReadOnlyCollection y ahh ... implementa IEnumerable<T> e IEnumerable por lo que no necesariamente tiene que ser una colección de solo lectura. Funcionalmente las implementaciones propuestas por la pregunta original son las mismas, pero semánticamente creo que es más correcto impulsar una implementación de interfaz lo más alto posible.


Esta interfaz es más una interfaz de descripción que una cosa realmente funcional.

En el enfoque sugerido, cada colección debe entenderse como algo que usted puede leer de forma directa y siempre es el mismo. Entonces no puedes agregar o eliminar nada después de la creación.

Podríamos preguntarnos por qué la interfaz se llama IReadOnlyCollection , no ''IReadOnlyEnumerable'' porque usa IEnumerable<T>, IEnumerable . La respuesta es fácil: este tipo de interfaz no tendría seneces, porque Enumerable ya es de "solo lectura".

Así que veamos en doc IReadOnlyCollection(T)

El primer (y último) senetece en la descripción dice:

Representa una colección de elementos de solo lectura, fuertemente tipada.

Y creo que esto explica todo si sabes para qué sirve el strong-typing .


Jon estaba aquí https://.com/a/12622784/395144 , debes marcar su respuesta como la respuesta:

int ICollection<Foo>.Count { ... } // compiler error!

Dado que las interfaces pueden tener implementaciones explícitas, la extracción de interfaces base no es compatible con versiones anteriores (con las clases base no tiene este problema).

Es por eso...

Collection<T> : IReadOnlyCollection<T> List<T> : IReadOnlyList<T> Dictionary<TKey, TValue> : IReadOnlyDictionary<TKey, TValue>

... pero no sus interfaces.

En mi humilde opinión, al principio hicieron un error de diseño, bastante imposible de resolver ahora (sin romper cosas).

EDITAR: ocultar no ayuda, las implementaciones antiguas (explícitas) aún no se compilarán (sin modificar el código):

interface INew<out T> { T Get(); } interface IOld<T> : INew<T> { void Set(T value); new T Get(); } class Old<T> : IOld<T> { T IOld<T>.Get() { return default(T); } void IOld<T>.Set(T value) { } }

''Sample.Old'' no implementa el miembro de la interfaz ''Sample.INew.Get ()''


Probablemente haya varias razones. Aquí hay dos de la parte superior:

  1. Enormes problemas de compatibilidad con versiones anteriores

    ¿Cómo escribirías la definición de ICollection<T> ? Esto se ve natural:

    interface ICollection<T> : IReadOnlyCollection<T> { int Count { get; } }

    Pero tiene un problema, porque IReadOnlyCollection<T> también declara una propiedad Count (el compilador emitirá una advertencia aquí). Además de la advertencia, dejarlo tal como está (lo que equivale a escribir un new int Count ) permite a los implementadores tener implementaciones diferentes para las dos propiedades de Count al implementar al menos uno explícitamente. Esto podría ser "divertido" si las dos implementaciones decidieron devolver valores diferentes. Permitir que la gente se pegue un tiro en el pie no es el estilo de C #.

    OK, ¿y qué hay de:

    interface ICollection<T> : IReadOnlyCollection<T> { // Count is "inherited" from IReadOnlyCollection<T> }

    Bueno, esto rompe todos los códigos existentes que decidieron implementar Count explícitamente:

    class UnluckyClass : ICollection<Foo> { int ICollection<Foo>.Count { ... } // compiler error! }

    Por lo tanto, me parece que no hay una buena solución a este problema: o se rompe el código existente, o se fuerza una implementación propensa a errores en todos . Entonces, el único movimiento ganador es no jugar .

  2. Comprobaciones de tipo de tiempo semántico

    No tendría sentido escribir código como

    if (collection is IReadOnlyCollection<Foo>)

    (o el equivalente con reflejo) a menos que IReadOnlyCollection<T> sea ​​"opt-in": si ICollection<T> fuera un superconjunto de IReadOnlyCollection<Foo> , prácticamente todas las clases de colecciones bajo el sol superarían esta prueba, lo que no sería útil en la práctica.


Sería semánticamente incorrecto, porque obviamente, no todas las ICollection son de solo lectura.

Dicho esto, podrían haber llamado a la interfaz IReadableCollection , mientras que una implementación podría llamarse ReadOnlyCollection .

Sin embargo, no fueron por esa ruta. ¿Por qué? Vi a un msdn.microsoft.com/en-us/magazine/jj133817.aspx . (Aunque ya lo es, francamente)