Pergunta

Cada vez que crio um objeto que possui uma propriedade de coleção, vou e volto sobre a melhor maneira de fazer isso?

  1. propriedade pública com um getter que retorna uma referência à variável privada
  2. get_ObjList e set_ObjList explícitos métodos que retornam e criam novos ou clonados objetos todas as vezes
  3. get_ObjList explícito que retorna um IEnumerator e um set_ObjList que leva IEnumerator

Faz diferença se a coleção é uma matriz (ou seja, objList.Clone()) versus uma lista?

Se retornar a coleção real como referência é tão ruim porque cria dependências, então por que retornar qualquer propriedade como referência?Sempre que você expõe um objeto filho como referência, os componentes internos desse filho podem ser alterados sem que o pai "saiba", a menos que o filho tenha um evento de alteração de propriedade.Existe risco de vazamento de memória?

E as opções 2 e 3 não quebram a serialização?Isso é um problema ou você precisa implementar a serialização personalizada sempre que tiver uma propriedade de coleção?

O ReadOnlyCollection genérico parece um bom compromisso para uso geral.Ele envolve um IList e restringe o acesso a ele.Talvez isso ajude com vazamentos de memória e serialização.Contudo ainda tem preocupações de enumeração

Talvez dependa apenas.Se você não se importa que a coleção seja modificada, basta expô-la como um acessador público sobre uma variável privada conforme nº 1.Se você não deseja que outros programas modifiquem a coleção, o número 2 e/ou o número 3 são melhores.

Implícito na questão está por que um método deveria ser usado em detrimento de outro e quais são as ramificações na segurança, memória, serialização, etc.?

Foi útil?

Solução

A forma como você expõe uma coleção depende inteiramente de como os usuários pretendem interagir com ela.

1) Se os usuários adicionarem e removerem itens da coleção de um objeto, então uma propriedade simples de coleção somente get é melhor (opção nº 1 da pergunta original):

private readonly Collection<T> myCollection_ = new ...;
public Collection<T> MyCollection {
  get { return this.myCollection_; }
}

Esta estratégia é utilizada para Items coleções no WindowsForms e WPF ItemsControl controles, onde os usuários adicionam e removem itens que desejam que o controle exiba.Esses controles publicam a coleção real e usam retornos de chamada ou ouvintes de eventos para controlar os itens.

O WPF também expõe algumas coleções configuráveis ​​para permitir que os usuários exibam uma coleção de itens que eles controlam, como o ItemsSource propriedade em ItemsControl (opção nº 3 da pergunta original).No entanto, este não é um caso de uso comum.


2) Se os usuários irão apenas ler dados mantidos pelo objeto, então você pode usar uma coleção somente leitura, como Quibblesome sugerido:

private readonly List<T> myPrivateCollection_ = new ...;
private ReadOnlyCollection<T> myPrivateCollectionView_;
public ReadOnlyCollection<T> MyCollection {
  get {
    if( this.myPrivateCollectionView_ == null ) { /* lazily initialize view */ }
    return this.myPrivateCollectionView_;
  }
}

Observe que ReadOnlyCollection<T> fornece uma visualização ao vivo da coleção subjacente, portanto, você só precisa criar a visualização uma vez.

Se a coleção interna não implementar IList<T>, ou, se quiser restringir o acesso a usuários mais avançados, você pode agrupar o acesso à coleção por meio de um enumerador:

public IEnumerable<T> MyCollection {
  get {
    foreach( T item in this.myPrivateCollection_ )
      yield return item;
  }
}

Essa abordagem é simples de implementar e também fornece acesso a todos os membros sem expor o acervo interno.No entanto, exige que a coleção permaneça inalterada, pois as classes da coleção BCL lançarão uma exceção se você tentar enumerar uma coleção após ela ter sido modificada.Se for provável que a coleção subjacente seja alterada, você poderá criar um wrapper leve que enumerará a coleção com segurança ou retornará uma cópia da coleção.


