为什么Queue(T)和Stack(T)没有实现ICollection(T)?

时间:2011-01-23 20:12:51

标签: .net interface stack queue icollection

在我问之前,让我得到明显的答案: ICollection<T>界面包含一个Remove方法来删除任意元素,Queue<T>Stack<T>无法真正支持(因为它们只能删除“结束”元素)。

好的,我意识到了。实际上,我的问题并不是专门针对Queue<T>Stack<T>集合类型;相反,它是关于不为{em>任何泛型类型实现ICollection<T>的设计决策,它基本上是T值的集合。

这是我觉得奇怪的事情。假设我有一个方法接受T的任意集合,并且出于我正在编写的代码的目的,知道集合的大小会很有用。例如(下面的代码很简单,仅供参考!):

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

现在,这个代码无法在Queue<T>Stack<T>上运行,除非这些类型没有实现ICollection<T>。当然,他们实现ICollection - 我主要猜测的是Count属性 - 但这会产生如此奇怪的优化代码:

// 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!");
}

仅仅完全放弃ICollection界面会不会更有意义(当然,我并不是说放弃实施,因为这将是一个突破性的变化;我只是意味着,停止使用它,并简单地为没有完美匹配的成员实施ICollection<T>并明确实施?

我的意思是,看看ICollection<T>提供的内容:

  • Count - Queue<T>Stack<T>都有此。
  • IsReadOnly - Queue<T>Stack<T>轻松可以拥有此内容。
  • Add - Queue<T>可以明确地实现此功能(使用Enqueue),Stack<T>(使用Push)。
  • Clear - 检查。
  • Contains - 检查。
  • CopyTo - 检查。
  • GetEnumerator - 检查(duh)。
  • Remove - 这是唯一一个Queue<T>Stack<T>没有完美匹配的人。

这是真正的踢球者:ICollection<T>.Remove 返回bool ;所以Queue<T>的显式实现可以完全(例如)检查要删除的项是否实际 head元素(使用Peek),如果是,则调用{ {1}}并返回Dequeue,否则返回true。使用falseStack<T>可以很容易地为Peek提供类似的实施。

好吧,既然我已经写了大约一千个关于为什么认为这是可能的话,我提出了一个明显的问题:为什么没有 PopQueue<T>的设计师实现了这个界面吗?那就是,设计因素是什么(我可能没有考虑)导致决定这将是错误的选择?为什么要实施Stack<T>

我想知道,在设计我的自己的类型时,是否有任何关于接口实现应该考虑的指导原则,我可能会在提出这个问题时忽略它。例如,显然实现一般不完全支持的接口只是被认为是不好的做法(如果是这样,这似乎与实现ICollection的{​​{1}}冲突?队列/堆栈的概念与List<T>的意图之间是否存在概念断开连接?

基本上,我觉得必须有一个很好的理由IList(例如)实施ICollection<T>,我不想只是去盲目地设计我自己的类型并以不恰当的方式实现接口,而不是通知我并正在全面思考我正在做的事情。

我为超长问题道歉。

3 个答案:

答案 0 :(得分:5)

我不能给出“实际思考是什么”的答案 - 也许其中一位设计师会给我们真正的 thinkng,我可以删除它。

然而,让自己置身于“如果有人来找我做出这个决定”的心态,我可以想出答案......让我用这段代码来说明:

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

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

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

(当然,任何人都可以创建ICollection<T>的错误实现,但我们希望框架能够设置示例)。让我们假设Stack / Queue在您在问题中陈述时实现。那么上面的代码是正确的,还是有边缘案例错误,因为应该检查ICollection<T>.Remove()?如果必须删除valueA,如何解决此问题以使用Stack Queue?有答案,但显然上面的代码在这种情况下是错误的 - 即使它闻起来很合理。

所以这两种解释都是有效的,但是我对这里做出的决定很满意 - 如果我有上面的代码并且知道我可以传递一个我可以围绕它设计的队列或堆栈,但它确实很容易陷入错误的坑(在你看到ICollection<T>的任何地方,记住要移除的边缘情况!)

答案 1 :(得分:1)

最终也许他们不是理想的契合;如果您需要列表或集合,请使用List<T>Collection<T>; p

Re Add - 隐含的假设是Add添加到集合的 end ,这对堆栈来说不是真的(尽管它是针对队列的) )。在某些方面,它实际上让我感到困惑的是,堆栈/队列的枚举器不会出队/弹出,因为我主要期望每个(仅一次)获取队列/堆栈中的项目。 / p>

也许(再次,使用我的枚举器作为一个例子)人们根本无法就某些情况下应该表现出的如何达成一致,并且缺乏完全一致,只需不实施是一个更好的选择。

答案 2 :(得分:0)

菲利普给出了一个很好的答案(+1)。还有另一个概念承诺Remove将为Stack<T>打破。 ICollection<T>.Remove记录为:

  

从ICollection中删除特定对象的 第一个

Stack<T>是LIFO,即使实现了Remove,也必须在重复对象的情况下删除最后一次出现。

如果它对Stack<T>没有意义,那么对于它的同等和相反的堂兄来说,它可以更好地避免。


如果MS:

,我会更喜欢它
  • 没有像Remove那样记录ICollection<T>。考虑到各种结构的内部实现有多么不同,在某处删除相等的对象会更有意义。强制删除第一项似乎受到线性搜索简单结构(如数组)的影响。

  • 具有队列结构的接口。可能是:

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