Question

Avant que je demande même, me laisser la réponse évidente de la façon suivante: L'interface ICollection<T> comprend une méthode Remove pour supprimer un élément arbitraire qui Queue<T> et Stack<T> ne peuvent pas vraiment le soutien (car ils ne peuvent retirer éléments "fin").

OK, je me rends compte que. En fait, ma question n'est pas spécifiquement sur les types de collecte Queue<T> ou de Stack<T>; plutôt, il est au sujet de la décision de conception de ne pas mettre en œuvre ICollection<T> pour any type générique qui est essentiellement une collection de valeurs T.

Voici ce que je trouve bizarre. Dire que j'ai une méthode qui accepte une collection arbitraire de T, et dans le but du code que je vous écris, il serait utile de connaître la taille de la collection. Par exemple (le ci-dessous le code 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 faire mettre en œuvre ICollection, bien sûr, je suis deviner principalement pour 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!");
}

Ce ne serait pas plus logique juste abandonner l'interface ICollection complètement (je ne baisse pas dire la mise en œuvre, bien sûr, car ce serait un changement de rupture, je viens de moyenne, arrêt l'utiliser), et tout simplement mettre en œuvre ICollection<T> mise en œuvre explicite pour les membres qui ne disposent pas d'un match parfait?

Je veux dire, regardez ce que les offres de ICollection<T>:

  • Count -. Queue<T> et Stack<T> ont tous deux cette
  • IsReadOnly -. Queue<T> et Stack<T> facilement peut cette
  • Add -. Queue<T> pourrait mettre en œuvre explicitement (avec Enqueue), comme on pouvait Stack<T> (avec Push)
  • Clear - Vérifier
  • .
  • Contains - Vérifier
  • .
  • CopyTo - Vérifier
  • .
  • GetEnumerator -. Check (duh)
  • Remove -. Ceci est le seul qui Queue<T> et Stack<T> ne sont pas un match parfait pour

Et voici le kicker réel: ICollection<T>.Remove retourne un bool ; si une mise en œuvre explicite pour Queue<T> pourrait vérifier totalement (par exemple) si l'élément à supprimer est en fait l'élément de tête (en utilisant Peek), et le cas échéant, appel Dequeue et true de retour, sinon retour false. Stack<T> pourrait facilement recevoir une mise en œuvre similaire avec Peek et Pop.

Bon, maintenant que je l'ai écrit au sujet de mille mots pourquoi I pense que ce serait possible, je pose la question évidente: pourquoi n'a pas les concepteurs de Queue<T> et Stack<T> implémenter cette interface? est, quels ont été les facteurs de conception (que je ne suis probablement pas envisageais) qui a conduit à la décision que ce serait le mauvais choix? Pourquoi at-on mis en œuvre à la place ICollection?

Je me demande si, dans la conception de mon propre types, il y a des principes directeurs que je dois prendre en considération en ce qui concerne la mise en œuvre de l'interface que je pourrais être en méconnais poser cette question. Par exemple, est-il simplement considéré comme une mauvaise pratique à mettre en œuvre explicitement les interfaces qui ne sont pas entièrement prises en charge en général (si oui, cela semble entrer en conflit avec, par exemple, la mise en œuvre List<T> IList)? Y at-il une conceptuel décalage entre le concept d'une file d'attente / pile et ce que ICollection<T> est censé représenter?

Basiquement, je sens qu'il doit y avoir une bonne raison Queue<T> (par exemple) ne 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 de manière inappropriée sans être informé et pleinement réfléchir à ce que je fais.

Je vous présente mes excuses pour la question de super long.

Était-ce utile?

La solution

Je ne peux pas donner « ce qui était la pensée réelle » réponse - peut-être l'un des designers nous donnera les réel thinkng et je peux supprimer cela.

Cependant, me mettre dans l'état d'esprit de « si quelqu'un est venu me prendre cette décision », je peux penser à une réponse .. laissez-moi d'illustrer avec ce code:

public void SomeMethod<T>( ICollection<T> collection, T valueA, T valueB)
{

  collection.Add( valueA);
  collection.Add( valueB);

  if( someComplicatedCondition())
  {
    collection.Remove(valueA);
  }
}

(Bien sûr, tout le monde peut créer une mauvaise mise en œuvre de ICollection<T>, mais nous nous attendons à ce cadre pour donner l'exemple). Supposons que la pile / file d'attente mise en œuvre comme vous l'avez dans la question. Donc, est le code ci-dessus à droite, ou at-il des bugs de cas de pointe parce que ICollection<T>.Remove() doit être vérifié? Si valueA DOIT être enlevé, comment puis-je résoudre ce problème à travailler avec les deux Stack et File d'attente? Il y a des réponses, mais de toute évidence le code ci-dessus est faux dans ce cas -. Même si elle sent raisonnable

Ainsi, les deux interprétations sont valables, mais je suis bien avec la décision prise ici - si j'avais le code ci-dessus et savais que je pouvais être passé une file d'attente ou la pile que je pouvais concevoir autour de lui, mais il vous serait facile pit bug de tomber dans (partout où vous voyez ICollection<T>, rappelez-vous les cas de pointe pour supprimer!)

Autres conseils

En fin de compte peut-être qu'ils sont tout simplement pas fits idéal; si vous voulez une liste ou d'une collection - utiliser un List<T> ou Collection<T>; p

Re Add - il existe une hypothèse implicite selon laquelle Add ajoute à la end de la collection, ce qui est faux d'une pile (bien que ce soit pour une file d'attente). D'une certaine façon, il me confond en fait que le recenseur pour pile / file d'attente ne pas dequeue / pop, puisque je pense en grande partie des éléments dans une file d'attente / pile à extraire une fois par (et une seule fois).

Peut-être aussi (encore une fois, en utilisant mon recenseur juste un exemple) les gens ne pouvaient tout simplement pas d'accord sur exactement comment il doit se comporter dans certains de ces scénarios, et sans accord complet, simplement ne pas les mettre en œuvre était un meilleur choix.

Philippe a donné une bonne réponse (+1). Il y a une autre promesse conceptuelle qui Remove plantera pour Stack<T>. ICollection<T>.Remove est documentée comme:

Supprime first occurrence d'un objet spécifique du ICollection.

Stack<T> est LIFO, même si Remove a été mis en œuvre, il devra enlever la dernière occurrence dans le cas d'objets en double.

S'il ne marche pas de sens pour Stack<T>, il vaut mieux évitable pour sa cousine égale et opposée.


Je l'aurais aimé mieux si MS:

  • n'a pas le document Remove pour ICollection<T> comme ça. La suppression d'un objet quelque part d'égal aurait été plus logique compte tenu comment très différentes implémentations internes de différentes structures sont. Forcer la suppression des premiers regards de l'objet comme influencé par la recherche linéaire des structures simples comme des tableaux.

  • eu interfaces pour les structures de file d'attente. Peut-être:

    public interface IPeekable<T> : IEnumerable<T> // or IInOut<T> or similar
    {
        int Count { get; }
    
        bool Contains(T item);
        T Peek();
        void Add(T item);
        bool Remove();
        void Clear();
    }
    
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top