3) Finalmente, se você precisar expor matrizes em vez de coleções de nível superior, deverá retornar uma cópia da matriz para evitar que os usuários a modifiquem (opção nº 2 da pergunta original):

private T[] myArray_;
public T[] GetMyArray( ) {
  T[] copy = new T[this.myArray_.Length];
  this.myArray_.CopyTo( copy, 0 );
  return copy;
  // Note: if you are using LINQ, calling the 'ToArray( )' 
  //  extension method will create a copy for you.
}

Você não deve expor o array subjacente por meio de uma propriedade, pois não será capaz de saber quando os usuários o modificarão.Para permitir a modificação do array, você pode adicionar um correspondente SetMyArray( T[] array ) método ou use um indexador personalizado:

public T this[int index] {
  get { return this.myArray_[index]; }
  set {
    // TODO: validate new value; raise change event; etc.
    this.myArray_[index] = value;
  }
}

(é claro que, ao implementar um indexador personalizado, você duplicará o trabalho das classes BCL :)

Outras dicas

Eu costumo optar por isso, um getter público que retorna System.Collections.ObjectModel.ReadOnlyCollection:

public ReadOnlyCollection<SomeClass> Collection
{
    get
    {
         return new ReadOnlyCollection<SomeClass>(myList);
    }
}

E métodos públicos no objeto para modificar a coleção.

Clear();
Add(SomeClass class);

Se a classe deveria ser um repositório para outras pessoas mexerem, então eu apenas exponho a variável privada conforme o método nº 1, pois ela economiza a escrita de sua própria API, mas tendo a evitar isso no código de produção.

Se você deseja simplesmente expor uma coleção em sua instância, usar um getter/setter para uma variável de membro privado parece ser a solução mais sensata para mim (sua primeira opção proposta).

Por que você sugere que usar ReadOnlyCollection(T) é um compromisso?Se você ainda precisar receber notificações de alterações feitas no IList original, você também pode usar um ReadOnlyObservableCollection(T) para embrulhar sua coleção.Isso seria menos comprometedor no seu cenário?

Sou desenvolvedor java, mas acho que o mesmo acontece com c#.

Eu nunca exponho uma propriedade de coleção privada porque outras partes do programa podem alterá-la sem que os pais percebam, então no método getter eu retorno um array com os objetos da coleção e no método setter eu chamo um clearAll() sobre a coleção e depois um addAll()

ReadOnlyCollection ainda tem a desvantagem de o consumidor não poder ter certeza de que a coleção original não será alterada em um momento inoportuno.Em vez disso você pode usar Coleções imutáveis.Se você precisar fazer uma alteração, em vez de alterar o original, você receberá uma cópia modificada.A forma como é implementado é competitivo com o desempenho das coleções mutáveis.Ou melhor ainda, se você não precisar copiar o original várias vezes para fazer diversas alterações diferentes (incompatíveis) posteriormente em cada cópia.

Eu recomendo usar o novo IReadOnlyList<T> e IReadOnlyCollection<T> Interfaces para expor uma coleção (requer .NET 4.5).

Exemplo:

public class AddressBook
{
    private readonly List<Contact> contacts;

    public AddressBook()
    {
        this.contacts = new List<Contact>();
    }

    public IReadOnlyList<Contact> Contacts { get { return contacts; } }

    public void AddContact(Contact contact)
    {
        contacts.Add(contact);
    }

    public void RemoveContact(Contact contact)
    {
        contacts.Remove(contact);
    }
}

Se você precisa garantir que a coleção não pode ser manipulada externamente, considere ReadOnlyCollection<T> ou as novas coleções imutáveis.

Evitar usando a interface IEnumerable<T> para expor uma coleção.Esta interface não define nenhuma garantia de que múltiplas enumerações tenham um bom desempenho.Se IEnumerable representar uma consulta, cada enumeração executará a consulta novamente.Os desenvolvedores que obtêm uma instância de IEnumerable não sabem se ela representa uma coleção ou uma consulta.

Mais sobre este tópico pode ser lido neste Página Wiki.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top