正确公开List ?

我知道我不应该在属性中暴露List ,但我想知道正确的方法是什么? 例如,这样做:

 public static class Class1 { private readonly static List _list; public static IEnumerable List { get { return _list; //return _list.AsEnumerable(); behaves the same } } static Class1() { _list = new List(); _list.Add("One"); _list.Add("Two"); _list.Add("Three"); } } 

允许我的调用者简单地转换回List

  private void button1_Click(object sender, EventArgs e) { var test = Class1.List as List; test.Add("Four"); // This really modifies Class1._list, which is bad™ } 

所以如果我想要一个真正不可变的List ,我总是要创建一个新列表吗? 例如,这似乎有效(在演员之后测试为null):

  public static IEnumerable List { get { return new ReadOnlyCollection(_list); } } 

但是我担心是否存在性能开销,因为每次有人试图访问它时我的列表都被克隆了?

使用AsReadOnly() – 有关详细信息,请参阅MSDN

List公开为财产实际上并不是万恶之源; 特别是如果它允许预期的用法,如foo.Items.Add(...)

您可以编写AsEnumerable()替代方法:

 public static IEnumerable AsSafeEnumerable(this IEnumerable data) { foreach(T item in data) yield return item; } 

但目前你最大的问题是线程安全。 作为一个静态成员,你可能会遇到很大的问题,特别是如果它是像ASP.NET这样的东西。 即使对现有列表的ReadOnlyCollection也会受此影响:

  List ints = new List { 1, 2, 3 }; var ro = ints.AsReadOnly(); Console.WriteLine(ro.Count); // 3 ints.Add(4); Console.WriteLine(ro.Count); // 4 

因此,简单地使用AsReadOnly包装并不足以使您的对象具有线程安全性; 它只是防止消费者添加数据(但是当你的其他线程添加数据时,他们仍然可以枚举它,除非你同步或复制)。

是和否。是的,存在性能开销,因为创建了一个新对象。 不,您的列表未被克隆,它被ReadOnlyCollection包装。

如果该类没有其他目的,您可以从列表inheritance并覆盖add方法并让它抛出exception。

您不必担心克隆的开销:使用ReadOnlyCollection包装集合不会克隆它。 它只是创建一个包装器; 如果底层集合发生更改,则只读版本也会更改。

如果您担心一遍又一遍地创建新的包装器,则可以将其缓存在单独的实例变量中。

我先前问了一个类似的问题:

  • 列表和集合之间的区别(CA1002,不公开通用列表)
  • 为什么DoNotExposeGenericLists建议我公开Collection而不是List?

基于此,我建议您在内部使用List ,并将其作为CollectionIList 。 或者如果只需要枚举而不是像这样添加或者antyhing, IEnumerable

关于能够将你返回的东西投射到其他东西的问题,我只想说不要打扰。 如果人们希望以一种非预期的方式使用您的代码,他们将能够以某种方式。 我之前也问了一个关于这个的问题,我想说唯一明智的做法就是暴露你想要的东西,如果人们以不同的方式使用它,那么,这就是他们的问题:p一些相关的问题:

  • 在处理List成员时我应该如何使用属性 (特别是这个答案 )
  • 封装例如集合

如果您将列表公开为IEnumerable,我不会担心调用者会回滚到List。 您已在类的合同中明确指出,此列表中只允许IEnumerable中定义的操作。 所以你已经隐含地声明该列表的实现可能会改变为实现IEnumerable的任何东西。

当您的枚举处于中途并且集合被修改时,AsEnumerable和ReadOnlyCollection会出现问题。 这些东西不是线程安全的。 将它们作为数组返回并在调用时缓存它们可能是更好的选择。

例如,

 public static String[] List{ get{ return _List.ToArray(); } } //While using ... String[] values = Class1.List; foreach(string v in values){ ... } // instead of calling foreach(string v in Class1.List) // again and again, values in this context will not be // duplicated, however values are cached instance so // immediate changes will not be available, but its // thread safe foreach(string v in values){ ... }