在c#中控制对内部集合的访问 - 需要模式
-
02-07-2019 - |
题
这有点难以解释,我希望我的英语足够:
我有一个班级<!>“A <!>”;应保留类<!>“B <!>”的对象列表; (像私人名单)。类<!>“A <!>”的消费者应该能够将项目添加到列表中。将项目添加到列表后,消费者应该无法再次修改它们,单独留下他不应该使用列表本身(添加或删除项目)。但他应该能够枚举列表中的项目并获取它们的值。有它的模式吗?你会怎么做?
如果问题不够清楚,请告诉我。
解决方案
为了防止编辑列表或其项目,您必须使它们不可变,这意味着你必须在每个请求上返回一个元素的新实例。
请参阅Eric Lippert的优秀系列<!>“C#中的不变性<!>”: http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (你必须向下滚动一下)
其他提示
拥有私人List<T>
有一个public void AddItem(T item)
方法 - 每当你决定让它停止工作时,让它停止工作。你可以抛出异常,或者你可以让它无声地失败。取决于你在那里发生了什么。
有一个public T[] GetItems()
return _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对象。
编辑:添加了对版本上下文的支持。调用者只能在编辑上下文中添加元素。您可以通过强制执行,只能在实例的生命周期内创建一个版本上下文。
使用封装,您可以定义任何一组策略来访问内部私有成员。以下示例是您的要求的基本实现:
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()方法来解决。如果要阻止更改,则需要创建列表的深层副本。
一般而言:大多数情况下,制作副本以强制执行良好行为是不值得的,尤其是当您还要编写消费者时。
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>();
}
缺点是消费者可以修改从枚举器获得的项目,解决方案是对私人列表进行深度复制<!> lt; T <!> gt;。
一旦添加到列表中,您是否还需要B实例本身是不可变的还不清楚。你可以通过使用B的只读接口来播放一个技巧,只在列表中公开它们。
internal class B : IB
{
private string someData;
public string SomeData
{
get { return someData; }
set { someData = value; }
}
}
public interface IB
{
string SomeData { get; }
}
我能想到的最简单的方法是返回 readonly version 。
public IList ListOfB
{
get
{
if (_readOnlyMode)
return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB);
else
return listOfB;
}
}
就个人而言,我不会将基础列表暴露给客户端,只提供添加,删除和枚举B实例的方法。