为什么 Queue(T) 和 Stack(T) 没有实现 ICollection(T)? [英] Why do Queue(T) and Stack(T) not implement ICollection(T)?

查看:23
本文介绍了为什么 Queue(T) 和 Stack(T) 没有实现 ICollection(T)?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

好的,我意识到这一点.实际上,我的问题并不是关于 Queue<T>Stack<T> 集合类型;相反,它是关于不为 any 本质上是 T 值集合的泛型类型实现 ICollection<T> 的设计决策.p>

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

//为简洁起见省略了参数验证.静态 IEnumerable<T>FirstHalf<T>(此 ICollection<T> 来源){诠释 i = 0;foreach(源中的 T 项){收益退货项目;if ((++i) >= (source.Count/2)){休息;}}}

现在,没有理由说这段代码不能在 Queue<T>Stack<T> 上运行,只是这些类型不能实现ICollection.当然,他们确实实现了ICollection——我猜主要是为了Count属性——但这会导致奇怪的优化代码如下:

//OK,这样就可以容纳那些混蛋Queue和堆栈<T>类型,//我们将只接受任何 IEnumerable<T>...静态 IEnumerable<T>FirstHalf<T>(这个 IEnumerable<T> 源){int count = CountQuickly<T>(来源);/* ... */}//然后,假设我们有一个带有 Count 属性的集合类型,//我们将使用它...static int CountQuickly(IEnumerable 集合){//注意:我意识到这基本上是 Enumerable.Count 已经做的//(减去异常);为了清楚起见,我将其包括在内.var genericCol = 集合为 ICollection;if (genericColl != null){返回genericCol.Count;}var nonGenericColl = 作为 ICollection 的集合;if (nonGenericColl != null){返回 nonGenericCol.Count;}//...否则我们将抛出一个异常,因为这个集合//无法快速计算.throw new ArgumentException("不能快速统计这个集合!");}

完全放弃 ICollection 接口不是更有意义(当然,我并不是说放弃实现,因为那样会破坏改变;我的意思是,停止使用它),并简单地实现 ICollection<T> 为没有完美匹配的成员显式实现?

我的意思是,看看 ICollection<T> 提供什么:

  • Count -- Queue<T>Stack 都有这个.
  • IsReadOnly -- Queue<T>Stack<T> 很容易可以拥有这个.
  • Add -- Queue<T> 可以显式实现这一点(使用 Enqueue),Stack<T>(使用 Push).
  • 清除 -- 检查.
  • 包含 -- 检查.
  • CopyTo -- 检查.
  • GetEnumerator -- 检查(废话).
  • Remove -- 这是唯一一个 Queue<T>Stack 没有完美匹配.

这才是真正的关键:ICollection<T>.Remove 返回一个 bool;因此 Queue<T> 的显式实现可以完全(例如)检查要删除的项目是否实际上是头部元素(使用 Peek),如果是,则调用Dequeue并返回true,否则返回false.Stack<T> 可以很容易地通过 PeekPop 实现类似的实现.

好吧,既然我已经写了大约一千字来说明为什么认为这是可能的,我提出一个显而易见的问题:为什么没有em> Queue<T>Stack<T> 的设计者实现了这个接口?也就是说,设计因素是什么(我可能不是考虑)导致决定这将是错误的选择?为什么改为实现ICollection?

我想知道,在设计我自己的 类型时,我是否应该考虑关于接口实现的任何指导原则,而我在提出这个问题时可能会忽略这些指导原则.例如,显式实现一般不完全支持的接口是否被认为是不好的做法(如果是这样,这似乎与实现 IList 的 List<T> 冲突)?队列/堆栈的概念与 ICollection<T> 所代表的内容之间是否存在概念上的脱节?

基本上,我觉得Queue<T>(例如)没有实现ICollection<T>一定有很好的理由代码>,我不想盲目地设计自己的类型并以不适当的方式实现接口,而没有充分了解我正在做的事情.

对于这个超长的问题,我深表歉意.

解决方案

我不能给出实际想法是什么"的答案——也许其中一位设计师会给我们真实的想法我可以删除它.

但是,将自己置于如果有人来找我做这个决定怎么办"的心态,我可以想到一个答案..让我用这段代码来说明:

public void SomeMethod(ICollectioncollection, T valueA, T valueB){集合.Add(valueA);集合.Add(valueB);如果(一些复杂条件()){集合.删除(值A);}}

