呼び出し元のコードがコレクションに対してのみ反復する場合、内部コレクションを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
最新のソリューション
内部コレクションを変更可能にする必要がない限り、 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;
}
}
コレクションを反復処理するだけの場合:
foreach (Foo f in bar.Foos)
IEnumerableを返すだけで十分です。
アイテムへのランダムアクセスが必要な場合:
Foo f = bar.Foos[17];
それをReadOnlyCollectionでラップします。
これを行うと、呼び出し元がIEnumerableをICollectionにキャストしてから変更するのを止めることはできません。 ReadOnlyCollectionはこの可能性を取り除きますが、リフレクションを介して基礎となる書き込み可能なコレクションにアクセスすることはまだ可能です。コレクションが小さい場合、この問題を回避する安全で簡単な方法は、代わりにコピーを返すことです。
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());
インターフェイスの使用が必要になる場合があります。おそらく、ユニットテスト中にコレクションのモックを作成するためです。アダプタを使用して独自のインターフェイスをReadonlyCollectionに追加する方法については、私の ブログエントリ を参照してください。