15

質問する前に、明白な答えを教えてくださいICollection<T>インターフェイスにはRemove、任意の要素を削除するメソッドが含まれていますが、これは実際Queue<T>Stack<T>はサポートできません (「終了」要素しか削除できないため)。

わかりました。実際、私の質問はコレクション型Queue<T>またはStack<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;
        }
    }
}

さて、これらの型が を実装していないことを除いて、このコードがQueue<T>や で動作できない理由は本当にありません。もちろん、彼らは を実装しています — 私は主にプロパティだけを推測しています — しかし、それは次のような奇妙な最適化コードにつながります:Stack<T>ICollection<T>ICollectionCount

// 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>明示的に実装できます( を使用)。EnqueueStack<T>Push
  • Clear- 小切手。
  • Contains- 小切手。
  • CopyTo- 小切手。
  • GetEnumerator-- 確認してください (当たり前)。
  • Remove--完全に一致ないのはこれだけです。Queue<T>Stack<T>

そして、これが本当のキッカーです: ;ICollection<T>.Remove を返します。boolしたがって、 の明示的な実装は、削除する項目が実際に head 要素Queue<T>であるかどうかを (たとえば) 完全にチェックし( を使用)、そうであれば を呼び出して returnを、そうでなければ returnを返すことができます。と を使用して同様の実装を簡単に行うことができます。PeekDequeuetruefalseStack<T>PeekPop

さて、これが可能であると考える理由について約 1000 語を書いたので、明らかな疑問を提起します。なぜこのインターフェースの設計者と実装者はこのインターフェースを実装しなかったのでしょうか? 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>、フレームワークが例を設定することを期待しています)。質問で述べたように、スタック/キューが実装されていると仮定しましょう。上のコードは正しいですか、それともICollection<T>.Remove()チェックする必要があるため、エッジケースのバグがありますか? 削除する必要がある場合、スタックキューvalueAの両方で動作するように修正するにはどうすればよいですか? 答えはありますが、この場合、上記のコードは明らかに間違っています。

したがって、両方の解釈は有効ですが、ここで下した決定には満足しています。上記のコードがあり、それを中心に設計できるキューまたはスタックを渡すことができるとわかっていれば、簡単にバグピットになることは間違いありません。に陥ります (どこを見ICollection<T>ても、削除のエッジ ケースを覚えておいてください!)

于 2011-01-24T17:19:56.663 に答える
1

最終的には、おそらくそれらは理想的な適合ではありません。リストまたはコレクションが必要な場合 - aList<T>またはCollection<T>;pを使用

Re - コレクションの末尾に追加されるAddという暗黙の仮定がありますが、これはスタックには当てはまりません (キューの場合はそうです)。いくつかの点で、スタック/キューの列挙子がデキュー/ポップしないことは実際には混乱します。これは、キュー/スタック内のアイテムがそれぞれ 1 回 (そして 1 回のみ) フェッチされることを主に期待しているためです。Add

たぶんまた (ここでも例として私の列挙子を使用します)、人々はそれらのシナリオのいくつかでどのように動作するべきかについて正確に同意できず、完全な同意が得られず、単純にそれらを実装しないことがより良い選択でした.

于 2011-01-23T20:20:34.520 に答える
0

フィリップは良い答えを出しました (+1)。Removeのために壊れる別の概念的な約束がありStack<T>ます。は次のICollection<T>.Removeように文書化されています。

ICollection から特定のオブジェクトの最初の出現を削除します。

Stack<T>LIFORemoveが実装されていても、オブジェクトが重複している場合は最後のオカレンスを削除する必要があります。

それが意味をなさない場合は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();
    }
    
于 2014-06-08T09:34:41.640 に答える