Pergunta

Esta é meio difícil de explicar, eu espero que o meu Inglês é suficiente:

Eu tenho uma classe "A", que deve manter uma lista de objetos da classe "B" (como uma lista privada). Um consumidor de classe "A" deve ser capaz de adicionar itens à lista. Depois que os itens são adicionados à lista, o consumidor não deve ser capaz de modificá-los de novo, deixou sozinho que ele não deve ser capaz de paciência com a própria lista (adicionar ou remover itens). Mas ele deve ser capaz de enumerar os itens na lista e obter seus valores. Existe um padrão para isso? Como você faria isso?

Se a questão não é clara o suficiente, por favor me avise.

Foi útil?

Solução

Para evitar a edição da lista ou de seus itens que você tem que fazê-los imutável , o que significa você tem que retornar uma nova instância de um elemento em cada solicitação.

excelente série See de Eric Lippert da "imutabilidade em C #": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (você tem que rolar um pouco para baixo)

Outras dicas

Wow, há algumas respostas excessivamente complexos aqui para um problema simples.

Tenha um List<T> privada

Tenha um método public void AddItem(T item) - sempre que você decidir fazer esse trabalho stop, torná-lo parar de trabalhar. Você poderia lançar uma exceção ou você pode apenas fazê-lo falhar silenciosamente. Depende do que você tem em curso lá.

Ter um método public T[] GetItems() que faz return _theList.ToArray()

Como muitas dessas respostas mostram, existem muitas maneiras de fazer a coleta em si imutável.

É preciso mais esforço para manter os membros da imutável coleção. Uma possibilidade é usar uma fachada / proxy (desculpem a falta de brevidade):

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();
    }
}

A desvantagem desta solução é que o chamador será iteração sobre uma colecção de ProxyB objectos, em vez de o B objetos eles adicionado.

EDIT: Adicionado suporte para contextos edição. Caller só pode adicionar elementos dentro de um contexto de edição. Você pode Aditionally aplicar nesse contexto apenas uma edição podem ser criados para a vida da instância.


Usando encapsulamento você pode definir qualquer conjunto de políticas para acessar o membro privado interior. O exemplo a seguir é uma implementação básica de seus requisitos:

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();
        }
    }
}

Você basicamente quer evitar dar afastado referências aos itens da classe B. É por isso que você deve fazer uma cópia dos itens.

Eu acho que isso pode ser resolvido com o método ToArray () de um objeto List. Você precisa criar um deep-cópia da lista se você quiser evitar alterações.

De um modo geral:. Na maioria das vezes não vale a pena fazer uma cópia para reforçar o bom comportamento, especialmente quando você também escrever o consumidor

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>();
}

A desvantagem é que um consumidor pode modificar os itens que ele recebe do Enumerator, uma solução é fazer deepcopy da Lista privada .

Não ficou claro se também necessário as instâncias B-se a ser imutável uma vez adicionado à lista. Você pode jogar um truque aqui usando uma interface somente leitura para B, e só expondo estes através da lista.

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}
scroll top