web-dev-qa-db-ja.com

IList <T>およびIReadOnlyList <T>

パラメータを必要とするメソッドがある場合、

  • Countプロパティを持っています
  • 整数インデクサーがあります(取得専用)

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

しかし、.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>_を実装するクラスを見つけることは非常にまれです。

34
Şafak Gür

あなたはここで不運です。 _IList<T>_は_IReadOnlyList<T>_を実装しません。 _List<T>_は両方のインターフェースを実装しますが、それはあなたが望むものではないと思います。

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

  • Count()拡張メソッドは、インスタンスが実際にコレクションであるかどうかを内部的にチェックし、Countプロパティを使用します。
  • ElementAt()拡張メソッドは、インスタンスが実際にリストであり、インデクサーを使用するかどうかを内部的にチェックします。
23
Daniel Hilgarth

[〜#〜] dry [〜#〜] のパフォーマンスを維持することに関心がある場合、coulddynamic を使用します。

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

ただし、落とし穴が大きすぎるため、これをお勧めすることを誠実に言うことはできません。

  • collection内のDoInternalのすべての呼び出しは、実行時に解決されます。型安全性、コンパイル時チェックなどを失います。
  • パフォーマンスの低下(深刻ではないが、特異なケースではあるが、集約される場合もある)willが発生する

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

つまり、IReadOnlyList<T>を受け取り、IList<T>実装でラッパーを返すAsListラッパーを作成する必要があります。

ただし、(データを変更していないという事実を強調するために)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);
}
4
casperOne

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

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

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

1
Jon

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

私はあなたのことを知りませんが、このインターフェースは非常に長い間行方不明であったに違いないと思います。

0
MaYaN