Управление доступом к внутренней коллекции в c # - Требуется шаблон

StackOverflow https://stackoverflow.com/questions/126367

Вопрос

Это довольно сложно объяснить, я надеюсь, что моего английского достаточно:

У меня есть класс "A", который должен поддерживать список объектов класса "B" (например, закрытый список).Потребитель класса "А" должен иметь возможность добавлять элементы в список.После добавления товаров в список потребитель не должен иметь возможности изменять их снова, не говоря уже о том, что он не должен иметь возможности управлять самим списком (добавлять или удалять товары).Но он должен быть в состоянии перечислить элементы в списке и получить их значения.Есть ли для этого какой-то шаблон?Как бы вы это сделали?

Если вопрос недостаточно ясен, пожалуйста, дайте мне знать.

Это было полезно?

Решение

Чтобы предотвратить редактирование списка или его элементов, вы должны создать их неизменяемый, что означает, что вы должны возвращать новый экземпляр элемента при каждом запросе.

Смотрите превосходную серию статей Эрика Липперта "Неизменяемость в 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.Вот почему вы должны сделать копию этих элементов.

Я думаю, это можно решить с помощью метода toArray() объекта List.Вам нужно создать полную копию списка, если вы хотите предотвратить изменения.

Вообще говоря:в большинстве случаев не стоит делать копию для обеспечения надлежащего поведения, особенно когда вы также пишете потребителю.

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

Недостатком является то, что потребитель может изменять элементы, которые он получает от перечислителя, решение состоит в том, чтобы сделать глубокую копию закрытого списка<T>.

Было неясно, нужно ли вам также, чтобы сами экземпляры B были неизменяемыми после добавления в список.Здесь вы можете сыграть хитрость, используя интерфейс только для чтения для B и отображая их только через список.

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}

Самое простое, что я могу придумать, это вернуть a версия только для чтения из базовой коллекции, если редактирование больше не разрешено.

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

Однако лично я бы не стал предоставлять базовый список клиенту, а просто предоставил методы для добавления, удаления и перечисления экземпляров B.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top