正确公开List <t>?</t>

时间:2009-08-25 07:28:17

标签: c# .net

我知道我不应该在一个属性中暴露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);
        }
    }

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

8 个答案:

答案 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){
  ...
}