如何允许迭代私有集合但不修改?

时间:2010-02-19 16:19:25

标签: c# design-patterns iterator

如果我有以下班级成员:

private List<object> obs;

我希望允许遍历此列表作为类'界面的一部分,我该怎么做?

将其公开将无效,因为我不想直接修改列表。

9 个答案:

答案 0 :(得分:13)

您会将其公开为IEnumerable<T>,但不会直接将其公开:

public IEnumerable<object> Objects { get { return obs.Select(o => o); } }

由于您表示您只想要列表的遍历,这就是您所需要的。

有人可能会试图直接将List<object>作为IEnumerable<T>返回,但这是不正确的,因为可以在运行时轻松检查IEnumerable<T>,确定它是List<T> 1}}并将其转换为内容。

但是,使用return obs.Select(o => o);最终会在List<object>上返回迭代器,而不是直接引用List<object>本身。

根据C#语言规范的第7.15.2.5节,有些人可能会认为这符合“退化表达式”。但是,Eric Lippert goes into detail as to why this projection isn't optimized away

此外,人们建议使用AsEnumerable extension method。这是不正确的,因为维护了原始列表的引用标识。从文档的备注部分:

除了将源代码的编译时类型从实现AsEnumerable<TSource>(IEnumerable<TSource>)的类型更改为IEnumerable<T>本身之外,IEnumerable<T>方法无效。

换句话说,它只是将源参数强制转换为IEnumerable<T>,这无助于保护参照完整性,返回原始引用并可以转换回List<T>并使用改变名单。

答案 1 :(得分:11)

您可以使用ReadOnlyCollection或复制List并将其返回(考虑到复制操作的性能损失)。您也可以使用List<T>.AsReadOnly

答案 2 :(得分:3)

这已经说过了,但我没有看到任何答案是否过于清晰。

最简单的方法是简单地返回一个ReadOnlyCollection

private List<object> objs;

public ReadOnlyCollection<object> Objs {
    get {
        return objs.AsReadOnly();
    }
}

这样做的缺点是,如果您想稍后更改您的实现,那么一些调用者可能已经依赖于该集合提供随机访问的事实。因此更安全的定义是公开IEnumerable

public IEnumerable<object> Objs { 
    get {
        return objs.AsReadOnly();
    }
}

请注意,您不必调用AsReadOnly()来编译此代码。但是如果你不这样做,调用者我只是将返回值强制转换回List并修改你的列表。

// Bad caller code
var objs = YourClass.Objs;
var list = objs as List<object>;
list.Add(new object); // They have just modified your list.

此解决方案也存在潜在问题

public IEnumerable<object> Objs { 
    get { 
        return objs.AsEnumerable(); 
    } 
}

所以我肯定会建议您在列表中调用AsReadOnly()并返回该值。

答案 3 :(得分:2)

您可以通过两种方式执行此操作:

  1. 通过将列表转换为Readonly集合:

    new System.Collections.ObjectModel.ReadOnlyCollection<object>(this.obs)
    
  2. 或者通过返回IEnumerable项目:

    this.obs.AsEnumerable()
    

答案 4 :(得分:2)

在您的界面中添加以下方法签名:  public IEnumerable TraverseTheList()

如此I:

public IEnumerable<object> TraverseTheList()
{

    foreach( object item in obj)
    {
      yield return item;
    }


}

允许您执行以下操作:

foreach(object item in Something.TraverseTheList())
{
// do something to the item
}

yield return告诉编译器为你构建一个枚举器。

答案 5 :(得分:1)

公开ReadOnlyCollection<T>

答案 6 :(得分:1)

有关此问题的有趣帖子和对话:http://davybrion.com/blog/2009/10/stop-exposing-collections-already/

答案 7 :(得分:0)

您是否考虑过从System.Collections.ReadOnlyCollectionBase派生一个类?

答案 8 :(得分:0)

只需返回一个 IReadOnlyCollection

private List<object> obs;

IReadOnlyCollection<object> GetObjects()
{
    return obs;
}