试图将信息隐藏到这种程度的价值有限。属性的类型应该告诉用户他们可以用它做什么。如果用户决定要滥用您的 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();
}
}
使用这种方法,我们可以规避到目前为止在这个问题上给出的三个答案:
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 在返回数组时所做的事情。这样做的缺点是,您每次想要对某些数据进行只读访问时,都会对 99.9% 的用户施加潜在的巨大成本,因为您担心 00.1% 的用户会受到黑客攻击。
或者你可以拒绝支持绕过静态类型系统的 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; }
}
}
UPDATE由于我写了这个答案,C# 4.0 出来了,所以上面的IReadOnlyList
接口可以利用协方差:
public interface IReadOnlyList<out T>
现在 .NET 4.5 已经到来,它已经......猜猜是什么......
IReadOnlyList 接口
因此,如果您想创建一个具有包含只读列表的属性的自记录 API,答案就在框架中。