atsukanrockのブログ

Microsoft系技術を中心にぼちぼち更新します

CollectionやReadOnlyCollectionで、リストのプロパティを公開する

はじめに

本エントリでは、.NET 2.0以降でリストのプロパティを公開する*1場合に使用する、以下のクラスについて述べる。

Collection

変更可能なリストとして、よく知られているのはList<T>クラスだろう。なので、変更可能なリストのプロパティを公開したい場合、その型をListクラスとしたくなる。
しかし、これはデザイン面で非推奨とされており、その代わりにCollectionを使うよう推奨されている。その理由は、「Why does DoNotExposeGenericLists recommend that I expose Collection<T> instead of List<T>?」に詳しい。リンク先で述べられている理由は以下の2点である。

  1. Listは速度と内部的な実装の詳細のために設計されているが、Collectionは拡張性のために設計されている
  2. Listが公開しているメンバが多すぎ、それらの多くは多くのシチュエーションに関係がない。対照的に、Collectionが公開しているのは少数である。さらに、Collectionの派生クラスでListのメンバの一部を公開するのも簡単である

これらの理由を見る限り、特にセキュリティ上の問題が生じるわけではなさそうだが、ガイドラインが示されているのだからそれに従うのが良いだろう。
なお、上記の「Why does DoNotExposeGenericLists recommend that I expose Collection instead of List?」ではプロパティの型をCollectionとするサンプルが示されているが、横着せずにその派生クラスとしておいた方がよいだろう(派生クラスとする場合のサンプルも示されている)。なぜなら、Collection型としてしまった時点で機能追加ができなくなるためだ。派生クラスとしておけば、例えばSortメソッドを追加したいといった場合に対応できる。

ReadOnlyCollection

では、変更不可能なリストを公開するにはどうするか。「配列フィールドを返すメソッド - 星一の日記」で述べられているとおり、以下の2パターンがある。

  1. 配列
  2. ReadOnlyCollection

このうち配列を公開するパターンでは、注意が必要だ。なぜなら、配列の要素の書き換えを防ぐ方法がないからだ。上記の「配列フィールドを返すメソッド - 星一の日記」にもあるが、型の内部フィールドである配列を型の外に出す際には、Cloneしなければならない。しかしこれは、配列の要素数に応じたコストがかかる操作だ。手元の環境で試したところ、以下のとおりだった。

要素の型 素数 Clone回数 かかった時間(単位:秒)
DateTime 1000 1000 0.008〜0.022
String 1000 1000 0.008〜0.013
DateTime 10000 10000 2.744〜2.842
String 10000 10000 2.724〜3.379

念のため、Microsoftガイドラインへのリンクを記しておく。

そこで、読み取り専用のリストであるReadOnlyCollectionの出番である。このクラスは、IListインタフェースを実装しているが、明示的なインターフェイスの実装をすることで、一見Add、Clear、Removeなどのメソッドが使用できないかのように見せかけている。明示的なインターフェイスの実装なので、IListにキャストすればそれらのメソッドを呼び出すコードでもコンパイルエラーとならないが、そこまでして呼び出すと実行時エラー(NotSupportedException)となる。実行時エラーとは一見美しくないが、IEnumerable←ICollection←IList*2と続く継承階層があるため、MicrosoftはIReadableListのようなインタフェース*3を設けるわけにいかなかったのだろう。IReadableListは上記の継承階層に割り込むことができない(「IListはIReadableListである」*4のでIReadableList←IListとしたいが、ICollection←IReadableListもIReadableList←ICollectionも成り立たないためこれはできない)ため、例えばインタフェースを引数とするメソッドの設計に悪影響を及ぼしてしまう。
ReadOnlyCollectionはパフォーマンスが悪いという悪い噂(上記「配列フィールドを返すメソッド - 星一の日記」や「ReadOnlyCollection creating huge performance issue」)もあるが、私の環境で試したところ、全くそのようなことはなかった。
変更不可能なリストを公開するには、ReadOnlyCollectionを優先的に利用すべきだろう。なお、Collectionと同じく、利用時には派生クラスを実装しておく方が良いだろう。

まとめ

自前の型でリストのプロパティを公開する場合、System.Collections.ObjectModel名前空間の各クラスを利用すべきだろう。少なくとも私はそうする。
ちなみに本エントリを書きながら気づいたが、この名前空間には.NET 3.0から以下のクラスが追加されたようだ。

リストを変更する各操作が行われると、イベントを発生させる機能を持つクラスのようだ。有用だと思う。

*1:publicまたはprotected。internalを「公開」とするかどうかは人によるかもしれない

*2:矢印は、<基底クラス>←<派生クラス>の関係を表す

*3:読み取り系のプロパティ、メソッドのみを持つイメージ

*4:is-a関係