Diretrizes para coleções

Observação

Este conteúdo é reimpresso com permissão da Pearson Education, Inc. de Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, 2nd Edition. Essa edição foi publicada em 2008 e, desde então, o livro foi totalmente revisado na terceira edição. Algumas das informações nesta página podem estar desatualizadas.

Qualquer tipo projetado especificamente para manipular um grupo de objetos com uma característica comum pode ser considerado uma coleção. É quase sempre apropriado que esses tipos implementem IEnumerable ou IEnumerable<T>, portanto, nesta seção, consideramos apenas os tipos que implementam uma ou ambas as interfaces como coleções.

❌ Não use coleções com tipos fracos em APIs públicas.

O tipo de todos os parâmetros e valores retornados que representam itens de coleção devem ser o tipo de item exato, não qualquer um de seus tipos base (isso se aplica somente a membros públicos da coleção).

❌ Não use ArrayList ou List<T> em APIs públicas.

Esses tipos são estruturas de dados projetadas para serem usadas na implementação interna, não em APIs públicas. List<T> é otimizado para desempenho e energia às custo da limpeza das APIs e flexibilidade. Por exemplo, se você retornar List<T>, nunca poderá receber notificações quando o código do cliente modificar a coleção. Além disso, List<T> expõe muitos membros, como BinarySearch, que não são úteis ou aplicáveis em muitos cenários. As duas seções a seguir descrevem tipos (abstrações) projetados especificamente para uso em APIs públicas.

❌ Não use Hashtable ou Dictionary<TKey,TValue> em APIs públicas.

Esses tipos são estruturas de dados projetadas para serem usadas na implementação interna. As APIs públicas devem usar IDictionary, IDictionary <TKey, TValue> ou um tipo personalizado que implemente uma ou ambas as interfaces.

❌ NÃO use IEnumerator<T>, IEnumerator ou qualquer outro tipo que implemente qualquer uma dessas interfaces, exceto como o tipo de retorno de um método GetEnumerator.

Tipos que retornam enumeradores de métodos diferentes de GetEnumerator não podem ser usados com a instrução foreach.

❌ Não implemente IEnumerator<T> e IEnumerable<T> no mesmo tipo. O mesmo se aplica às interfaces não genéricas IEnumerator e IEnumerable.

Parâmetros de coleção

✔️ Use o tipo menos especializado possível como um tipo de parâmetro. A maioria dos membros que usam coleções como parâmetros usa a interface IEnumerable<T>.

❌ Evite usar ICollection<T> ou ICollection como um parâmetro apenas para acessar a propriedade Count.

Em vez disso, considere usar IEnumerable<T> ou IEnumerable e verificar dinamicamente se o objeto implementa ICollection<T> ou ICollection.

Propriedades da coleção e valores retornados

❌ Não forneça propriedades de coleção configuráveis.

Os usuários podem substituir o conteúdo da coleção limpando a coleção primeiro e, em seguida, adicionando o novo conteúdo. Se a substituição de toda a coleção for um cenário comum, considere fornecer o método AddRange na coleção.

✔️ Use Collection<T> ou uma subclasse de Collection<T> para propriedades ou valores retornados que representam coleções de leitura/gravação.

Se Collection<T> não atender a alguns requisitos (por exemplo, a coleção não deve implementar IList), use uma coleção personalizada implementando IEnumerable<T>, ICollection<T> ou IList<T>.

✔️ Use ReadOnlyCollection<T>, uma subclasse de ReadOnlyCollection<T>, ou em casos raros IEnumerable<T> para propriedades ou valores retornados que representam coleções somente leitura.

Em geral, prefira ReadOnlyCollection<T>. Se não atender a alguns requisitos (por exemplo, a coleção não deve implementar IList), use uma coleção personalizada implementando IEnumerable<T>, ICollection<T> ou IList<T>. Se você implementar uma coleção personalizada somente leitura, implemente ICollection<T>.IsReadOnly para retornar true.

Quando você tiver certeza de que o único cenário que deseja dar suporte é a iteração somente avanço, você pode usar IEnumerable<T>.

✔️ Considere o uso de subclasses de coleções base genéricas em vez de usar as coleções diretamente.

Isso permite um nome melhor e a adição de membros auxiliares que não estão presentes nos tipos de coleção base. Isso é especialmente aplicável a APIs de alto nível.

✔️ Considere retornar uma subclasse de Collection<T> ou ReadOnlyCollection<T> de métodos e propriedades comumente usados.

Isso permitirá que você adicione métodos auxiliares ou altere a implementação da coleção no futuro.

✔️ Considere o uso de uma coleção com chave se os itens armazenados na coleção tiverem chaves exclusivas (nomes, IDs etc.). As coleções de chave podem ser indexadas por um inteiro e uma chave e geralmente são implementadas herdando de KeyedCollection<TKey,TItem>.

Coleções com chave geralmente têm volumes de memória maiores e não devem ser usadas se a sobrecarga de memória superar os benefícios de ter as chaves.

❌ Não retorne valores nulos de propriedades de coleção ou de métodos que retornam coleções. Em vez disso, retorne uma coleção vazia ou uma matriz vazia.

A regra geral é que coleções ou matrizes nulas e vazias (0 itens) devem ser tratadas da mesma forma.

Instantâneos versus coleções dinâmicas

