O programador

Coleções .NET: Introdução à C5

Ted Neward

 

Ted NewardTenho uma confissão a fazer.

Durante o dia, trabalho como um desenvolvedor complacente do .NET para a Neudesic LLC, uma empresa de consultoria. Mas à noite, depois que minha esposa e meus filhos dormem, fujo de casa, laptop nas mãos e vou até meu esconderijo secreto (um Denny's na 148th) e... escrevo códigos Java.

Sim, pessoal, vivo uma vida dupla, como um desenvolvedor .NET e um desenvolvedor Java ou, mais precisamente, uma JVM (Máquina Virtual Java). Uma das vantagens interessantes de ter um estilo de vida duplo é que posso ver áreas onde o Microsoft .NET Framework tem algumas excelentes ideias que podem ser trazidas de volta para a JVM. Uma dessas ideias foi a dos atributos personalizados, que a JVM adotou no Java5 (de volta mais ou menos em 2005) com o nome de "anotações". Mas também aconteceu o inverso: na realidade, a JVM teve alguns êxitos que o CLR e a BCL (Biblioteca de Classes Base) do .NET não obtiveram (ou, pelo menos, não totalmente, caso você esteja um pouco na defensiva). Uma delas está no centro da BCL do .NET: as coleções.

Coleções: Uma crítica

Parte da falha nas coleções .NET se baseia no fato de que a equipe da BCL teve que escrever bobagens duas vezes: uma para versão do .NET Framework 1.0/1.1, antes da disponibilização dos genéricos, e novamente para o .NET Framework 2.0, depois que os genéricos faziam parte do CLR, pois as coleções sem versões fortemente tipadas são apenas um tipo de bobagem. Isso automaticamente significa que uma delas foi obrigada a ser deixada de lado, basicamente, em favor de qualquer aprimoramento ou adição à biblioteca que estava por vir. (O Java driblou esse problema específico, basicamente, "substituindo" as versões não genéricas por versões genéricas, que foi possível apenas devido ao modo como o Java criou genéricos - que não é algo em que vou me aprofundar agora.) E, fora as melhorias que vieram via LINQ no Visual Studio 2008 e no C# 3.0, a biblioteca de coleções nunca foi muito adorada de verdade após a versão 2.0, que por si só, mais ou menos apenas reimplementou as classes System.Collections em um novo namespace (System.Collections.Generic, ou SCG) de versões fortemente tipadas.

Entretanto, o mais importante é que no design das coleções .NET, o foco parece ter sido aproveitar algo prático e útil que fazia parte da versão 1.0, em vez de se tentar pensar profundamente sobre o design das coleções e em como elas podiam ser estendidas. Essa foi uma área em que o .NET Framework realmente (suspeito que de forma não intencional) se emparelhou com o mundo Java. Quando o Java 1.0 foi lançado, ele incluía um conjunto de coleções básico e funcional. Mas elas tinham algumas falhas de design (a mais grave delas era a decisão que tinha a classe Stack, uma coleção "último a entrar, primeiro a sair", que estendia diretamente a classe Vector, que era basicamente uma ArrayList). Depois que o Java 1.1 foi lançado, alguns engenheiros da Sun Microsystems trabalharam arduamente para reescrever as classes de coleções, que se tornaram conhecidas como Coleções Java, e foram lançadas como parte do Java 1.2.

De qualquer forma, o .NET Framework passou por uma renovação de suas classes de coleção, de modo ideal de uma forma que é, pelo menos, na maioria das vezes, compatível com as classes SCG existentes. Felizmente, os pesquisadores na Universidade de TI de Copenhague na Dinamarca geraram um sucessor e complemento honrado para as classes SCG: uma biblioteca que eles chamam de Copenhagen Comprehensive Collection Classes for C#, ou de forma abreviada, C5.

Logística da C5

Para começar, você pode encontrar a C5 na Web, em itu.dk/research/c5, caso queira ver o histórico da versão ou obter um link para um manual (PDF) sobre essa biblioteca; embora o manual esteja algumas versões atrasadas. Ou, como alternativa, a C5 está disponível por meio do NuGet pelo comando Install-Package (onipresente no momento), bastando simplesmente digitar "Install-Package C5". Observe que a C5 foi escrita para estar disponível para o Visual Studio e o Mono, e quando o NuGet instala o pacote, adiciona referências ao assembly C5.dll, bem como ao assembly C5Mono.dll. Eles são redundantes, de modo que você pode excluir aquele que não deseja. Para explorar as coleções C5 por meio de vários testes de exploração, criei um Projeto de Teste do Visual C# e adicionei a C5 a ele. Além disso, a única alteração notável no código são duas instruções "using", que a documentação da C5 também pressupõe:

 

using SCG = System.Collections.Generic; using C5;

A razão para o alias é simples: A C5 "reimplementa" algumas interfaces e classes que têm o mesmo nome na versão SCG, de modo que usar aliases nos itens antigos deixa-os disponíveis para nós, mas com um prefixo curto (IList<T> é a versão da C5, por exemplo, enquanto SCG.IList<T> é a versão "clássica" do SCG).

A propósito, para fins legais, a C5 é um programa de software livre que usa uma licença MIT, de modo que você está muito mais apto a modificar ou aprimorar algumas das classes da C5 do que estaria sob uma GNU GPL (General Public License) ou GNU LGPL (Lesser General Public License).

Visão geral do design da C5

Observando a abordagem de design da C5, ela é semelhante ao estilo do SCG, já que as coleções estão divididas em dois "níveis". uma camada de interface que descreve a interface e o comportamento esperado de uma determinada coleção e uma camada de implementação que fornece o código de apoio real para uma ou mais interfaces desejadas. As classes do SCG se aproximam dessa ideia, mas, em alguns casos, elas não a concretizam tão bem - por exemplo, não temos nenhuma flexibilidade em termos de implementação do SortedSet <T> (o que significa a opção de se basear em matriz, lista vinculada ou hash; cada uma delas têm diferentes características em relação ao desempenho de inserção, passagem, etc). Em alguns casos, as classes do SCG simplesmente não têm determinados tipos de coleção - uma fila circular, por exemplo (na qual, quando o último item na fila é atravessado, a iteração é "redirecionada" para o começo da fila novamente), ou uma simples coleção "recipiente" (que não tem nenhuma funcionalidade, exceto conter itens, evitando, dessa forma, sobrecarga desnecessária de classificação, indexação, etc.).

A verdade é que, para o habitual desenvolvedor do .NET, essa não parecer ser uma grande perda. Mas em muitos aplicativos, à medida que o desempenho começa a se tornar o principal foco, escolher a classe de coleção certa para solucionar o problema à mão se torna mais crítico. Essa é uma coleção que será estabelecida uma vez e atravessada frequentemente? Ou é uma coleção que será adicionada ou removida com frequência, mas raramente atravessada? Se essa coleção estiver no centro de um recurso de aplicativo (ou for o aplicativo em si), a diferença entre essas duas perguntas poderia significar a diferença entre "Uau, esse aplicativo é demais" e "Bem, os usuários gostaram dele, mas o acharam muito lento".

Desse modo, a C5 tem como um de seus princípios básicos que os desenvolvedores devem "codificar para interfaces, e não implementações" e, assim, oferecer mais do que dezenas de interfaces diferentes que descrevem o que a coleção subjacente deve fornecer. A ICollection<T> é a base de todas, garantindo o comportamento da coleção básica, mas nela encontramos IList<T>, IIndexed<T>, ISorted<T> e ISequenced<T>, apenas para começar. Veja a lista completa de interfaces, suas relações com outras interfaces e garantias de modo geral:

  • Uma SCG.IEnumerable<T> pode ter seus itens enumerados. Todos os dicionários e coleções são enumeráveis.
  • Uma IDirectedEnumerable<T> é uma interface enumerável que pode ser revertida, fornecendo uma enumerável regressiva que enumera seus itens na ordem contrária.
  • Uma ICollectionValue<T> é um valor de coleção. Ela não oferece suporte à modificação, é enumerável, sabe quantos itens tem e pode copiá-los em uma matriz.
  • Uma IDirectedCollectionValue<T> é um valor de coleção que pode ser revertido em um valor de coleção regressivo.
  • Uma IExtensible<T> é uma coleção à qual os itens podem ser adicionados.
  • Uma IPriorityQueue<T> é extensível, cujos itens menor e maior podem ser encontrados (e removidos) de modo eficiente.
  • Uma ICollection<T> é extensível, da qual itens também podem ser removidos.
  • Uma ISequenced<T> é uma coleção cujos itens aparecem em uma sequência específica (determinada pela ordem de inserção ou ordenação do item).
  • Uma IIndexed<T> é uma coleção sequenciada cujos itens podem se acessados pelo índice.
  • Uma ISorted<T> é uma coleção sequenciada na qual os itens aparecem na ordem crescente; as comparações de itens determinam a sequência de itens. Ela pode encontrar, eficientemente, o antecessor ou sucessor (na coleção) de um determinado item.
  • Uma IIndexedSorted<T> é uma coleção indexada e classificada. Ela pode, com eficiência, determinar quantos itens são maiores que ou igual a um determinado item x.
  • Uma IPersistentSorted<T> é uma coleção classificada da qual é possível criar um instantâneo de modo eficiente, isto é, uma cópia somente leitura que não será afetada pelas atualizações na coleção original.
  • Uma IQueue<T> é uma fila PEPS (primeiro a entrar, primeiro a sair) que oferece suporte à indexação.
  • Uma IStack<T> é uma pilha UEPS (último a entrar, primeiro a sair).
  • Uma IList<T> é uma coleção indexada e, portanto, sequenciada, onde a ordem dos itens é determinada pelas inserções e exclusões. É derivada de SCG.IList<T>.

Em termos de implementações, a C5 tem várias, incluindo filas circulares; listas com backup em matriz, bem como com backup em lista vinculada, mas também listas de matriz com hash e vinculadas com hash; matrizes encapsuladas; matrizes classificadas; conjuntos e recipientes baseados em árvore, e muito mais.

Estilo da codificação da C5

Felizmente, a C5 não exige uma mudança significativa no estilo de codificação - e, o que é melhor, ela oferece suporte a todas as operações do LINQ (pois ela é criada sobre as interfaces do SCG, das quais os métodos de extensão LINQ se aproveitam), de modo que, em alguns casos, você pode se envolver em uma coleção C5 na hora da construção sem alterar nenhum dos códigos em volta. Veja a Figura 1 para obter um exemplo disso.

Figura 1 Introdução à C5

// These are C5 IList and ArrayList, not SCG IList<String> names = new ArrayList<String>(); names.AddAll(new String[] { "Hoover", "Roosevelt", "Truman",   "Eisenhower", "Kennedy" }); // Print list: Assert.AreEqual("[ 0:Hoover, 1:Roosevelt, 2:Truman, 3:Eisenhower," +   " 4:Kennedy ]", names.ToString()); // Print item 1 ("Roosevelt") in the list Assert.AreEqual("Roosevelt", names[1]); Console.WriteLine(names[1]); // Create a list view comprising post-WW2 presidents IList<String> postWWII = names.View(2, 3); // Print item 2 ("Kennedy") in the view Assert.AreEqual("Kennedy", postWWII[2]); // Enumerate and print the list view in reverse chronological order Assert.AreEqual("{ Kennedy, Eisenhower, Truman }",   postWWII.Backwards().ToString());

Mesmo sem jamais ter lido a documentação da C5, é muito fácil entender o que está acontecendo nesses exemplos.

Implementações da coleção C5 vs. SCG

Essa á apenas a ponta do iceberg em relação à C5. Na minha próxima coluna observarei alguns exemplos práticos de como usar a C5 em vez das implementações da coleção SCG, bem como alguns dos benefícios que temos fazendo isso. Apesar disso, aconselho você a não esperar: use o NuGet, ative o C5 e comece a explorar por sua conta - há muitas coisas com as quais você pode se entreter nesse meio tempo.

Boa codificação!

Ted Neward é um consultor de arquitetura na Neudesic LLC. Ele já escreveu mais de 100 artigos e é autor e coautor de dezenas de livros, incluindo "Professional F# 2.0" (Wrox, 2010). Ele é um MVP de F# e um famoso especialista em Java, além de participar como palestrante sobre o Java e o .NET em conferências no mundo todo. Ele atua como consultor e mentor regularmente. Entre em contato com ele pelo email ted@tedneward.com ou Ted.Neward@neudesic.com se estiver interessado em que ele trabalhe com sua equipe. Ele mantém um blog em blogs.tedneward.com e pode ser seguido no Twitter em twitter.com/tedneward.

Agradecemos ao seguinte especialista técnico pela revisão deste artigo: Immo Landwerth