如何从声明为IEnumerable 的方法/属性中安全地返回List ?

假设我使用List字段支持IEnumerable属性,因此我可以在类中修改集合,但它是以只读方式公开公开的。

 public class Foo { private List _bar = new List(); public IEnumerable Bar { get { return _bar; } } } 

但是使用这样的代码,您可以轻松地将从属性中检索到的对象转换回List并对其进行修改:

 var foo = new Foo(); var bar = (List)foo.Bar; bar.Add(10); 

问题是: 什么是最好的(最好的可读性,最容易编写,没有性能损失)避免这种情况的方法?

我可以提出至少4个解决方案,但不是它们是完美的:

  1. foreachyield return

     public IEnumerable Bar { get { foreach (var item in _bar) yield return item; } } 

    写作和阅读真的很烦人。

  2. AsReadOnly()

     public IEnumerable Bar { get { return _bar.AsReadOnly(); } } 

    当有人试图修改返回的集合时, +将导致exception
    +不会创建整个集合的副本。

  3. ToList()

     public IEnumerable Bar { get { return _bar.ToList(); } } 

    +用户仍然可以修改检索到的集合,但它与我们在课堂上修改的集合不同,所以我们不应该在意。
    创建整个集合的副本,当集合很大时可能会导致问题。

  4. 自定义包装类。

     public static class MyExtensions { private class MyEnumerable : IEnumerable { private ICollection _source; public MyEnumerable(ICollection source) { _source = source; } public IEnumerator GetEnumerator() { return _source.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_source).GetEnumerator(); } } public static IEnumerable AsMyEnumerable(this ICollection source) { return new MyEnumerable(source); } } 

    用法:

     public IEnumerable Bar { get { return _bar.AsMyEnumerable(); } } 

    +不需要克隆集合
    当您将其用作LINQ查询源时,某些方法将不使用ICollection.Count ,因为您不公开它。


有没有更好的方法呢?

问题是:什么是最好的(最好的可读性,最容易编写,没有性能损失)避免这种情况的方法?

一般来说,我不会试图避免它。 我的API的消费者应该使用我公开的类型,如果他们不这样做,任何错误都是他们的错,而不是我的。 因此,我并不关心他们是否以这种方式投放数据 – 当我更改内部表示时,他们会获得强制转换exception,这就是他们的问题。

话虽如此,如果存在安全问题,我可能只会使用AsReadOnly 。 这实际上是自我记录的,并没有真正的缺点(除了包装器的小分配,因为没有数据的副本,你确实得到了有意义的修改例外等)。 与制作自己的自定义包装器相比,没有真正的缺点,自定义包装器意味着需要更多的代码来测试和维护。

一般来说,我个人试图避免无理由地复制。 这将通常消除ToList()作为选项。 使用迭代器(你的第一个选项)并没有那么糟糕,尽管它并没有提供比ReadOnlyCollection更多的优点。

一般来说,我在不打扰营地。 如果我给你一个IEnumerable,然后你把它投到别的东西,你就是那个违反合同的人,如果有什么东西坏了,那不是我的错。 如果我发现自己处于一个我真正需要保护客户端免受破坏状态的位置,我会创建一个扩展方法,如:

 static IEnumerable AsSafeEnumerable(this IEnumerable obj) { foreach (var item in obj) { yield return item; } } 

再也不用担心了。

我认为你已经充分概述了许多解决方案,但我没有看到你考虑的一件事是分配开销(但性能被列为一个问题)。 分配很重要,并且属性在同一列表的每次调用中分配新对象都是浪费。 相反,我更喜欢一个生命周期等于原始列表的对象,可以在没有额外开销的情况下返回。 基本上是#4的修改版本

我同意Reed Copsey的post,如果消费者希望以破坏类数据的方式投射和管理数据,那么就让他们这样做。

你可以花很多时间试图“阻止”一个特定的动作发生只是为了让一些聪明的反思破坏你所有的难以理解的话。

现在回答这个问题,如果你不得不这样做。 第一个选项似乎与我最相关,但是在调用属性时我不会通过列表进行枚举。 我只会返回内部集合,如。

 public IEnumerable Bar { get { return _bar; } }