1

編集

与えられた回答から、以下で質問している設計を実際にどのように実装する必要があるかがかなり明確になりました。これらの提案を念頭に置いて (そして、私のコード例はコンパイルすらしないことを丁寧に指摘するコメントに応えて)、一般的なコンセンサスを反映するように次のコードを編集しました。残っている質問は、コードを考えるともはや意味をなさないかもしれませんが、後世のためにそのままにしておきます。


次のような関数の 3 つのオーバーロードがあるとしIEnumerable<T>ます。ICollection<T>IList<T>

public static T GetMiddle<T>(IEnumerable<T> values) {
    IList<T> list = values as IList<T>;
    if (list != null) return GetMiddle(list);

    int count = GetCount<T>(values);

    T middle = default(T);
    int index = 0;

    foreach (T value in values) {
        if (index++ >= count / 2) {
            middle = value;
            break;
        }
    }

    return middle;
}

private static T GetMiddle<T>(IList<T> values) {
    int middleIndex = values.Count / 2;
    return values[middleIndex];
}

private static int GetCount<T>(IEnumerable<T> values) {
    // if values is actually an ICollection<T> (e.g., List<T>),
    // we can get the count quite cheaply
    ICollection<T> genericCollection = values as ICollection<T>;
    if (genericCollection != null) return genericCollection.Count;

    // same for ICollection (e.g., Queue<T>, Stack<T>)
    ICollection collection = values as ICollection;
    if (collection != null) return collection.Count;

    // otherwise, we've got to count values ourselves
    int count = 0;
    foreach (T value in values) count++;

    return count;
}

ここでの考え方は、 を持っていればIList<T>仕事が楽になるということです。一方、または;ICollection<T>でさえも仕事をすることができます。IEnumerable<T>これらのインターフェイスの実装は効率的ではありません。

これが機能するかどうかはわかりませんでしたが (ランタイムが渡されたパラメーターに基づいてオーバーロードを選択できる場合)、テストしたところ、機能するようです。

私の質問は次のとおりです。私が考えていなかったこのアプローチに問題はありますか? あるいは、これは実際には良いアプローチですが、それを達成するためのより良い方法があります (おそらく、values引数をIList<T>最初にキャストし、キャストが機能する場合はより効率的なオーバーロードを実行することによって)? 他の人の考えを知りたいだけです。

4

7 に答える 7

5

Reflector を使用して LINQ 拡張メソッドがどのように実装されているかを見ると、Count() などの IEnumerable<T> のいくつかの拡張メソッドが、シーケンスを ICollection<T> または IList< にキャストしようとしていることがわかります。 T> を使用して操作を最適化します (たとえば、IEnumerable<T> を反復処理して要素をカウントする代わりに、ICollection<T>.Count プロパティを使用します)。したがって、IEnumerable<T> を受け入れ、ICollection<T> または IList<T> が利用可能な場合は、この種の最適化を行うことが最善の策です。

于 2009-11-19T01:57:35.743 に答える
2

IEnumerable<T> を受け入れる 1 つのバージョンが進むべき道だと思います。パラメーターがより派生したコレクション型の 1 つであるかどうかをメソッド内で確認します。あなたが提案する 3 つのバージョンでは、コンパイラが静的に IEnumerable<T> と見なす (ランタイム) IList<T> を誰かが渡すと、効率の利点が失われます。

        IList<string> stringList = new List<string> { "A", "B", "C" };
        IEnumerable<string> seq = stringList;
        Extensions.GetMiddle(stringList); // calls IList version
        Extensions.GetMiddle(seq);        // calls IEnumerable version
于 2009-11-19T02:08:59.307 に答える
1

基本型または派生型のいずれかを受け入れるようにメソッドをオーバーロードすることは合法ですが、それ以外の場合は他のすべてのパラメーターが同一であり、コンパイラーが後者の形式の方が優れているとしばしば識別できる場合にのみ、そうすることが有利です。マッチ。ICollection<T>のみを必要とするコードによって実装されるオブジェクトが渡さIEnumerable<T>れることは非常に一般的であるため、 の実装がオーバーロードICollection<T>に渡されることは非常に一般的ですIEnumerable<T>。したがって、IEnumerable<T>オーバーロードは、渡されたオブジェクトが実装されているかどうかを確認し、実装ICollection<T>している場合は特別に処理する必要があります。

