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

Mar*_*zek 7 c# ienumerable list

假设我有IEnumerable<int>使用List<int>字段支持的属性,所以我可以在类中修改集合,但它是以只读方式公开公开的.

public class Foo
{
    private List<int> _bar = new List<int>();

    public IEnumerable<int> Bar
    {
        get { return _bar; }
    }
}
Run Code Online (Sandbox Code Playgroud)

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

var foo = new Foo();
var bar = (List<int>)foo.Bar;
bar.Add(10);
Run Code Online (Sandbox Code Playgroud)

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

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

  1. foreach并且yield return:

    public IEnumerable<int> Bar
    {
        get
        {
            foreach (var item in _bar)
                yield return item;
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    -写作和阅读真的很烦人.

  2. AsReadOnly():

    public IEnumerable<int> Bar
    {
        get { return _bar.AsReadOnly(); }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    +当有人试图修改返回的集合时+将导致异常
    +不创建整个集合的副本.

  3. ToList()

    public IEnumerable<int> Bar
    {
        get { return _bar.ToList(); }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

  4. 自定义包装类.

    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);
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

    用法:

    public IEnumerable<int> Bar
    {
        get
        {
            return _bar.AsMyEnumerable();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

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


有没有更好的方法呢?

Ree*_*sey 13

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

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

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

一般来说,我个人试图避免无理由地复制.这将ToList()作为一般选择消除.使用迭代器(你的第一个选项)并不是那么糟糕,尽管它并没有真正提供许多优势ReadOnlyCollection<T>.