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

发布于 2024-07-12 12:52:44 字数 483 浏览 9 评论 0原文

如果调用代码仅迭代集合,是否有任何理由将内部集合公开为 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 接口的子集,它不允许用户修改集合。 因此,如果 IEnumberable 接口足够了,那么就可以使用它。 这是推理的正确方式还是我错过了什么?

Is there any reason to expose an internal collection as a ReadOnlyCollection rather than an IEnumerable if the calling code only iterates over the collection?

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);

As I see it IEnumerable is a subset of the interface of ReadOnlyCollection and it does not allow the user to modify the collection. So if the IEnumberable interface is enough then that is the one to use. Is that a proper way of reasoning about it or am I missing something?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

找个人就嫁了吧 2024-07-19 12:52:44

更现代的解决方案

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

更新了答案以更直接地解决问题

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

这取决于您对调用代码的信任程度。 如果您完全控制将调用此成员的所有内容,并且您保证没有任何代码会使用:

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

那么当然,如果您直接返回集合,则不会造成任何损害。 不过,我通常会尝试变得更加偏执一点。

同样,正如您所说:如果您只需要 IEnumerable,那么为什么要把自己绑在更强大的东西上呢?

原始答案

如果您使用的是.NET 3.5,您可以避免制作副本通过使用对 Skip 的简单调用来避免简单转换:(

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

还有很多其他方法简单包装的选项 - Skip over Select/Where 的好处是每次迭代都没有毫无意义地执行的委托。)

如果您不使用 .NET 3.5,您可以编写一个非常简单的包装器做同样的事情:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}

More modern solution

Unless you need the internal collection to be mutable, you could use the System.Collections.Immutable package, change your field type to be an immutable collection, and then expose that directly - assuming Foo itself is immutable, of course.

Updated answer to address the question more directly

Is there any reason to expose an internal collection as a ReadOnlyCollection rather than an IEnumerable if the calling code only iterates over the collection?

It depends on how much you trust the calling code. If you're in complete control over everything that will ever call this member and you guarantee that no code will ever use:

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

then sure, no harm will be done if you just return the collection directly. I generally try to be a bit more paranoid than that though.

Likewise, as you say: if you only need IEnumerable<T>, then why tie yourself to anything stronger?

Original answer

If you're using .NET 3.5, you can avoid making a copy and avoid the simple cast by using a simple call to Skip:

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

(There are plenty of other options for wrapping trivially - the nice thing about Skip over Select/Where is that there's no delegate to execute pointlessly for each iteration.)

If you're not using .NET 3.5 you can write a very simple wrapper to do the same thing:

public static IEnumerable<T> Wrapper<T>(IEnumerable<T> source)
{
    foreach (T element in source)
    {
        yield return element;
    }
}
寻找我们的幸福 2024-07-19 12:52:44

如果您只需要迭代集合:

foreach (Foo f in bar.Foos)

那么返回 IEnumerable 就足够了。

如果您需要随机访问项目:

Foo f = bar.Foos[17];

则将其包装在 ReadOnlyCollection 中。

If you only need to iterate through the collection:

foreach (Foo f in bar.Foos)

then returning IEnumerable is enough.

If you need random access to items:

Foo f = bar.Foos[17];

then wrap it in ReadOnlyCollection.

浪荡不羁 2024-07-19 12:52:44

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

If you do this then there's nothing stopping your callers casting the IEnumerable back to ICollection and then modifying it. ReadOnlyCollection removes this possibility, although it's still possible to access the underlying writable collection via reflection. If the collection is small then a safe and easy way to get around this problem is to return a copy instead.

风筝在阴天搁浅。 2024-07-19 12:52:44

我尽可能避免使用 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());

I avoid using ReadOnlyCollection as much as possible, it is actually considerably slower than just using a normal List.
See this example:

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());
反差帅 2024-07-19 12:52:44

有时您可能想要使用接口,也许是因为您想在单元测试期间模拟集合。 请参阅我的博客文章,了解如何使用以下命令将您自己的界面添加到 ReadonlyCollection一个适配器。

Sometimes you may want to use an interface, perhaps because you want to mock the collection during unit testing. Please see my blog entry for adding your own interface to ReadonlyCollection by using an adapter.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文