のロジックを実装する最も自然な方法が特別なメソッドを記述することである場合、 を受け入れるパブリック オーバーロードを使用し、を実装するオブジェクトが与えられた場合にそのオーバーロードをオーバーロードで呼び出すICollection<T>ことに特に問題はありません。このようなオーバーロードを public にしてもあまり価値はありませんが、害はないでしょう。一方、オブジェクトが と の両方を実装しているが実装していない状況(たとえば、 と を実装しているが は実装していない) では、両方のインターフェースを使用したい場合がありますが、それは、それらの両方を使用するメソッドを渡すか、ICollection<T>IEnumerable<T>ICollection<T>ICollection<T>IEnumerable<T>ICollectionICollection<T>List<Cat>IEnumerable<Animal>ICollectionICollection<Animal>ICollection参照とIEnumerable<T>参照。後者は public メソッドでは非常に見苦しく、前者のアプローチはオーバーロードの利点を失います。

于 2012-10-18T17:55:35.040 に答える
1

これは一般的ではなく、混乱を招く可能性があるため、パブリック API としては適切な選択ではないと思います。

IEnumerable<T> パラメーターを受け入れ、それが実際に ICollection<T> または IList<T> であるかどうかを内部的にチェックし、それに応じて最適化することができます。

これは、.NET 3.5 Framework の一部の IEnumerable<T> 拡張メソッドの一部の最適化に類似している可能性があります。

于 2009-11-19T01:55:13.417 に答える
1

私は本当に無関心です。私があなたのようにそれを見たら、私はそれについて何も考えないでしょう. しかし、ジョーのアイデアにはメリットがあります。次のようになります。

public static T GetMiddle<T>(IEnumerable<T> values)
{
  if (values is IList<T>) return GetMiddle((IList<T>)values);
  if (values is ICollection<T>) return GetMiddle((ICollection<T>)values);

  // Use the default implementation here.
}

private static T GetMiddle<T>(ICollection<T> values)
{
}

private static T GetMiddle<T>(IList<T> values)
{
}
于 2009-11-19T02:13:33.213 に答える
0

通常、インターフェイスを設計するときは、引数に「最小公分母」型を受け入れたいと考えます。戻り値の型については、いくつかの議論の問題です。一般的に、上記のオーバーロードを作成するのはやり過ぎだと思います。最大の問題は、テストが必要な不要なコード パスが導入されることです。1 つの方法で操作を実行し、100% の時間で動作するメソッドを 1 つ用意することをお勧めします。上記の指定されたオーバーロードを使用すると、動作に矛盾があり、それを認識していない可能性があります。さらに悪いことに、誤って 1 つのコピーに変更を導入し、他のコピーには変更を導入しない可能性があります。

できる場合はそれをIEnumerable<T>使用し、そうでない場合は必要最小限のインターフェイスを使用します。

于 2009-11-19T01:54:46.927 に答える
0

いいえ、それは確かに珍しいことです。

ともかく。IList<T> は ICollection<T> および IEnumerable<T> から継承し、ICollection<T> は IEnumerable<T> から継承するため、IEnumerable<T> 型でのパフォーマンスだけが問題になります。

そのように関数をオーバーロードし、まったく同じ結果を得るためにさまざまなシグネチャを提供し、パラメーターとしてまったく同じ型を受け入れる理由はわかりません (IEnumerable<T> または IList<T> があるかどうかに関係なく、 3 つのオーバーロードのいずれかに渡すことができます); それは混乱を招くだけです。

関数をオーバーロードするときは、その署名がない場合に関数に渡すことができない別のタイプのパラメーターを渡す方法を提供するだけです。

本当に必要でない限り、最適化しないでください。最適化したい場合は、秘密裏に実行してください。どのメソッド シグネチャを使用するかを決定するために、クラスを使用している誰かがその「最適化」を認識しているふりをすることはありませんよね?

于 2009-11-19T02:09:33.487 に答える