【问题标题】:How to allow iteration over a private collection but not modification?如何允许对私有集合进行迭代但不允许修改?
【发布时间】:2011-01-18 20:38:52
【问题描述】:

如果我有以下班级成员:

private List<object> obs;

并且我想允许将此列表作为类接口的一部分进行遍历,我该怎么做?

将其公开是行不通的,因为我不想让列表被直接修改。

【问题讨论】:

    标签: c# design-patterns iterator


    【解决方案1】:

    只需返回 IReadOnlyCollection

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

    【讨论】:

      【解决方案2】:

      您可以通过两种方式做到这一点:

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

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

        this.obs.AsEnumerable()
        

      【讨论】:

        【解决方案3】:

        您可以将其公开为IEnumerable&lt;T&gt;,但不只是直接返回:

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

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

        人们可能想直接将List&lt;object&gt; 作为IEnumerable&lt;T&gt; 返回,但这是不正确的,因为人们可以在运行时轻松检查IEnumerable&lt;T&gt;,确定它是List&lt;T&gt; 并将其转换为这样的并改变内容。

        但是,通过使用 return obs.Select(o =&gt; o);,您最终会在 List&lt;object&gt; 上返回一个迭代器,而不是对 List&lt;object&gt; 本身的直接引用。

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

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

        AsEnumerable&lt;TSource&gt;(IEnumerable&lt;TSource&gt;) 方法除了将源的编译时类型从实现IEnumerable&lt;T&gt; 的类型更改为IEnumerable&lt;T&gt; 本身之外没有任何作用。

        换句话说,它所做的只是将源参数转换为IEnumerable&lt;T&gt;,这无助于保护引用完整性,原始引用被返回并且可以被转换回List&lt;T&gt;并用于改变列表.

        【讨论】:

        • 请注意,如果您的代码中出现了对 Select 的 显式 调用,我们将永远不会优化它。在将查询表达式转换为方法调用时,我们将优化对 Select 的隐式调用。如果您说“from x in y where b select x”,则没有 Select 调用,只有 Where。但是如果你说“from x in y select x”那么我们不只是生成“y”,我们会在它上面生成一个 select 调用以确保结果的不变性。
        【解决方案4】:

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

        实现如下:

        public IEnumerable<object> TraverseTheList()
        {
        
            foreach( object item in obj)
            {
              yield return item;
            }
        
        
        }
        

        这将允许您执行以下操作:

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

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

        【讨论】:

          【解决方案5】:

          这已经说过了,但我认为没有任何答案是非常清楚的。

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

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

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

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

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

          // 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(),并返回该值。

          【讨论】:

          • 简单+安全的完美结合。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

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

            【讨论】:

              【解决方案8】:

              公开ReadOnlyCollection&lt;T&gt;

              【讨论】:

              • 让提问者四处打听并不是问题。 Google 上的第一个结果清楚地表明了如何公开ReadOnlyCollection&lt;T&gt;
              • 我只承认@Mitch Wheat 应该包含指向 MSDN 页面的链接。再多的话,我们只会复制 MSDN 页面上的内容。
              【解决方案9】:

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

              【讨论】:

                猜你喜欢
                • 2014-01-31
                • 2015-06-20
                • 2021-09-30
                • 2012-03-13
                • 2015-08-15
                • 1970-01-01
                • 2019-06-13
                • 2018-11-16
                • 2017-08-30
                相关资源
                最近更新 更多