(当然,任何人都可以创建一个糟糕的 ICollection<T> 实现,但我们希望框架能够树立榜样).让我们假设您在问题中陈述的 Stack/Queue 实现.那么上面的代码是正确的,还是因为应该检查 ICollection<T>.Remove() 而存在边缘情况错误?如果必须删除 valueA,我该如何解决这个问题以便同时使用 Stack 队列?有答案,但显然上面的代码在这种情况下是错误的——尽管它闻起来很合理.

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

Before I even ask, let me get the obvious answer out of the way: The ICollection<T> interface includes a Remove method to remove an arbitrary element, which Queue<T> and Stack<T> can't really support (since they can only remove "end" elements).

OK, I realize that. Actually, my question is not specifically about the Queue<T> or Stack<T> collection types; rather, it's about the design decision of not implementing ICollection<T> for any generic type that is essentially a collection of T values.

Here's what I find odd. Say I have a method that accepts an arbitrary collection of T, and for the purpose of the code I'm writing it would be useful to know the size of the collection. For example (the below code is trivial, for illustration only!):

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

Now, there's really no reason why this code couldn't operate on a Queue<T> or a Stack<T>, except that those types don't implement ICollection<T>. They do implement ICollection, of course—I'm guessing mainly for the Count property alone—but that leads to weird optimization code like this:

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

Wouldn't it make more sense to just abandon the ICollection interface completely (I don't mean drop the implementation, of course, as that would be a breaking change; I just mean, stop using it), and simply implement ICollection<T> with explicit implementation for members that don't have a perfect match?

I mean, look at what ICollection<T> offers:

  • Count -- Queue<T> and Stack<T> both have this.
  • IsReadOnly -- Queue<T> and Stack<T> easily could have this.
  • Add -- Queue<T> could implement this explicitly (with Enqueue), as could Stack<T> (with Push).
  • Clear -- Check.
  • Contains -- Check.
  • CopyTo -- Check.
  • GetEnumerator -- Check (duh).
  • Remove -- This is the only one that Queue<T> and Stack<T> don't have a perfect match for.

And here's the real kicker: ICollection<T>.Remove returns a bool; so an explicit implementation for Queue<T> could totally (for example) check if the item to be removed is actually the head element (using Peek), and if so, call Dequeue and return true, otherwise return false. Stack<T> could easily be given a similar implementation with Peek and Pop.

All right, now that I've written about a thousand words on why I think this would be possible, I pose the obvious question: why didn't the designers of Queue<T> and Stack<T> implement this interface? That is, what were the design factors (which I am probably not considering) that led to the decision that this would be the wrong choice? Why was ICollection implemented instead?

I am wondering if, in designing my own types, there are any guiding principles I should consider with respect to interface implementation that I might be overlooking in asking this question. For example, is it just considered bad practice to explicitly implement interfaces that aren't fully supported in general (if so, this would seem to conflict with, e.g., List<T> implementing IList)? Is there a conceptual disconnect between the concept of a queue/stack and what ICollection<T> is meant to represent?

Basically, I sense that there must be a pretty good reason Queue<T> (for example) doesn't implement ICollection<T>, and I don't want to just go blindly forward designing my own types and implementing interfaces in an inappropriate manner without being informed and fully thinking through what I'm doing.

I do apologize for the super-long question.

解决方案

I can't give the "what was the actual thinking" answer - perhaps one of the designers will give us the real thinkng and I can delete this.

However, putting myself in the mindset of "what if someone came to me to make this decision", I can think of an answer.. let me illustrate with this code:

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

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

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

(Sure, anyone can create a bad implementation of ICollection<T>, but we expect the framework to set the example). Let's assume Stack/Queue implement as you state in the question. So is the code above right, or does it have edge case bugs because ICollection<T>.Remove() should be checked? If valueA MUST be removed, how do I fix this to work with both Stack and Queue? There are answers, but obviously the code above is wrong in this case - even though it smells reasonable.

So both interpretations are valid, but I'm good with the decision made here - if I had the code above and knew I could be passed a Queue or Stack that I could design around it, but it sure would be an easy bug pit to fall into (everywhere you see ICollection<T>, remember the edge cases for remove!)

这篇关于为什么 Queue(T) 和 Stack(T) 没有实现 ICollection(T)?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