Domanda

Questo è un po 'difficile da spiegare, spero che il mio inglese sia sufficiente:

Ho una classe " A " che dovrebbe mantenere un elenco di oggetti di classe " B " (come un elenco privato). Un consumatore di classe & Quot; A & Quot; dovrebbe essere in grado di aggiungere elementi all'elenco. Dopo che gli articoli sono stati aggiunti all'elenco, il consumatore non dovrebbe essere in grado di modificarli di nuovo, lasciato solo che non dovrebbe essere in grado di temperare con l'elenco stesso (aggiungere o rimuovere elementi). Ma dovrebbe essere in grado di enumerare gli elementi nell'elenco e ottenere i loro valori. C'è un modello per questo? Come lo faresti?

Se la domanda non è abbastanza chiara, per favore fatemi sapere.

È stato utile?

Soluzione

Per impedire la modifica dell'elenco o dei suoi elementi, è necessario renderli immutabili , il che significa devi restituire una nuova istanza di un elemento su ogni richiesta.

Vedi l'eccellente serie di " di Eric Lippert; Immutabilità in C # " ;: http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (devi scorrere un po 'verso il basso)

Altri suggerimenti

Wow, ci sono alcune risposte troppo complesse qui per un semplice problema.

Avere un List<T>

privato

Avere un metodo public void AddItem(T item) - ogni volta che decidi di farlo smettere di funzionare, fallo smettere di funzionare. Potresti lanciare un'eccezione o potresti semplicemente farla fallire silenziosamente. Dipende da cosa stai succedendo laggiù.

Avere un public T[] GetItems() metodo che fa return _theList.ToArray()

Come mostrano molte di queste risposte, ci sono molti modi per rendere immutabile la collezione stessa.

Ci vuole uno sforzo maggiore per mantenere immutabili i membri della collezione. Una possibilità è quella di utilizzare una facciata / proxy (scusate la mancanza di brevità):

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

Il rovescio della medaglia di questa soluzione è che il chiamante ripeterà una raccolta di oggetti ProxyB, piuttosto che gli oggetti B che hanno aggiunto.

MODIFICA: Aggiunto supporto per contesti di edizione. Il chiamante può solo aggiungere elementi all'interno di un contesto di edizione. Puoi imporre adizionale che è possibile creare un solo contesto di edizione per la durata dell'istanza.


Utilizzando l'incapsulamento è possibile definire qualsiasi set di criteri per accedere al membro privato interno. Il seguente esempio è un'implementazione di base dei tuoi requisiti:

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

Fondamentalmente si vuole evitare di regalare riferimenti agli oggetti di classe B. Ecco perché dovresti fare una copia degli oggetti.

Penso che questo possa essere risolto con il metodo ToArray () di un oggetto List. È necessario creare una copia approfondita dell'elenco se si desidera impedire le modifiche.

In generale: il più delle volte non vale la pena fare una copia per far rispettare il buon comportamento, specialmente quando si scrive anche al consumatore.

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

Il rovescio della medaglia è che un consumatore può modificare gli articoli che ottiene dall'enumeratore, una soluzione è quella di fare una copia in profondità dell'elenco privato < T > ;.

Non era chiaro se fosse necessario che le istanze B stesse fossero immutabili una volta aggiunte all'elenco. Puoi giocare un trucco qui usando un'interfaccia di sola lettura per B, ed esponendoli solo attraverso l'elenco.

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}

Il più semplice che mi viene in mente è restituire una versione sola della raccolta sottostante se la modifica non è più consentita.

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

Personalmente, però, non esporrei l'elenco sottostante al client e fornirei solo metodi per aggiungere, rimuovere ed enumerare le istanze B.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top