Управление доступом к внутренней коллекции в c # - шаблон необходим - PullRequest
5 голосов
/ 24 сентября 2008

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

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

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

Ответы [ 8 ]

3 голосов
/ 24 сентября 2008

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

См. Превосходную серию Эрика Липперта "Неизменность в C #": http://blogs.msdn.com/ericlippert/archive/tags/Immutability/C_2300_/default.aspx (вам нужно немного прокрутить вниз)

1 голос
/ 24 сентября 2008

Как показывают многие из этих ответов, есть много способов сделать коллекцию неизменной.

Требуется больше усилий для сохранения неизменности членов коллекции. Одна из возможностей - использовать фасад / прокси (извините за краткость):

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.

1 голос
/ 24 сентября 2008

Ух ты, здесь есть несколько слишком сложных ответов для простой проблемы.

Иметь личное List<T>

Имейте метод public void AddItem(T item) - всякий раз, когда вы решите, чтобы это перестало работать, перестаньте работать. Вы можете выбросить исключение или просто заставить его молчать. Зависит от того, что у тебя там происходит.

Есть метод public T[] GetItems(), который делает return _theList.ToArray()

0 голосов
/ 24 сентября 2008

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

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

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

0 голосов
/ 24 сентября 2008

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

internal class B : IB
{
    private string someData;

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

public interface IB
{
    string SomeData { get; }
}
0 голосов
/ 24 сентября 2008
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>();
}

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

0 голосов
/ 24 сентября 2008

Вы, в основном, хотите не давать ссылки на предметы класса B. Вот почему вы должны сделать копию предметов.

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

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

0 голосов
/ 24 сентября 2008

РЕДАКТИРОВАТЬ: Добавлена ​​поддержка контекстов издания. Вызывающая сторона может добавлять элементы только в контекст издания. Вы можете дополнительно принудительно установить, что только один контекст издания может быть создан для времени существования экземпляра.


Используя инкапсуляцию, вы можете определить любой набор политик для доступа к внутреннему приватному члену. Следующий пример представляет собой базовую реализацию ваших требований:

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