代码之家  ›  专栏  ›  技术社区  ›  Erik Öjebo

用于公开成员集合的ReadOnlyCollection或IEnumerable?

  •  108
  • Erik Öjebo  · 技术社区  · 16 年前

    如果调用代码只在集合上迭代,是否有任何理由将内部集合公开为ReadOnlyCollection而不是IEnumerable?

    class Bar
    {
        private ICollection<Foo> foos;
    
        // Which one is to be preferred?
        public IEnumerable<Foo> Foos { ... }
        public ReadOnlyCollection<Foo> Foos { ... }
    }
    
    
    // Calling code:
    
    foreach (var f in bar.Foos)
        DoSomething(f);
    

    正如我看到的,IEnumerable是readOnlyCollection接口的子集,它不允许用户修改集合。因此,如果IEnumerable接口足够,那么这就是要使用的接口。这是一种正确的推理方式还是我遗漏了什么?

    谢谢/埃里克

    5 回复  |  直到 7 年前
        1
  •  87
  •   Jon Skeet    7 年前

    更现代的解决方案

    除非需要内部集合是可变的,否则可以使用 System.Collections.Immutable 打包,将字段类型更改为不可变的集合,然后直接公开该集合-假定 Foo 当然,它本身是不变的。

    更新答案,更直接地解决问题

    如果调用代码只在集合上迭代,是否有任何理由将内部集合公开为ReadOnlyCollection而不是IEnumerable?

    这取决于您对调用代码的信任程度。如果你完全控制了所有可以称之为会员的事情, 保证 任何代码都不会使用:

    ICollection<Foo> evil = (ICollection<Foo>) bar.Foos;
    evil.Add(...);
    

    当然,如果你直接归还收藏品,不会造成任何伤害。不过,我总想比这更偏执一点。

    同样地,正如你所说:如果你只是 需要 IEnumerable<T> 那为什么要把自己绑在更结实的东西上呢?

    原始答案

    如果您使用.NET 3.5,则可以避免制作副本 通过使用简单调用跳过来避免简单强制转换:

    public IEnumerable<Foo> Foos {
        get { return foos.Skip(0); }
    }
    

    (有很多其他的选择来包装琐碎的东西-关于 Skip over select/where is that no delegate to execute pointless for each迭代。)

    如果不使用.NET 3.5,则可以编写一个非常简单的包装器来执行相同的操作:

    public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
    {
        foreach (T element in source)
        {
            yield return element;
        }
    }
    
        2
  •  39
  •   Vojislav Stojkovic    16 年前

    如果只需要遍历集合:

    foreach (Foo f in bar.Foos)
    

    然后回来 可枚举的 就够了。

    如果需要随机访问项目:

    Foo f = bar.Foos[17];
    

    然后把它包起来 读数集合 .

        3
  •  27
  •   Stu Mackellar    16 年前

    如果这样做,则没有什么可以阻止调用方将IEnumerable强制转换回ICollection,然后对其进行修改。readOnlyCollection消除了这种可能性,尽管仍然可以通过反射访问底层的可写集合。如果集合很小,那么解决此问题的一个安全而简单的方法是返回一个副本。

        4
  •  3
  •   James Madison    14 年前

    我尽量避免使用readOnlyCollection,它实际上比使用普通列表慢得多。 请参见此示例:

    List<int> intList = new List<int>();
            //Use a ReadOnlyCollection around the List
            System.Collections.ObjectModel.ReadOnlyCollection<int> mValue = new System.Collections.ObjectModel.ReadOnlyCollection<int>(intList);
    
            for (int i = 0; i < 100000000; i++)
            {
                intList.Add(i);
            }
            long result = 0;
    
            //Use normal foreach on the ReadOnlyCollection
            TimeSpan lStart = new TimeSpan(System.DateTime.Now.Ticks);
            foreach (int i in mValue)
                result += i;
            TimeSpan lEnd = new TimeSpan(System.DateTime.Now.Ticks);
            MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
            MessageBox.Show("Result: " + result.ToString());
    
            //use <list>.ForEach
            lStart = new TimeSpan(System.DateTime.Now.Ticks);
            result = 0;
            intList.ForEach(delegate(int i) { result += i; });
            lEnd = new TimeSpan(System.DateTime.Now.Ticks);
            MessageBox.Show("Speed(ms): " + (lEnd.TotalMilliseconds - lStart.TotalMilliseconds).ToString());
            MessageBox.Show("Result: " + result.ToString());
    
        5
  •  0
  •   Nick Taylor    16 年前

    有时您可能希望使用接口,可能是因为您希望在单元测试期间模拟集合。请看我的 blog entry 用于使用适配器将自己的接口添加到readOnlyCollection。