Controlar o acesso a uma coleção interna em c # - padrão exigido
-
02-07-2019 - |
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.
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; }
}
O mais simples que eu posso pensar é retornar um rel="nofollow noreferrer"> versão somente leitura da coleção subjacente se a edição não é mais permitido.
public IList ListOfB
{
get
{
if (_readOnlyMode)
return listOfB.AsReadOnly(); // also use ArrayList.ReadOnly(listOfB);
else
return listOfB;
}
}
Pessoalmente, porém, eu não iria expor a lista subjacente ao cliente e apenas fornecer métodos para adicionar, remover e enumerar as instâncias B.