【问题标题】:Best way to expose ReadOnlyCollection of ReadOnlyCollection while still being able to modify it inside class公开 ReadOnlyCollection 的 ReadOnlyCollection 的最佳方法,同时仍然能够在类内对其进行修改
【发布时间】:2014-07-05 22:20:28
【问题描述】:

我不知道这是否可能,但基本上,我想公开ReadOnlyCollection<ReadOnlyCollection<MyType>> 类型的属性,同时仍然能够修改暴露该属性的类中的底层集合。此外,我希望我的类的用户能够保存对返回集合的引用,以便它在我在暴露属性的类内部更新它时更新。

例如,如果这是一个单一的集合,我可以这样做:

  public class MyClass {
    private List<MyType> _List;
    public ReadOnlyCollection<MyType> RList {
      get {
        return _List.AsReadOnly();
      }
    }
  }

这仍然允许通过_List.Add() 将项目添加到我的班级内的列表中。此外,如果此类的客户要这样做,请说:

var collectionRef = myClassInstance.RList;

然后collectionRef 也会随着我从MyClass 内部向列表中添加元素而改变。

如果我想要一个列表,就会出现问题:

  private List<List<MyType>> _List;
    public ReadOnlyCollection<ReadOnlyCollection<MyType>> RList {
      get {
        return _List.AsReadOnly();
      }
    }

上述尝试的直接问题是AsReadonly() 仅适用于外部列表。所以我会尝试返回一个ReadOnlyCollection&lt;List&lt;MyItem&gt;&gt;,它不是该属性的声明类型。我能想到的满足声明的属性类型的唯一方法是创建一个新的ReadOnlyCollection,其方式类似于:

new ReadOnlyCollection<ReadOnlyCollection<MyItem>>(_List)

new ReadOnlyCollection<ReadOnlyCollection<MyItem>>() {
new ReadOnlyCollection<MyItem>(_List[0]),
new ReadOnlyCollection<MyItem>(_List[1]),
...
}

但我不确定创建这个嵌套集合的语法是什么;此外,我不确定是否通过创建“新”集合,我将无法通过对属性返回值的引用来跟踪对基础列表的更改。也就是说,如果在 MyClass 里面我做 _List[1].Add(myTypeInstance) 我不确定持有只读版本引用的人会看到那个新项目。

我对其他方法持开放态度。基本上,我只需要公开一个项目列表列表,该列表是只读的,但可以在公开属性的类中进行修改,并且客户端能够看到反映的更改,而无需再次从属性访问器获取值。

