Antes incluso pido, me deja la respuesta obvia fuera del camino: La interfaz ICollection<T>
incluye un método Remove
para eliminar un elemento arbitrario, que Queue<T>
Stack<T>
y realmente no puede soportar (ya que sólo se pueden eliminar "fin" elementos).¿Por qué Queue (T) y Stack (T) no implementan ICollection (T)?
Bien, me doy cuenta de eso. En realidad, mi pregunta no es específicamente sobre los tipos de colección Queue<T>
o Stack<T>
; más bien, se trata de la decisión de diseño de no implementar ICollection<T>
para cualquier tipo genérico que sea esencialmente una colección de valores T
.
Esto es lo que me parece extraño. Digamos que tengo un método que acepta una colección arbitraria de T
, y para el propósito del código que estoy escribiendo, sería útil conocer el tamaño de la colección. Por ejemplo (el código de abajo es trivial, ya que sólo la ilustración!):
// Argument validation omitted for brevity.
static IEnumerable<T> FirstHalf<T>(this ICollection<T> source)
{
int i = 0;
foreach (T item in source)
{
yield return item;
if ((++i) >= (source.Count/2))
{
break;
}
}
}
Ahora, no hay realmente ninguna razón para que este código no podría operar en un Queue<T>
o una Stack<T>
, excepto que esos tipos no implementan ICollection<T>
. Ellos hacen implemento ICollection
, por supuesto-estoy adivinando principalmente para la propiedad Count
solos, pero que conduce a la optimización de código extraño como esto:
// OK, so to accommodate those bastard Queue<T> and Stack<T> types,
// we will just accept any IEnumerable<T>...
static IEnumerable<T> FirstHalf<T>(this IEnumerable<T> source)
{
int count = CountQuickly<T>(source);
/* ... */
}
// Then, assuming we've got a collection type with a Count property,
// we'll use that...
static int CountQuickly<T>(IEnumerable collection)
{
// Note: I realize this is basically what Enumerable.Count already does
// (minus the exception); I am just including it for clarity.
var genericColl = collection as ICollection<T>;
if (genericColl != null)
{
return genericColl.Count;
}
var nonGenericColl = collection as ICollection;
if (nonGenericColl != null)
{
return nonGenericColl.Count;
}
// ...or else we'll just throw an exception, since this collection
// can't be counted quickly.
throw new ArgumentException("Cannot count this collection quickly!");
}
¿No tendría más sentido que acaba de abandonar el ICollection
interfaz completamente (no me refiero a abandonar la implementación, por supuesto, ya que eso sería un cambio radical, solo quiero decir, dejar de usarlo), y simplemente implementar ICollection<T>
con implementación explícita para los miembros que no tienen un perfecto ¿partido?
Quiero decir, mira lo que ICollection<T>
ofertas:
Count
-Queue<T>
Stack<T>
y ambos tienen esto.IsReadOnly
-Queue<T>
yStack<T>
fácilmente podría tener esto.Add
-Queue<T>
podría implementar esto explícitamente (conEnqueue
), como podríaStack<T>
(conPush
).Clear
- Comprobar.Contains
- Comprobar.CopyTo
- Comprobar.GetEnumerator
- Comprobar (duh).Remove
- Este es el único queQueue<T>
yStack<T>
no tienen una combinación perfecta.
Y aquí es el verdadero problema: ICollection<T>.Remove
devuelve un bool
; por lo que una implementación explícita de Queue<T>
podía totalmente (por ejemplo) comprobar si el elemento que desea eliminar es en realidad el elemento de cabeza (usando Peek
), y si es así, llame Dequeue
y volver true
, de lo contrario volver false
. Stack<T>
fácilmente se podría dar una implementación similar con Peek
y Pop
.
Muy bien, ahora que he escrito sobre mil palabras sobre por qué que piensan que esto sería posible, planteo la pregunta obvia: ¿por no lo hicieron los diseñadores de Queue<T>
y Stack<T>
implementar esta interfaz ? Es decir, ¿cuáles fueron los factores de diseño (que probablemente no estoy considerando) que llevaron a la decisión de que esta sería la elección incorrecta? ¿Por qué se implementó ICollection
en su lugar?
Me pregunto si, en el diseño de mis propios tipos, hay algunos principios rectores que debería considerar con respecto a la implementación de la interfaz que podría pasar por alto al hacer esta pregunta. Por ejemplo, ¿se considera una mala práctica implementar explícitamente interfaces que no son totalmente compatibles en general (si es así, parece estar en conflicto con, por ejemplo, List<T>
implementando IList
)? ¿Existe una desconexión conceptual entre el concepto de una cola/pila y qué representa ICollection<T>
?
Básicamente, tengo la sensación de que debe haber una buena razón Queue<T>
(por ejemplo) no lo hace implementar ICollection<T>
, y yo no quiero ir a ciegas diseñar mis propios tipos de interfaces e implementación de una inadecuada manera sin estar informado y pensando completamente en lo que estoy haciendo.
Me disculpo por la súper larga pregunta.
Sólo otro ejemplo de las bibliotecas de recolección pobres de MS. Este problema podría haberse resuelto al menos separando las interfaces en versiones de solo lectura y de lectura y escritura (con la versión de lectura-escritura heredando de la versión de solo lectura) y también teniendo interfaces más finas, centrándose solo en una aspecto específico de una colección en lugar de incluir propiedades y métodos irrelevantes. Por lo tanto, ICollection no debería haber incluido Agregar, Borrar o Eliminar. Eso debería haberse dejado, por ejemplo, IMutableCollection . El ICollection podría ser implementado por más clases. –
siride
@siride: su vista de las bibliotecas de colección MS refleja la mía. Es molesto que cosas como las propiedades indexadas tengan que definirse dos veces para las versiones de solo lectura y de lectura y escritura, pero tal es la vida. – supercat
+1 para que la innovación implemente 'Remove' solo, eso fue genial :) Y no, esta super-long está bien escrita q. – nawfal