我知道我不应该在一个属性中暴露List<T>
,但我想知道这样做的正确方法是什么?例如,这样做:
public static class Class1
{
private readonly static List<string> _list;
public static IEnumerable<string> List
{
get
{
return _list;
//return _list.AsEnumerable<string>(); behaves the same
}
}
static Class1()
{
_list = new List<string>();
_list.Add("One");
_list.Add("Two");
_list.Add("Three");
}
}
允许我的来电者简单地转回List<T>
:
private void button1_Click(object sender, EventArgs e)
{
var test = Class1.List as List<string>;
test.Add("Four"); // This really modifies Class1._list, which is bad™
}
所以如果我想要一个真正不可变的List<T>
,我总是要创建一个新的列表吗?例如,这似乎有效(在演员之后测试为null):
public static IEnumerable<string> List
{
get
{
return new ReadOnlyCollection<string>(_list);
}
}
但是我担心是否存在性能开销,因为每次有人试图访问它时都会克隆我的列表?
答案 0 :(得分:8)
将List<T>
作为财产公开实际上并不是万恶之源;特别是如果它允许预期用途,例如foo.Items.Add(...)
。
您可以为AsEnumerable()
编写一个强制替代选项:
public static IEnumerable<T> AsSafeEnumerable<T>(this IEnumerable<T> data) {
foreach(T item in data) yield return item;
}
但目前你最大的问题是线程安全。作为一个静态成员,你可能在这里遇到很大的问题,特别是如果它是像ASP.NET这样的东西。即使ReadOnlyCollection
超过现有列表也会受此影响:
List<int> ints = new List<int> { 1, 2, 3 };
var ro = ints.AsReadOnly();
Console.WriteLine(ro.Count); // 3
ints.Add(4);
Console.WriteLine(ro.Count); // 4
所以简单地用AsReadOnly
包裹不就足以使你的对象具有线程安全性;它只是防止使用者添加数据(但是当你的其他线程添加数据时,他们仍然可以枚举它,除非你同步或复制)。
答案 1 :(得分:4)
是和否。是的,存在性能开销,因为创建了一个新对象。不,您的列表未被克隆,它由ReadOnlyCollection包装。
答案 2 :(得分:3)
如果该类没有其他用途,您可以从列表继承并覆盖add方法并让它抛出异常。
答案 3 :(得分:2)
答案 4 :(得分:2)
您无需担心克隆的开销:使用ReadOnlyCollection包装集合不克隆它。它只是创建一个包装器;如果底层集合发生更改,则只读版本也会更改。
如果您担心反复创建新的包装器,可以将其缓存在单独的实例变量中。
答案 5 :(得分:2)
我之前问了一个类似的问题:
基于此,我建议您在内部使用List<T>
,并将其作为Collection<T>
或IList<T>
返回。或者,如果只需要枚举而不是像这样添加或攻击,IEnumerable<T>
。
关于能否将你归还的内容转移到其他事情上,我只想说不要打扰。如果人们希望以一种非预期的方式使用您的代码,他们将能够以某种方式。我之前也问了一个关于这个的问题,我想说唯一明智的做法就是暴露你想要的东西,如果人们以不同的方式使用它,那么,这就是他们的问题:p一些相关的问题:< / p>
答案 6 :(得分:1)
如果您将列表公开为IEnumerable,我不会担心调用者会回滚到List。您已在类的合同中明确指出,此列表中只允许IEnumerable中定义的操作。所以你已经隐含地声明该列表的实现可能会改变为实现IEnumerable的任何东西。
答案 7 :(得分:0)
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){
...
}