15

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

好的,我意识到这一点。实际上,我的问题并不是专门针对Queue<T>orStack<T>集合类型。相反,它是关于不为本质上是值集合的任何泛型类型实现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;
        }
    }
}

现在,这段代码真的没有理由不能对 aQueue<T>或 a进行操作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>可以明确地实现这一点(with Enqueue),也可以Stack<T>(with Push)。
  • Clear- 查看。
  • Contains- 查看。
  • CopyTo- 查看。
  • GetEnumerator——检查(呃)。
  • Remove——这是唯一一个没有完美匹配的人Queue<T>Stack<T>

这是真正的踢球者:ICollection<T>.Remove 返回 abool ; 所以一个显式的实现Queue<T>可以完全(例如)检查要删除的项目是否实际上是头元素(使用Peek),如果是,则调用Dequeue并返回true,否则返回false。可以很容易地用和Stack<T>给出类似的实现。PeekPop

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

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

基本上,我觉得必须有一个很好的理由Queue<T>(例如)实现ICollection<T>,并且我不想只是盲目地设计自己的类型并以不适当的方式实现接口而没有被告知和充分考虑我在做什么。

我为这个超长的问题道歉。

4

3 回答 3

6

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

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

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必须删除,我该如何解决这个问题以同时使用堆栈队列?有答案,但显然上面的代码在这种情况下是错误的——即使它闻起来很合理。

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

于 2011-01-24T17:19:56.663 回答
1

最终,也许它们并不理想。如果你想要一个列表或集合 - 使用 aList<T>Collection<T>;p

重新Add- 有一个隐含的假设Add添加到集合的末尾,这不适用于堆栈(尽管它适用于队列)。在某些方面,它实际上让我感到困惑,堆栈/队列的枚举器不会出队/弹出,因为我主要希望队列/堆栈中的项目每次(并且仅一次)获取一次。

也可能(同样,仅以我的枚举器为例)人们根本无法就它在某些场景中的行为方式达成一致,并且缺乏完全一致的意见,干脆实施它们是一个更好的选择。

于 2011-01-23T20:20:34.520 回答
0

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

从 ICollection 中删除特定对象的第一个匹配项。

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

如果它对 没有意义Stack<T>,那么对于它的平等和相反的堂兄来说,它是最好避免的。


如果 MS:

  • 没有那样记录RemoveICollection<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();
    }
    
于 2014-06-08T09:34:41.640 回答