As coleções que representam um estado em algum momento são chamadas de coleções de instantâneos. Por exemplo, uma coleção que contém linhas retornadas de uma consulta de banco de dados seria um instantâneo. As coleções que sempre representam o estado atual são chamadas de coleções dinâmicas. Por exemplo, uma coleção de ComboBox itens é uma coleção dinâmica.

❌ Não retorne coleções de instantâneos de propriedades. As propriedades devem retornar coleções dinâmicas.

Os getters de propriedade devem ser operações muito leves. O retorno de um instantâneo requer a criação de uma cópia de uma coleção interna em uma operação O(n).

✔️ Use uma coleção de instantâneos ou um IEnumerable<T> dinâmico (ou seu subtipo) para representar coleções voláteis (ou seja, que podem ser alteradas sem modificar explicitamente a coleção).

Em geral, todas as coleções que representam um recurso compartilhado (por exemplo, arquivos em um diretório) são voláteis. Essas coleções são muito difíceis ou impossíveis de serem implementadas como coleções dinâmicas, a menos que a implementação seja simplesmente um enumerador somente avanço.

Escolhendo entre matrizes e coleções

✔️ Prefira coleções em vez de matrizes.

As coleções fornecem mais controle sobre o conteúdo, podem evoluir ao longo do tempo e são mais utilizáveis. Além disso, o uso de matrizes para cenários somente leitura não é recomendado porque o custo de clonagem da matriz é muito elevado. Estudos de usabilidade mostraram que alguns desenvolvedores se sentem mais confortáveis usando APIs baseadas em coleção.

No entanto, se você estiver desenvolvendo APIs de baixo nível, talvez seja melhor usar matrizes para cenários de leitura/gravação. As matrizes têm um volume de memória menor, o que ajuda a reduzir o conjunto de trabalho e o acesso a elementos em uma matriz é mais rápido porque é otimizado pelo runtime.

✔️ Considere o uso de matrizes em APIs de baixo nível para minimizar o consumo de memória e maximizar o desempenho.

✔️ Use matrizes de bytes em vez de coleções de bytes.

❌ Não use matrizes para propriedades se a propriedade tiver que retornar uma nova matriz (por exemplo, uma cópia de uma matriz interna) sempre que o getter de propriedade for chamado.

Implementando coleções personalizadas

✔️ Considere herdar de Collection<T>, ReadOnlyCollection<T> ou KeyedCollection<TKey,TItem> ao projetar novas coleções.

✔️ Implemente IEnumerable<T> ao criar novas coleções. Considere a implementação de ICollection<T> ou IList<T>, quando aplicável.

Ao implementar essa coleção personalizada, siga o padrão de API estabelecido por Collection<T> e ReadOnlyCollection<T> o mais próximo possível. Ou seja, implemente os mesmos membros explicitamente, nomeie os parâmetros como essas duas coleções e assim por diante.

✔️ Considere implementar interfaces de coleção não genéricas (IList e ICollection) se a coleção geralmente for passada para APIs que tomam essas interfaces como entrada.

❌ Evite implementar interfaces de coleção em tipos com APIs complexas não relacionadas ao conceito de uma coleção.

❌ Não herde de coleções base não genéricas, como CollectionBase. Em vez disso, use Collection<T>, ReadOnlyCollection<T> e KeyedCollection<TKey,TItem>.

Nomeação de coleções personalizadas

Coleções (tipos que implementam IEnumerable) são criadas principalmente por dois motivos: (1) para criar uma nova estrutura de dados com operações específicas de estrutura e, muitas vezes, características de desempenho diferentes das estruturas de dados existentes (por exemplo, List<T>, LinkedList<T>, Stack<T>) e (2) para criar uma coleção especializada para manter um conjunto específico de itens (por exemplo, StringCollection). As estruturas de dados são usadas com mais frequência na implementação interna de aplicativos e bibliotecas. As coleções especializadas devem ser expostas principalmente em APIs (como tipos de propriedade e parâmetro).

✔️ Use o sufixo "Dicionário" em nomes de abstrações que implementam IDictionary ou IDictionary<TKey,TValue>.

✔️ Use o sufixo "Coleção" em nomes de tipos que implementam IEnumerable (ou qualquer um de seus descendentes) e representa uma lista de itens.

✔️ Use o nome da estrutura de dados apropriado para estruturas de dados personalizadas.

❌ Evite o uso de sufixos que impliquem uma implementação específica, como "LinkedList" ou "Hashtable", em nomes de abstrações de coleção.

✔️ Considere prefixar nomes de coleção com o nome do tipo de item. Por exemplo, uma coleção que armazena itens do tipo Address (implementando IEnumerable<Address>) deve ser nomeada AddressCollection. Se o tipo de item for uma interface, o prefixo "I" do tipo de item poderá ser omitido. Portanto, uma coleção de IDisposable itens pode ser chamada DisposableCollection.

✔️ Considere o uso do prefixo "ReadOnly" em nomes de coleções somente leitura, se uma coleção gravável correspondente puder ser adicionada ou já existir na estrutura.

Por exemplo, uma coleção somente leitura de cadeias de caracteres deve ser chamada ReadOnlyStringCollection.

Portions © 2005, 2009 Microsoft Corporation. Todos os direitos reservados.

Reimpresso com permissão da Pearson Education, Inc. das Diretrizes de Design do Framework: convenções, linguagens e padrões para bibliotecas do .NET reutilizável, 2ª edição por Krzysztof Cwalina e Brad Abrams, publicado em 22 de outubro de 2008 por Addison-Wesley Professional como parte da série de desenvolvimento do Microsoft Windows.

Confira também