46

次のようなパラメータを必要とするメソッドがある場合、

  • Countプロパティがあります
  • 整数インデクサーがあります(get-only)

このパラメータのタイプはどうあるべきですか?IList<T>これには他にインデックス可能なコレクションインターフェイスがなく、配列がそれを実装しているため、.NET4.5より前を選択しました。これは大きなプラスです。

しかし、.NET 4.5は新しいIReadOnlyList<T>インターフェースを導入しており、私のメソッドもそれをサポートしたいと思っています。DRYのような基本原則に違反することなく、両方IList<T>をサポートするためにこのメソッドを作成するにはどうすればよいですか?IReadOnlyList<T>

編集:ダニエルの答えは私にいくつかのアイデアを与えました:

public void Foo<T>(IList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

public void Foo<T>(IReadOnlyList<T> list)
    => Foo(list, list.Count, (c, i) => c[i]);

private void Foo<TList, TItem>(
    TList list, int count, Func<TList, int, TItem> indexer)
    where TList : IEnumerable<TItem>
{
    // Stuff
}

編集2:IReadOnlyList<T>または、次のようなヘルパーを受け入れて提供することもできます。

public static class CollectionEx
{
    public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> list)
    {
        if (list == null)
            throw new ArgumentNullException(nameof(list));

        return list as IReadOnlyList<T> ?? new ReadOnlyWrapper<T>(list);
    }

    private sealed class ReadOnlyWrapper<T> : IReadOnlyList<T>
    {
        private readonly IList<T> _list;

        public ReadOnlyWrapper(IList<T> list) => _list = list;

        public int Count => _list.Count;

        public T this[int index] => _list[index];

        public IEnumerator<T> GetEnumerator() => _list.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
    }
}

それなら私はそれを次のように呼ぶことができますFoo(list.AsReadOnly())


編集3: 配列はとの両方IList<T>を実装IReadOnlyList<T>し、List<T>クラスも実装します。これにより、を実装するが実装IList<T>しないクラスを見つけることは非常にまれになりますIReadOnlyList<T>

4

4 に答える 4

33

ここでは運が悪い。IList<T>を実装していませんIReadOnlyList<T>List<T>両方のインターフェースを実装していますが、それはあなたが望んでいることではないと思います。

ただし、LINQは使用できます。

  • 拡張メソッドはCount()、インスタンスが実際にコレクションであるかどうかを内部的にチェックしてから、Countプロパティを使用します。
  • 拡張メソッドはElementAt()、インスタンスが実際にリストであるかどうかを内部的にチェックし、インデクサーを使用します。
于 2012-10-11T11:19:05.583 に答える
5

IList<T>とは有用な「祖先」を共有しないためIReadOnlyList<T>、メソッドが他のタイプのパラメーターを受け入れたくない場合は、2つのオーバーロードを提供するだけです。

コードの再利用が最優先事項であると判断した場合は、これらのオーバーロードにより、ダニエルが提案する方法でLINQprivateを受け入れIEnumerable<T>て使用するメソッドに呼び出しを転送し、事実上、実行時にLINQに正規化を実行させることができます。

ただし、私見では、コードを1回コピーして貼り付け、引数のタイプだけが異なる2つの独立したオーバーロードを保持する方がおそらく良いでしょう。この規模のマイクロアーキテクチャが具体的なものを提供するとは思わないが、一方で、それは非自明な操作を必要とし、より遅い。

于 2012-10-11T11:26:08.093 に答える
5

パフォーマンスよりもDRYのプリンシパルを維持することに関心がある場合は、次のように使用できます。dynamic

public void Do<T>(IList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}
public void Do<T>(IReadOnlyList<T> collection)
{
    DoInternal(collection, collection.Count, i => collection[i]);
}

private void DoInternal(dynamic collection, int count, Func<int, T> indexer)
{
    // Get the count.
    int count = collection.Count;
}

ただし、落とし穴が大きすぎるため、これをお勧めするとは誠意を持って言えません。

  • でのすべての呼び出しはcollectionDoInternal実行時に解決されます。型の安全性、コンパイル時のチェックなどが失われます。
  • パフォーマンスの低下(深刻ではありませんが、単一の場合ですが、集約された場合は発生する可能性があります)発生します

あなたのヘルパーの提案が最も有用ですが、私はあなたがそれをひっくり返すべきだと思います。IReadOnlyList<T>インターフェイスが.NET4.5で導入されたことを考えると、多くのAPIはそれをサポートしていませんが、IList<T>インターフェイスはサポートしています。

とはいえ、AsListラッパーを作成する必要があります。ラッパーは、実装IReadOnlyList<T>でラッパーを取得して返します。IList<T>

IReadOnlyList<T>ただし、 (データを変更していないという事実を強調するために)使用しているAPIを強調したい場合は、AsReadOnlyList現在使用している拡張機能の方が適切ですが、次の最適化を行います。にAsReadOnly

public static IReadOnlyList<T> AsReadOnly<T>(this IList<T> collection)
{
    if (collection == null)
        throw new ArgumentNullException("collection");

    // Type-sniff, no need to create a wrapper when collection
    // is an IReadOnlyList<T> *already*.
    IReadOnlyList<T> list = collection as IReadOnlyList<T>;

    // If not null, return that.
    if (list != null) return list;

    // Wrap.
    return new ReadOnlyWrapper<T>(collection);
}
于 2012-10-11T15:33:56.217 に答える
-1

必要なのはIReadOnlyCollection<T>.Net4.5で利用可能であり、これは基本的にプロパティとしてIEnumerable<T>持っCountていますが、インデックス作成も必要な場合はIReadOnlyList<T>、インデクサーも提供する必要があります。

あなたのことはわかりませんが、このインターフェースは非常に長い間欠けていた必需品だと思います。

于 2014-06-28T22:28:33.487 に答える