在C#中控制对内部集合的访问-需要模式
这很难解释,我希望我的英语足够:
我有一个" A"类,该类应维护" B"类的对象列表(如私有列表)。类" A"的消费者应该能够将项目添加到列表中。将项目添加到列表后,消费者应该不能再次对其进行修改,更不用说他不应该对列表本身进行修改(添加或者删除项目)。但是他应该能够枚举列表中的项目并获得它们的值。有模式吗?你会怎么做?
如果问题不够清楚,请告诉我。
解决方案
编辑:添加了对版本上下文的支持。调用者只能在版本上下文中添加元素。我们可以强制实施实例的生命周期内只能创建一个版本上下文。
使用封装,我们可以定义任何策略集来访问内部私有成员。以下示例是要求的基本实现:
namespace ConsoleApplication2 { using System; using System.Collections.Generic; using System.Collections; class B { } interface IEditable { void StartEdit(); void StopEdit(); } class EditContext<T> : IDisposable where T : IEditable { private T parent; public EditContext(T parent) { parent.StartEdit(); this.parent = parent; } public void Dispose() { this.parent.StopEdit(); } } class A : IEnumerable<B>, IEditable { private List<B> _myList = new List<B>(); private bool editable; public void Add(B o) { if (!editable) { throw new NotSupportedException(); } _myList.Add(o); } public EditContext<A> ForEdition() { return new EditContext<A>(this); } public IEnumerator<B> GetEnumerator() { return _myList.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } public void StartEdit() { this.editable = true; } public void StopEdit() { this.editable = false; } } class Program { static void Main(string[] args) { A a = new A(); using (EditContext<A> edit = a.ForEdition()) { a.Add(new B()); a.Add(new B()); } foreach (B o in a) { Console.WriteLine(o.GetType().ToString()); } a.Add(new B()); Console.ReadLine(); } } }
我们基本上希望避免放弃对B类项目的引用。这就是为什么我们应该复制这些项目的原因。
我认为可以使用List对象的ToArray()方法解决此问题。如果要防止更改,则需要创建列表的深层副本。
一般而言:在大多数情况下,不值得进行复制以实施良好行为,特别是当我们还编写使用者时。
为了防止编辑列表或者其项目,我们必须使其不可变,这意味着我们必须在每个请求上返回一个元素的新实例。
请参阅Eric Lippert的出色的" C#的不可变性"系列:http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx(我们需要向下滚动)
public class MyList<T> : IEnumerable<T>{ public MyList(IEnumerable<T> source){ data.AddRange(source); } public IEnumerator<T> GetEnumerator(){ return data.Enumerator(); } private List<T> data = new List<T>(); }
缺点是消费者可以修改从枚举器获取的项目,解决方案是对私有List <T>进行深拷贝。
尚不清楚添加到列表中后是否还需要B实例本身是不可变的。我们可以在此处使用B的只读接口,并仅在列表中公开这些接口,从而发挥作用。
internal class B : IB { private string someData; public string SomeData { get { return someData; } set { someData = value; } } } public interface IB { string SomeData { get; } }
我想到的最简单的方法是,如果不再允许编辑,则返回基础集合的只读版本。
public IList ListOfB { get { if (_readOnlyMode) return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB); else return listOfB; } }
不过,就我个人而言,我不会向客户公开底层列表,而只是提供添加,删除和枚举B实例的方法。
哇,对于一个简单的问题,这里有一些过于复杂的答案。
有一个私有的List <T>
每当我们决定使其停止工作时,都有一个public void AddItem(T item)
方法。我们可以引发异常,也可以使其无声地失败。取决于你在那里发生的事情。
有一个公共T [] GetItems()方法,该方法返回_theList.ToArray()。
正如许多答案所示,有很多方法可以使集合本身不可变。
要使集合的成员保持不变,需要付出更多的努力。一种可能性是使用外墙/代理(抱歉,缺乏简洁性):
class B { public B(int data) { this.data = data; } public int data { get { return privateData; } set { privateData = value; } } private int privateData; } class ProxyB { public ProxyB(B b) { actual = b; } public int data { get { return actual.data; } } private B actual; } class A : IEnumerable<ProxyB> { private List<B> bList = new List<B>(); class ProxyEnumerator : IEnumerator<ProxyB> { private IEnumerator<B> b_enum; public ProxyEnumerator(IEnumerator<B> benum) { b_enum = benum; } public bool MoveNext() { return b_enum.MoveNext(); } public ProxyB Current { get { return new ProxyB(b_enum.Current); } } Object IEnumerator.Current { get { return this.Current; } } public void Reset() { b_enum.Reset(); } public void Dispose() { b_enum.Dispose(); } } public void AddB(B b) { bList.Add(b); } public IEnumerator<ProxyB> GetEnumerator() { return new ProxyEnumerator(bList.GetEnumerator()); } IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } }
该解决方案的缺点是,调用方将遍历ProxyB对象的集合,而不是遍历它们添加的B对象。