【问题讨论】:

    标签: c# readonly-collection


    【解决方案1】:

    ReadOnlyCollection 不能做到这一点,至少不能自己做到。如果您向_List 添加新列表,RList 的任何用户都需要有机会看到链接到该新列表的新ReadOnlyCollectionReadOnlyCollection 根本不支持。它是only原始项目的只读视图,并且原始项目是可修改的。

    您可以实现一个行为类似于ReadOnlyCollection 的自定义类,除了它不返回原始项目,而是包装它们。 ReadOnlyCollection 的实现很简单:几乎所有ReadOnlyCollection 的方法都可以在一行中实现:它们要么抛出一个无条件异常(例如IList.Add),要么返回一个常量值(例如IList.IsReadOnly) ,或转发到代理容器(例如Count)。

    您需要进行的更改以使其适应您的使用涉及直接处理项目的各种方法。请注意,这不仅仅是this[int] 索引器:您还需要确保相同列表的两个代理比较相等,并提供一个私有/内部方法来从代理获取原始列表,以制作这样的方法作为Contains 工作。您还需要创建自定义枚举器类型以确保 GetEnumerator 不会开始返回原始项目。您需要创建一个自定义的 CopyTo 实现。但即使记住这些,也应该很容易做到。


    也就是说,也许有一种不太干净但更简单的方法:如果您创建一个派生自ReadOnlyCollection&lt;T&gt; 的自定义类MyReadOnlyCollection&lt;T&gt;,您可以提供一个内部Items 成员(转发到ReadOnlyCollection&lt;T&gt;'s受保护的Items 成员)。

    然后您可以创建一个MyReadOnlyCollection&lt;MyReadOnlyCollection&lt;MyClass&gt;&gt;,并从您自己的程序集中调用RList[0].Items.Add,而不必担心外部用户也可以调用它。


    如前所述,如果外部列表实际上从未更改,则可以更简单:在这种情况下,您可以简单地执行类似的操作

    public ReadOnlyCollection<ReadOnlyCollection<MyType>> RList {
      get {
        return _List.ConvertAll(list => list.AsReadOnly()).AsReadOnly();
      }
    }
    

    它不会费心监视_List 的变化。

    【讨论】:

    • 但是如果我 ReadOnlyCollection 是普通集合的只读版本,我不能以某种方式为主列表中的每个列表创建一个 ReadOnlyCollection,然后从该主列表中创建一个 ReadOnlyCollection清单?之后,我可以添加和删除“内部”列表的项目,只要不修改“内部”最少的数量(仅其中的项目),我就可以公开只读列表的只读列表。也许?
    • @SaldaVonSchwartz 是的,如果您从不更改外部列表,那将起作用。但是,如果您确实更改了外部列表,无论您如何更改它(添加列表、删除列表、将列表更改为新列表实例),它都会中断。而且您确实在问题中提到了_List.Add(),这将是破坏它的事情之一。
    • 也许我在我的例子中不是很清楚:/我的意思是“项目列表”方法会起作用,所以询问变量项目的固定列表列表方法是否会以类似的方式起作用方式,我相信你刚才说的会奏效。在这种情况下,您是否介意编辑您的答案以显示如何初始化/构造每个变量项的固定数量的 ReadOnlyCollection 的 ReadOnlyCollection,以便我可以将其标记为正确?
    • @SaldaVonSchwartz(忘记通知你)当然,完成。如果任何内部列表发生变化,这将反映在任何已检索的集合中。
    • 属性获取方法通常不应分配内存,或执行潜在的长时间迭代操作。如果这是一个普通的方法而不是一个属性,那么开销对用户来说会更明显。
    【解决方案2】:

    使用Private Set修改暴露属性的类中的底层集合

    private List<MyType> _List;
    public ReadOnlyCollection<MyType> RList {
      get {
        return _List.AsReadOnly();
      }
      private set
       {
          _List = value;
       }
    }
    

    使用Events 通知其他类集合已被修改

    我正在添加代码以防上述链接过期。 :/

    示例

    The following simple example shows a class, ListWithChangedEvent, which is similar to the standard ArrayList class, but also invokes a Changed event whenever the contents of the list change. Such a general-purpose class could be used in numerous ways in a large 
    program.
    
    // events1.cs
    using System;
    namespace MyCollections 
    {
       using System.Collections;
    
       // A delegate type for hooking up change notifications.
       public delegate void ChangedEventHandler(object sender, EventArgs e);
    
       // A class that works just like ArrayList, but sends event
       // notifications whenever the list changes.
       public class ListWithChangedEvent: ArrayList 
       {
          // An event that clients can use to be notified whenever the
          // elements of the list change.
          public event ChangedEventHandler Changed;
    
          // Invoke the Changed event; called whenever list changes
          protected virtual void OnChanged(EventArgs e) 
          {
             if (Changed != null)
                Changed(this, e);
          }
    
          // Override some of the methods that can change the list;
          // invoke event after each
          public override int Add(object value) 
          {
             int i = base.Add(value);
             OnChanged(EventArgs.Empty);
             return i;
          }
    
          public override void Clear() 
          {
             base.Clear();
             OnChanged(EventArgs.Empty);
          }
    
          public override object this[int index] 
          {
             set 
             {
                base[index] = value;
                OnChanged(EventArgs.Empty);
             }
          }
       }
    }
    
    namespace TestEvents 
    {
       using MyCollections;
    
       class EventListener 
       {
          private ListWithChangedEvent List;
    
          public EventListener(ListWithChangedEvent list) 
          {
             List = list;
             // Add "ListChanged" to the Changed event on "List".
             List.Changed += new ChangedEventHandler(ListChanged);
          }
    
          // This will be called whenever the list changes.
          private void ListChanged(object sender, EventArgs e) 
          {
             Console.WriteLine("This is called when the event fires.");
          }
    
          public void Detach() 
          {
             // Detach the event and delete the list
             List.Changed -= new ChangedEventHandler(ListChanged);
             List = null;
          }
       }
    
       class Test 
       {
          // Test the ListWithChangedEvent class.
          public static void Main() 
          {
          // Create a new list.
          ListWithChangedEvent list = new ListWithChangedEvent();
    
          // Create a class that listens to the list's change event.
          EventListener listener = new EventListener(list);
    
          // Add and remove items from the list.
          list.Add("item 1");
          list.Clear();
          listener.Detach();
          }
       }
    }
    

    输出

    This is called when the event fires.
    This is called when the event fires.
    

    【讨论】:

      猜你喜欢
      • 2018-01-23
      • 1970-01-01
      • 1970-01-01
      • 2012-12-15
      • 2011-10-16
      • 2010-10-04
      • 2013-08-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多