假设我有 IEnumerable<int>
以 List<int>
为后盾的属性(property)字段,因此我可以在类中修改集合,但它公开显示为只读。
public class Foo
{
private List<int> _bar = new List<int>();
public IEnumerable<int> Bar
{
get { return _bar; }
}
}
但是使用这样的代码,您可以轻松地将从属性中检索到的对象转换回 List<int>
并修改它:
var foo = new Foo();
var bar = (List<int>)foo.Bar;
bar.Add(10);
问题是:什么是最好的(最好的可读性、最容易编写的、没有性能损失的)方法来避免这种情况?
我可以想出至少 4 个解决方案,但没有一个是完美的:
foreach
和yield return
:public IEnumerable<int> Bar { get { foreach (var item in _bar) yield return item; } }
- 写和读真的很烦人。
AsReadOnly()
:public IEnumerable<int> Bar { get { return _bar.AsReadOnly(); } }
+ 当有人试图修改返回的集合时会导致异常
+ 不会创建整个集合的副本。ToList()
public IEnumerable<int> Bar { get { return _bar.ToList(); } }
+ 用户仍然可以修改检索到的集合,但它不是我们在类中修改的同一个集合,所以我们不应该在意。
- 创建整个集合的副本,当集合很大时可能会导致问题。自定义包装器类。
public static class MyExtensions { private class MyEnumerable<T> : IEnumerable<T> { private ICollection<T> _source; public MyEnumerable(ICollection<T> source) { _source = source; } public IEnumerator<T> GetEnumerator() { return _source.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_source).GetEnumerator(); } } public static IEnumerable<T> AsMyEnumerable<T>(this ICollection<T> source) { return new MyEnumerable<T>(source); } }
用法:
public IEnumerable<int> Bar { get { return _bar.AsMyEnumerable(); } }
+不需要克隆集合
- 当您将它用作 LINQ 查询源时,某些方法不会使用ICollection.Count
,因为您不公开它。
有更好的方法吗?
最佳答案
Question is: what is the best (best readable, easiest to write, without performance loss) way to avoid that?
一般来说,我不会试图回避它。我的 API 的使用者应该使用我公开的类型,如果他们不这样做,那么由此产生的任何错误都是他们的错,而不是我的错。因此,我真的不在乎他们是否以这种方式转换数据 - 当我更改我的内部表示时,他们得到转换异常,那是他们的问题。
也就是说,如果存在安全问题,我可能只使用 AsReadOnly
.这是有效的 self 记录,并没有真正的缺点(除了包装器的小分配,因为没有数据副本,你确实会在修改时得到有意义的异常等)。与制作您自己的自定义包装器相比,这并没有真正的缺点,并且自定义包装器意味着需要测试和维护更多代码。
一般来说,我个人尽量避免无故复制。这将消除 ToList()
作为一般的选择。使用迭代器(您的第一个选项)并没有那么糟糕,尽管它并没有真正提供比 ReadOnlyCollection<T>
很多优势。 .
关于c# - 如何从声明为 IEnumerable<T> 的方法/属性安全地返回 List<T>?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20984980/