【问题标题】:Forbid public Add and Delete for a List<T>禁止对 List<T> 公开添加和删除
【发布时间】:2010-06-16 12:01:30
【问题描述】:

在我的 C# 项目中,我有一个包含列表的类

public class MyClass
{
  public MyClass parent;
  public List<MyClass> children;
  ...
}

我想阻止该类的用户向子列表添加(和删除)一个元​​素,但他仍然应该能够解析它的元素。我想处理 MyClass 中的添加和删除,提供 AddChild(MyClass item) 和 DeleteChild(MyClass item) 以确保在将项目添加到子列表时,该项目的父项将被正确设置。

除了实现我自己的 IList 之外,知道如何做到这一点吗?

提前致谢, 弗兰克

【问题讨论】:

标签: c# list


【解决方案1】:

如果你把List&lt;T&gt;交给调用者,你就无法控制它;你甚至不能继承List&lt;T&gt;,因为添加/删除不是virtual

然后,我会将数据公开为IEnumerable&lt;MyClass&gt;

private readonly List<MyClass> children = new List<MyClass>();
public void AddChild(MyClass child) {...}
public void RemoveChild(MyClass child) {...}
public IEnumerable<MyClass> Children {
    get {
        foreach(var child in children) yield return child;
    }
}

yield return 阻止他们只是投射它)

调用者仍然可以在.Children 上使用foreach,并且感谢LINQ,他们也可以做所有其他有趣的事情(WhereFirst 等)。

【讨论】:

  • 您也可以使用.AsEnumerable() 扩展方法代替foreach/yield 循环。 msdn.microsoft.com/en-us/library/bb335435.aspx
  • @spoulson - 不;这被实现为“返回源”(查看反射器),所以我仍然可以将其转换回List&lt;T&gt; 并调用Add。要演示,请尝试:var list = new List&lt;string&gt;(); ((IList)list.AsEnumerable()).Add("abc"); Console.WriteLine(list.Count);
  • 这样你会返回一个独立的集合,如果孩子被添加到原始列表中/从原始列表中删除,它就会过时,不是吗?
  • 最好将列表直接返回为IEnumerable&lt;MyClass&gt;,因为这样可以让LINQ 有效地使用它。例如,用户可以使用具有 O(1) 性能的 .Count().ElementAt()。他们可以通过强制转换来改变您的列表这一事实通常并不重要,因为如果您完全信任他们,他们可以通过反射轻松访问您的私有字段。公共合同的意图很明确:我不希望你改变这个枚举的集合,我也不保证如果你尝试会发生什么。
  • @Ozan - 不,通过 yield return 实现的 IEnumerables 通过状态机延迟执行。 foreach...yield 返回方法将产生与底层列表的迭代器面对并发修改时相同的结果。在未主动枚举时修改列表是可以的;应避免在它存在时对其进行修改(即您处于 foreach 循环中)。
【解决方案2】:

除了实现您自己的IList&lt;T&gt;,您还可以返回ReadOnlyCollection&lt;MyClass&gt;

public MyClass
{
    public MyClass Parent;

    private List<MyClass> children;
    public ReadOnlyCollection<MyClass> Children
    {
        get { return children.AsReadOnly(); }
    }
}

【讨论】:

  • +1 是最干净的解决方案 - 尽管只是公开为 IEnumerable 几乎一样好、更快、更简单。
  • +1 来自我。我喜欢这个只是为了代码的简单性。 KISS原则!
  • 我比通过 yield return 枚举更喜欢这个,因为它保留了列表访问的效率(对间接的影响很小)。即使使用AsReadOnly(),我也会将返回类型更改为IEnumerable&lt;MyClass&gt;,因为这为类实现者提供了更大的灵活性来更改将来如何存储/公开子项。如果稍后将它们放在内部字典中,我不必创建包装列表来保留公共接口。
【解决方案3】:

将列表公开为IEnumerable&lt;MyClass&gt;

【讨论】:

    【解决方案4】:

    将列表设为私有,并创建公共方法以从类外部访问其内容。

    public class MyClass
    {
        public MyClass Parent { get; private set; }
    
        private List<MyClass> _children = new List<MyClass>();
        public IEnumerable<MyClass> Children
        {
            get
            {
                // Using an iterator so that client code can't access the underlying list
                foreach(var child in _children)
                {
                    yield return child;
                }
            }
        }
    
        public void AddChild(MyClass child)
        {
            child.Parent = this;
            _children.Add(child);
        }
    
        public void RemoveChild(MyClass child)
        {
            _children.Remove(child);
            child.Parent = null;
        }
    }
    

    顺便说一句,避免声明公共字段,因为您无法控制客户端代码对它们的作用。

    【讨论】:

      【解决方案5】:
      using System.Collections.ObjectModel;
      
      public class MyClass
      {
          private List<MyClass> children;
      
          public ReadOnlyCollection<MyClass> Children
          {
              get
              {
                  return new ReadOnlyCollection<MyClass>(children);
              }
          }
      }
      

      【讨论】:

      • 如果您在 this.children 上添加/删除项目,readOnlyCollection 将获得其项目,因为它在内部存储原始列表...
      【解决方案6】:

      您不需要重新实现 IList,只需将 List 封装在一个类中,并提供您自己的 Add 和 Delete 函数,这些函数将负责添加和设置所需的属性。

      如果您想通过列表进行枚举,您可能必须创建自己的枚举器。

      【讨论】:

        【解决方案7】:

        您可以通过将列表设为私有来将列表封装在类中,并将其作为 ReadOnlyCollection 提供:

        public class MyClass {
        
          public MyClass Parent { get; private set; };
        
          private List<MyClass> _children;
        
          public ReadOnlyCollection<MyClass> Children {
            get { return _children.AsReadOnly(); }
          }
        
          public void AddChild(MyClass item) {
            item.Parent = this;
            _children.Add(item);
          }
        
          public void DeleteChild(MyClass item) {
            item.Parent = null;
            _children.Remove(item);
          }
        
        }
        

        您可以使用私有设置器将Parent 设为属性,这样就无法从外部对其进行修改,但AddChildDeleteChild 方法可以更改它。

        【讨论】:

          【解决方案8】:

          使用List(T).AsReadOnly()

          http://msdn.microsoft.com/en-us/library/e78dcd75.aspx

          返回一个只读的 IList)>) 当前集合的包装器。

          自 .NET 2.0 起可用。

          【讨论】:

            猜你喜欢
            • 2010-12-21
            • 1970-01-01
            • 1970-01-01
            • 2014-04-14
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多