このような範囲で情報を隠そうとすることには、限られた価値しかありません。プロパティのタイプは、ユーザーがそれを使って何ができるかをユーザーに伝える必要があります。ユーザーがAPIを悪用したいと判断した場合、その方法が見つかります。それらのキャストをブロックしても、それらは停止しません。
public static class Circumventions
{
public static IList<T> AsWritable<T>(this IEnumerable<T> source)
{
return source.GetType()
.GetFields(BindingFlags.Public |
BindingFlags.NonPublic |
BindingFlags.Instance)
.Select(f => f.GetValue(source))
.OfType<IList<T>>()
.First();
}
}
その1つの方法で、これまでにこの質問で与えられた3つの答えを回避できます。
List<int> a = new List<int> {1, 2, 3, 4, 5};
IList<int> b = a.AsReadOnly(); // block modification...
IList<int> c = b.AsWritable(); // ... but unblock it again
c.Add(6);
Debug.Assert(a.Count == 6); // we've modified the original
IEnumerable<int> d = a.Select(x => x); // okay, try this...
IList<int> e = d.AsWritable(); // no, can still get round it
e.Add(7);
Debug.Assert(a.Count == 7); // modified original again
また:
public static class AlexeyR
{
public static IEnumerable<T> AsReallyReadOnly<T>(this IEnumerable<T> source)
{
foreach (T t in source) yield return t;
}
}
IEnumerable<int> f = a.AsReallyReadOnly(); // really?
IList<int> g = f.AsWritable(); // apparently not!
g.Add(8);
Debug.Assert(a.Count == 8); // modified original again
繰り返しになりますが...この種の「軍拡競争」は好きなだけ続けることができます!
これを停止する唯一の方法は、ソースリストとのリンクを完全に解除することです。つまり、元のリストの完全なコピーを作成する必要があります。これは、BCLが配列を返すときに行うことです。これの欠点は、ユーザーの00.1%のハッカリーについて心配しているため、一部のデータへの読み取り専用アクセスを希望するたびに、ユーザーの99.9%に潜在的に大きなコストを課していることです。
または、静的型システムを回避するAPIの使用をサポートすることを拒否することもできます。
プロパティがランダムアクセスで読み取り専用リストを返すようにしたい場合は、以下を実装するものを返します。
public interface IReadOnlyList<T> : IEnumerable<T>
{
int Count { get; }
T this[int index] { get; }
}
(はるかに一般的ですが)順番に列挙可能である必要がある場合は、次を返しIEnumerable
ます。
public class MyClassList
{
private List<int> li = new List<int> { 1, 2, 3 };
public IEnumerable<int> MyList
{
get { return li; }
}
}
更新この回答を書いたので、C#4.0が出てきたので、上記のIReadOnlyList
インターフェイスは共分散を利用できます。
public interface IReadOnlyList<out T>
そして今、.NET 4.5が到着しました、そしてそれは...何を推測します...
IReadOnlyListインターフェイス
したがって、読み取り専用リストを保持するプロパティを使用して自己文書化APIを作成する場合、答えはフレームワークにあります。