web-dev-qa-db-ja.com

メンバーコレクションを公開するためのReadOnlyCollectionまたはIEnumerable?

呼び出し元のコードがコレクションに対してのみ反復する場合、内部コレクションをIEnumerableではなくReadOnlyCollectionとして公開する理由はありますか?

class Bar
{
    private ICollection<Foo> foos;

    // Which one is to be preferred?
    public IEnumerable<Foo> Foos { ... }
    public ReadOnlyCollection<Foo> Foos { ... }
}


// Calling code:

foreach (var f in bar.Foos)
    DoSomething(f);

ご覧のとおり、IEnumerableはReadOnlyCollectionのインターフェイスのサブセットであり、ユーザーがコレクションを変更することを許可していません。したがって、IEnumberableインターフェイスで十分な場合は、それが使用されます。それはそれについての推論の適切な方法ですか、何かが欠けていますか?

ありがとう/ Erik

115
Erik Öjebo

最新のソリューション

内部コレクションを変更可能にする必要がない限り、 System.Collections.Immutable パッケージ、フィールドタイプを不変コレクションに変更し、それを直接公開します-Foo自体が不変であることを前提としています。

より直接質問に対処するために回答を更新しました

呼び出し元のコードがコレクションに対してのみ反復する場合、内部コレクションをIEnumerableではなくReadOnlyCollectionとして公開する理由はありますか?

呼び出し元のコードをどれだけ信頼するかによります。このメンバーを呼び出すすべてを完全に制御できる場合guaranteeコードは使用されません:

ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
evil.Add(...);

確かに、コレクションを直接返すだけであれば害はありません。私は一般的にそれよりも少し妄想的になります。

同様に、あなたが言うように:needIEnumerable<T>、それではなぜより強力なものに縛られますか?

元の答え

.NET 3.5を使用している場合は、コピーの作成を避けることができますand Skipの単純な呼び出しを使用して、単純なキャストを回避します。

public IEnumerable<Foo> Foos {
    get { return foos.Skip(0); }
}

(簡単にラッピングするためのオプションは他にもたくさんあります。Select/ WhereのSkipの良いところは、反復ごとに無意味に実行するデリゲートがないことです。)

.NET 3.5を使用していない場合は、非常に簡単なラッパーを作成して同じことを行うことができます。

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
92
Jon Skeet

コレクションを反復処理するだけの場合:

foreach (Foo f in bar.Foos)

IEnumerableを返すだけで十分です。

アイテムへのランダムアクセスが必要な場合:

Foo f = bar.Foos[17];

それをReadOnlyCollectionでラップします。

40

これを行うと、呼び出し元がIEnumerableをICollectionにキャストしてから変更するのを止めることはできません。 ReadOnlyCollectionはこの可能性を取り除きますが、リフレクションを介して基礎となる書き込み可能なコレクションにアクセスすることはまだ可能です。コレクションが小さい場合、この問題を回避する安全で簡単な方法は、代わりにコピーを返すことです。

31
Stu Mackellar

ReadOnlyCollectionの使用は可能な限り避け、実際には通常のListを使用するよりもかなり遅くなります。この例を参照してください。

List<int> intList = new List<int>();
        //Use a ReadOnlyCollection around the List
        System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);

        for (int i = 0; i < 100000000; i++)
        {
            intList.Add(i);
        }
        long result = 0;

        //Use normal foreach on the ReadOnlyCollection
        TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
        foreach (int i in mValue)
            result += i;
        TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());

        //use <list>.ForEach
        lStart = new TimeSpan(System.DateTime.Now.Ticks);
        result = 0;
        intList.ForEach(delegate(int i) { result += i; });
        lEnd = new TimeSpan(System.DateTime.Now.Ticks);
        MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
        MessageBox.Show("Result: " + result.ToString());
3
James Madison

インターフェイスの使用が必要になる場合があります。おそらく、ユニットテスト中にコレクションのモックを作成するためです。アダプタを使用して独自のインターフェイスをReadonlyCollectionに追加する方法については、私の ブログエントリ を参照してください。

0
Nick Taylor