Avant même que je demande, laissez-moi obtenir la réponse évidente à l'écart: L'interface ICollection<T>
inclut une méthode Remove
pour supprimer un élément arbitraire, qui Queue<T>
et Stack<T>
ne peut pas vraiment prendre en charge (car ils ne peuvent supprimer "fin" éléments).Pourquoi la file d'attente (T) et la pile (T) n'implémentent pas ICollection (T)?
OK, je m'en rends compte. En fait, ma question ne concerne pas spécifiquement les types de collection Queue<T>
ou Stack<T>
; plutôt, il s'agit de la décision de conception de ne pas mettre en œuvre ICollection<T>
pour tout type générique qui est essentiellement une collection de valeurs T
.
Voici ce que je trouve étrange. Dites que j'ai une méthode qui accepte une collection arbitraire de T
, et pour le but du code que j'écris, il serait utile de connaître la taille de la collection. Par exemple (le code ci-dessous est trivial, pour illustration!):
// 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;
}
}
}
Maintenant, il n'y a vraiment aucune raison pour que ce code ne pourrait fonctionner sur un Queue<T>
ou Stack<T>
, sauf que ces types ne mettent pas en œuvre ICollection<T>
. Ils font mettre en œuvre ICollection
, bien sûr, Je devine principalement sur la propriété Count
seul, mais qui conduit à un code d'optimisation bizarre comme ceci:
// 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!");
}
Ne serait-il plus logique de simplement abandonner le ICollection
interface complètement (je ne veux pas dire abandonner l'implémentation, bien sûr, car ce serait un changement de rupture, je veux juste dire, cesser de l'utiliser), et simplement mettre en œuvre ICollection<T>
avec une implémentation explicite pour les membres qui n'ont pas rencontre?
Je veux dire, regardez ce ICollection<T>
offres:
Count
-Queue<T>
etStack<T>
ont tous deux cela.IsReadOnly
-Queue<T>
etStack<T>
facilement pourrait avoir ceci.Add
-Queue<T>
pourrait implémenter cela explicitement (avecEnqueue
), tout commeStack<T>
(avecPush
).Clear
- Vérifier.Contains
- Vérifier.CopyTo
- Vérifier.GetEnumerator
- Vérifiez (duh).Remove
- Ceci est le seul queQueue<T>
etStack<T>
ne correspondent pas parfaitement.
Et voici le vrai kicker: ICollection<T>.Remove
renvoie un bool
; si une mise en œuvre explicite pour Queue<T>
pourrait tout à fait (par exemple) vérifier si l'élément à supprimer est en fait l'élément de tête (en utilisant Peek
), et le cas échéant, appeler Dequeue
et retourner true
, sinon retour false
. Stack<T>
pourrait facilement être donné une mise en œuvre similaire avec Peek
et Pop
.
Bon, maintenant que je l'ai écrit au sujet de mille mots sur pourquoi je pense que ce serait possible, je pose la question évidente: pourquoi ne l'ont pas les concepteurs de Queue<T>
et Stack<T>
implémenter cette interface ? C'est-à-dire, quels étaient les facteurs de conception (que je ne considère probablement pas) qui ont mené à la décision que ce serait le mauvais choix? Pourquoi est-ce que ICollection
a été implémenté à la place? Je me demande si, en concevant mes propres types, il y a des principes directeurs que je devrais considérer en ce qui concerne la mise en œuvre d'interface que je pourrais négliger en posant cette question. Par exemple, est-il simplement considéré comme une mauvaise pratique d'implémenter explicitement des interfaces qui ne sont pas entièrement prises en charge en général (si tel est le cas, cela semble entrer en conflit avec, par exemple, List<T>
implémentant IList
)? Y at-il un conceptuel déconnecter entre le concept d'une file d'attente/pile et ce que ICollection<T>
est censé représenter?
Fondamentalement, je sens qu'il doit y avoir une bonne raison Queue<T>
(par exemple) ne le fait pas mettre en œuvre ICollection<T>
, et je ne veux pas aller aveuglément la conception avant mes propres types et mettre en œuvre des interfaces dans un inappropriée manière sans être informé et en pleine réflexion à travers ce que je fais.
Je m'excuse pour la question super-longue.
un autre exemple des bibliothèques de collecte pauvres de MS. Ce problème aurait pu être résolu en séparant au minimum les interfaces en versions lecture seule et en lecture-écriture (avec la version en lecture-écriture héritée de la version en lecture seule) et en ayant également des interfaces plus fines, en se concentrant uniquement sur un aspect spécifique d'une collection plutôt que d'inclure des propriétés et des méthodes non pertinentes. ICollection n'aurait donc pas dû inclure Add, Clear ou Remove. Cela aurait dû être laissé à, par exemple, IMableCollection . ICollection pourrait être implémenté par plus de classes. –
siride
@siride: Votre vue des bibliothèques de la collection MS reflète la mienne. Il est fastidieux que des choses comme les propriétés indexées soient définies deux fois pour les versions en lecture seule et en lecture-écriture, mais c'est la vie. – supercat
+1 pour l'innovation à implémenter 'Remove' seul, c'était cool :) Et non, ce q super-long est bien écrit. – nawfal