在处理只读List 成员时,我应该如何使用属性

当我想在我的课外制作一个只读的值类型时,我这样做:

public class myClassInt { private int m_i; public int i { get { return m_i; } } public myClassInt(int i) { m_i = i; } } 

我可以做些什么来使List类型只读(因此它们不能在我的类之外添加/删除元素)? 现在我只是宣布公开:

 public class myClassList { public List li; public myClassList() { li = new List(); li.Add(1); li.Add(2); li.Add(3); } } 

您可以将其公开为AsReadOnly 。 也就是说,返回一个只读的IList包装器。 例如 …

 public ReadOnlyCollection List { get { return _lst.AsReadOnly(); } } 

只返回IEnumerable是不够的。 例如 …

 void Main() { var el = new ExposeList(); var lst = el.ListEnumerator; var oops = (IList)lst; oops.Add( 4 ); // mutates list var rol = el.ReadOnly; var oops2 = (IList)rol; oops2.Add( 5 ); // raises exception } class ExposeList { private List _lst = new List() { 1, 2, 3 }; public IEnumerable ListEnumerator { get { return _lst; } } public ReadOnlyCollection ReadOnly { get { return _lst.AsReadOnly(); } } } 

史蒂夫的回答也有一个聪明的方法来避免演员。

试图隐藏信息到这种程度是有限的。 该属性的类型应该告诉用户他们可以使用它做什么。 如果用户决定滥用您的API,他们会找到一种方法。 阻止它们进行铸造并不会阻止它们:

 public static class Circumventions { public static IList AsWritable(this IEnumerable source) { return source.GetType() .GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) .Select(f => f.GetValue(source)) .OfType>() .First(); } } 

通过这种方法,我们可以绕过迄今为止在这个问题上给出的三个答案:

 List a = new List {1, 2, 3, 4, 5}; IList b = a.AsReadOnly(); // block modification... IList c = b.AsWritable(); // ... but unblock it again c.Add(6); Debug.Assert(a.Count == 6); // we've modified the original IEnumerable d = a.Select(x => x); // okay, try this... IList 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 AsReallyReadOnly(this IEnumerable source) { foreach (T t in source) yield return t; } } IEnumerable f = a.AsReallyReadOnly(); // really? IList 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 : IEnumerable { int Count { get; } T this[int index] { get; } } 

如果(更常见)它只需要按顺序枚举,只需返回IEnumerable

 public class MyClassList { private List li = new List { 1, 2, 3 }; public IEnumerable MyList { get { return li; } } } 

更新自从我写了这个答案后,C#4.0问世了,所以上面的IReadOnlyList接口可以利用协方差:

 public interface IReadOnlyList 

现在.NET 4.5已经到了,它已……猜猜……

IReadOnlyList接口

因此,如果您想创建一个具有包含只读列表的属性的自我记录API,答案就在框架中。

JP关于返回IEnumerable 的答案是正确的(您可以向下转换为列表),但这是一种阻止向下转换的技术。

 class ExposeList { private List _lst = new List() { 1, 2, 3 }; public IEnumerable ListEnumerator { get { return _lst.Select(x => x); } // Identity transformation. } public ReadOnlyCollection ReadOnly { get { return _lst.AsReadOnly(); } } } 

枚举期间的标识转换有效地创建了一个编译器生成的迭代器 – 一种与_lst无关的新类型。

Eric Lippert在他的博客上发表了一系列关于Immutability In C#的文章。

该系列的第一篇文章可以在这里找到。

您也可以找到有用的Jon Skeet对类似问题的回答 。

 public List li; 

不要声明公共字段,它通常被认为是不好的做法…而是将其包装在属性中。

您可以将集合公开为ReadOnlyCollection:

 private List li; public ReadOnlyCollection List { get { return li.AsReadOnly(); } } 
 public class MyClassList { private List _lst = new List() { 1, 2, 3 }; public IEnumerable ListEnumerator { get { return _lst.AsReadOnly(); } } } 

检查一下

  MyClassList myClassList = new MyClassList(); var lst= (IList)myClassList.ListEnumerator ; lst.Add(4); //At this point ypu will get exception Collection is read-only. 
 public static IEnumerable AsReallyReadOnly(this IEnumerable source) { foreach (T t in source) yield return t; } 

如果我加入Earwicker的例子

 ... IEnumerable f = a.AsReallyReadOnly(); IList g = f.AsWritable(); // finally can't get around it g.Add(8); Debug.Assert(a.Count == 78); 

我得到InvalidOperationException: Sequence contains no matching element