Pregunta

Esto es un poco difícil de explicar, espero que mi inglés sea suficiente:

Tengo una clase "A" que debería mantener una lista de objetos de clase "B" (como una Lista privada).Un consumidor de clase "A" debería poder agregar artículos a la lista.Después de agregar los elementos a la lista, el consumidor no debería poder modificarlos nuevamente, sin olvidar que no debería poder modificar la lista en sí (agregar o eliminar elementos).Pero debería poder enumerar los elementos de la lista y obtener sus valores.¿Existe un patrón para ello?¿Cómo lo harías tú?

Si la pregunta no es lo suficientemente clara, hágamelo saber.

¿Fue útil?

Solución

Para evitar editar la lista o sus elementos, debe hacerlos inmutable, lo que significa que debe devolver una nueva instancia de un elemento en cada solicitud.

Vea la excelente serie de Eric Lippert sobre "Inmutabilidad en C#": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (tienes que desplazarte un poco hacia abajo)

Otros consejos

Vaya, aquí hay algunas respuestas demasiado complejas para un problema simple.

tener un privado List<T>

Tener un public void AddItem(T item) método: cada vez que decidas hacer que eso deje de funcionar, haz que deje de funcionar.Podrías lanzar una excepción o simplemente hacer que falle silenciosamente.Depende de lo que estés pasando allí.

Tener un public T[] GetItems() método que lo hace return _theList.ToArray()

Como muestran muchas de estas respuestas, hay muchas maneras de hacer que la colección sea inmutable.

Se necesita más esfuerzo para mantener inmutables a los miembros de la colección.Una posibilidad es utilizar una fachada/proxy (perdón por la falta de brevedad):

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

La desventaja de esta solución es que la persona que llama iterará sobre una colección de objetos ProxyB, en lugar de los objetos B que agregó.

EDITAR: Se agregó soporte para contextos de edición.La persona que llama solo puede agregar elementos dentro de un contexto de edición.Además, puede exigir que solo se pueda crear un contexto de edición durante la vida útil de la instancia.


Al utilizar la encapsulación, puede definir cualquier conjunto de políticas para acceder al miembro privado interno.El siguiente ejemplo es una implementación básica de sus 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();
        }
    }
}

Básicamente, desea evitar revelar referencias a los elementos de clase B.Por eso debes hacer una copia de los artículos.

Creo que esto se puede resolver con el método ToArray() de un objeto List.Debe crear una copia profunda de la lista si desea evitar cambios.

Generalmente hablando:la mayoría de las veces no vale la pena hacer una copia para imponer un buen comportamiento, especialmente cuando también le escribes al 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>();
}

La desventaja es que un consumidor puede modificar los elementos que obtiene del enumerador; una solución es hacer una copia profunda de la lista privada <T>.

No estaba claro si también era necesario que las instancias B fueran inmutables una vez agregadas a la lista.Puedes jugar un truco aquí usando una interfaz de solo lectura para B y exponiéndolos solo a través de la lista.

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}

Lo más simple que se me ocurre es devolver un versión de solo lectura de la colección subyacente si ya no se permite la edición.

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

Sin embargo, personalmente, no expondría la lista subyacente al cliente y solo proporcionaría métodos para agregar, eliminar y enumerar las instancias B.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top