5

这有点难以解释,我希望我的英语足够:

我有一个“A”类,它应该维护一个“B”类对象的列表(如私有列表)。“A”类的消费者应该能够将项目添加到列表中。将项目添加到列表后,消费者应该不能再次修改它们,更不用说他不能对列表本身进行调整(添加或删除项目)。但他应该能够枚举列表中的项目并获取它们的值。有它的模式吗?你会怎么做?

如果问题不够清楚,请告诉我。

4

8 回答 8

4

为了防止编辑列表或其项目,您必须使它们成为不可变的,这意味着您必须在每次请求时返回元素的新实例。

请参阅 Eric Lippert 的优秀系列“C# 中的不变性”:http: //blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx(您必须向下滚动一点)

于 2008-09-24T10:34:54.270 回答
1

正如许多这些答案所示,有很多方法可以使集合本身不可变。

保持集合的成员不可变需要更多的努力。一种可能性是使用外观/代理(对不起,不够简洁):

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 对象。

于 2008-09-24T13:07:09.273 回答
0

编辑:添加了对版本上下文的支持。调用者只能在版本上下文中添加元素。您还可以强制在实例的生命周期内只能创建一个版本上下文。


使用封装,您可以定义任何一组策略来访问内部私有成员。以下示例是您要求的基本实现:

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();
        }
    }
}
于 2008-09-24T10:20:06.240 回答
0

您基本上希望避免放弃对 B 类项目的引用。这就是为什么你应该做一个项目的副本。

我认为这可以通过 List 对象的 ToArray() 方法解决。如果要防止更改,则需要创建列表的深层副本。

一般来说:在大多数情况下,为了强制执行良好的行为而进行复制是不值得的,尤其是当您还写信给消费者时。

于 2008-09-24T10:32:04.263 回答
0
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>();
}

缺点是消费者可以修改它从 Enumerator 获得的项目,解决方案是对私有 List<T> 进行深度复制。

于 2008-09-24T10:38:34.527 回答
0

不清楚您是否还需要 B 实例本身在添加到列表后是不可变的。您可以在这里通过使用 B 的只读接口来玩一个技巧,并且只通过列表公开这些。

internal class B : IB
{
    private string someData;

    public string SomeData
    {
        get { return someData; }
        set { someData = value; }
    }
}

public interface IB
{
    string SomeData { get; }
}
于 2008-09-24T10:40:55.363 回答
0

如果不再允许编辑,我能想到的最简单的方法是返回基础集合的只读版本。

public IList ListOfB
{
    get 
    {
        if (_readOnlyMode) 
            return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB);
        else
            return listOfB;
    }
}

但就个人而言,我不会向客户端公开底层列表,而只是提供添加、删除和枚举 B 实例的方法。

于 2008-09-24T10:48:50.947 回答
0

哇,对于一个简单的问题,这里有一些过于复杂的答案。

拥有私人List<T>

有一个public void AddItem(T item)方法——每当你决定让它停止工作时,让它停止工作。你可以抛出一个异常,或者你可以让它静默失败。就看你那边的情况了。

有一个public T[] GetItems()方法return _theList.ToArray()

于 2008-09-24T11:11:14.510 回答