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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/491375/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-04 05:26:22  来源:igfitidea点击:

ReadOnlyCollection or IEnumerable for exposing member collections?

c#.netcollectionsienumerablereadonly-collection

提问by Erik ?jebo

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?

如果调用代码仅迭代集合,是否有任何理由将内部集合公开为 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);

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?

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

Thanks /Erik

谢谢/埃里克

采纳答案by Jon Skeet

More modern solution

更现代的解决方案

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

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

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?

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

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 guaranteethat 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 needIEnumerable<T>, then why tie yourself to anything stronger?

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

Original answer

原答案

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

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

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

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

(还有很多其他选项可以简单地包装 -Skip超过 Select/Where 的好处是没有委托可以为每次迭代毫无意义地执行。)

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

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

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

回答by Stu Mackellar

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.

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

回答by Vojislav Stojkovic

If you only need to iterate through the collection:

如果您只需要遍历集合:

foreach (Foo f in bar.Foos)

then returning IEnumerableis enough.

然后返回IEnumerable就足够了。

If you need random access to items:

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

Foo f = bar.Foos[17];

then wrap it in ReadOnlyCollection.

然后将其包装在ReadOnlyCollection 中

回答by Jon Skeet

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

有时您可能想使用接口,可能是因为您想在单元测试期间模拟集合。请参阅我的博客条目以使用适配器将您自己的接口添加到 ReadonlyCollection。

回答by James Madison

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

我尽可能避免使用 ReadOnlyCollection,它实际上比仅使用普通 List 慢得多。看这个例子:

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