コレクションを読み取り専用として返す
-
09-06-2019 - |
質問
マルチスレッド環境に情報のコレクションを保持するオブジェクトがあります。例:
public IList<string> Data
{
get
{
return data;
}
}
私は現在持っています return data;
に包まれた ReaderWriterLockSlim
コレクションを共有違反から保護します。ただし、念のため、コレクションを読み取り専用として返し、呼び出し元のコードがコレクションに変更を加えることができず、既存のものの表示のみを行えるようにしたいと考えています。そもそもそんなことは可能なのでしょうか?
解決
間違いを犯さないように呼び出し元のコードを取得し、読み取りのみを行う必要があるときにコレクションを変更することだけが目的の場合、必要なのは、Add、Remove などをサポートしていないインターフェイスを返すことだけです。なぜ戻らないのか IEnumerable<string>
?コードを呼び出すにはキャストする必要がありますが、アクセスしているプロパティの内部を知らずにキャストすることはほとんどありません。
ただし、呼び出し元のコードが他のスレッドからの更新を監視できないようにすることが目的の場合は、必要に応じて深いコピーまたは浅いコピーを実行するために、前述のソリューションにフォールバックする必要があります。
他のヒント
基になるデータがリストとして保存されている場合は、使用できます リスト(T).AsReadOnly 方法。
データを列挙できる場合は、次を使用できます。 Enumerable.ToList メソッドを使用して、コレクションを List にキャストし、それに対して AsReadOnly を呼び出します。
私はあなたの受け入れられた回答に投票し、それに同意します。ただし、考慮すべき点があるのでしょうか?
コレクションを直接返さないでください。コレクションの目的を反映した正確な名前のビジネス ロジック クラスを作成します。
この主な利点は、コレクションにコードを追加できないため、オブジェクト モデルにネイティブの「コレクション」がある場合は常に、それにアクセスするためにプロジェクト全体に非 OO サポート コードが分散されることになります。
たとえば、コレクションが請求書の場合、コード内に未払いの請求書を反復処理する箇所がおそらく 3 か所 4 か所あるでしょう。getUnpaidInvoices メソッドを使用することもできます。ただし、「payUnpaidInvoices(payer, account);」のようなメソッドを考え始めると、真の力が発揮されます。
オブジェクト モデルを作成する代わりにコレクションを渡すと、クラス全体のリファクタリングが思い浮かぶことはありません。
これにより、問題が特に優れたものになることにも注意してください。人々がコレクションを変更したくない場合は、コンテナにミューテーターを含める必要はありません。後で、実際に変更する必要があるのは 1 つのケースだけであると判断した場合は、変更を行うための安全なメカニズムを作成できます。
ネイティブ コレクションを渡すときに、その問題をどのように解決しますか?
また、ネイティブ コレクションを追加のデータで強化することはできません。次回、(Collection、Extra) を 1 つまたは 2 つ以上のメソッドに渡すと、これに気づくでしょう。これは、「Extra」がコレクションを含むオブジェクトに属していることを示します。
ここで概念を混乱させていると思います。
の ReadOnlyCollection
既存のコレクションの読み取り専用ラッパーを提供します。これにより、呼び出し元 (クラス B) がコレクションを変更できないことを認識した上で、ユーザー (クラス A) がコレクションへの参照を安全に渡すことができます (つまり、クラス A)。できない 追加 または 取り除く コレクションの要素。)
スレッドの安全性はまったく保証されません。
- あなた (クラス A) が、基礎となるコレクションを
ReadOnlyCollection
クラス B はこれらの変更を認識し、イテレータが無効になるなどします。また、一般的に、コレクションに関する通常の同時実行の問題には寛容です。 - さらに、コレクション内の要素が変更可能な場合は、両方の要素が変更可能です (クラス A)。 そして 呼び出し元 (クラス B) は、コレクション内のオブジェクトの変更可能な状態を変更できます。
実装はニーズによって異なります。- 呼び出し元 (クラス B) がコレクションにさらに変更を加えることを気にしない場合は、コレクションを複製して配布し、気にするのをやめることができます。- コレクションに加えられた変更を呼び出し元 (クラス B) に確認させる必要があり、これをスレッドセーフにしたい場合は、さらに多くの問題が発生します。1 つの可能性は、ReadOnlyCollection のスレッドセーフなバリアントを独自に実装して、ロックされたアクセスを許可することです。ただし、これは簡単ではなく、IEnumerable をサポートしたい場合には非パフォーマンスになります。 まだ コレクション内の変更可能な要素から保護することはできません。
注意すべき点は、 アクの答えは、リストを読み取り専用として保護するだけです。リスト内の要素は引き続き書き込み可能です。非アトミック要素を読み取り専用リストに入れる前にクローンを作成せずに保護する方法があるかどうかはわかりません。
使用したいのは、 収率 キーワード。IEnumerable リストをループし、結果を yield で返します。これにより、コンシューマはコレクションを変更せずに for each を使用できるようになります。
次のようになります。
List<string> _Data;
public IEnumerable<string> Data
{
get
{
foreach(string item in _Data)
{
return yield item;
}
}
}
代わりにコレクションのコピーを使用できます。
public IList<string> Data {
get {
return new List<T>(data);
}}
そうすれば更新されても問題ありません。