Os fãs da linguagem C# certamente se encantarão com o Visual C#® 2005. O Visual Studio® 2005 traz uma série de recursos novos e excitantes para o Visual C# 2005, tais como generics, iterators, partial classes e métodos anonymous. Embora os generics sejam o recurso mais comentado e aguardado, especialmente entre os desenvolvedores C++ que estão familiarizados com modelos, os outros novos recursos constituem acréscimos importantes ao seu arsenal de desenvolvimento Microsoft® .NET. Esses recursos e os acréscimos na linguagem aumentarão nossa produtividade geral se comparados à primeira versão do C# e permitem que você escreva um código mais limpo com mais rapidez.
Iteradores
No C# 1.1, você pode iteragir sobre as estruturas de dados, tais como arrays e coleções, usando um loop foreach:
string[] cities = {"New York","Paris","London"};
foreach(string city in cities)
{
Console.WriteLine(city);
}
Na verdade, você pode usar qualquer coleção de dados personalizada no loop foreach, desde que esse tipo de coleção implemente um método GetEnumerator que retorne uma interface IEnumerator. Geralmente, você faz isso implementando a interface IEnumerable:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator
{
object Current{get;}
bool MoveNext();
void Reset();
}
Freqüentemente, a classe que é usada para fazer a iteração sobre uma coleção por meio da implementação de IEnumerable é fornecida como uma classe aninhada do mesmo tipo da coleção a ser iterada. Esse tipo de iterador mantém o estado da iteração. Uma classe aninhada é sempre melhor como um enumerador porque ela tem acesso a todos os membros privados da classe à qual pertence. Este é o design pattern do Iterator que protege a iteração dos clientes dos detalhes da implementação real da estrutura de dados, permitindo o uso da mesma lógica de iteração no lado cliente sobre múltiplas estruturas de dados, conforme mostrado na Figura 1.
Além disso, como cada iterador mantém um estado de interação separado, é possível haver vários clientes executando iterações simultâneas em separado. Estruturas de dados com Arrays e Queue suportam iteração prontas para seu uso através da implementação do Ienumerable. O código gerado no loop foreach obtém um objeto IEnumerator através de uma chamada ao método GetEnumerator da classe e o utiliza em um loop while para iteragir sobre a coleção, por meio de chamadas contínuas ao seu método MoveNext e à sua propriedade atual. Você pode usar o IEnumerator diretamente (sem recorrer a uma instrução foreach) se precisar de iteração explícita sobre a coleção.
No entanto, essa abordagem apresenta alguns problemas. O primeiro deles é que se a coleção contém tipos de valor, obter os itens requer boxing e unboxing desses tipos porque IEnumerator.Current retorna um Object. Isso resulta em uma possível degradação de desempenho e no aumento de pressão no managed heap. Mesmo que a coleção contenha tipos de referência, você ainda tem o ônus do down-casting do Object. Embora seja estranho para a maioria dos desenvolvedores, no C# 1.0 você pode realmente implementar o modelo do iterador para cada loop sem implementar o IEnumerator ou o IEnumerable. O compilador escolherá chamar a versão strongly typed, evitando o casting e o boxing. O resultado é que mesmo na versão 1.0, é possível não ter o ônus do prejuízo do desempenho.
Para formular melhor essa solução e facilitar a implementação, o Microsoft .NET Framework 2.0 define as interfaces type-safe IEnumerable e IEnumerator genéricas, no namespace System.Collections.Generics:
public interface IEnumerable
{
IEnumerator GetEnumerator();
}
public interface IEnumerator : IDisposable
{
ItemType Current{get;}
bool MoveNext();
}
Além de fazer uso dos generics, as novas interfaces são um pouco diferentes de suas predecessoras. Diferentemente de IEnumerator, IEnumerator deriva de IDisposable e não tem nenhum método Reset. O código na Listagem 1 mostra uma coleção city simples implementando IEnumerable, e a Listagem 2 mostra como o compilador usa essa interface ao estender o código do loop foreach. A implementação na Listagem 1 use uma classe aninhada denominada MyEnumerator, que aceita como parâmetro de construção uma referência à coleção a ser enumerada. MyEnumerator está intimamente a par dos detalhes de implementação da coleção city (neste exemplo, um array). A classe MyEnumerator mantém o estado de iteração atual na variável membro m_Current, que é usada como um índice para um array.
public class CityCollection : IEnumerable
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerator GetEnumerator()
{
return new MyEnumerator(this);
}
//Definição de classe aninhada
class MyEnumerator : IEnumerator
{
CityCollection m_Collection;
int m_Current;
public MyEnumerator(CityCollection collection)
{
m_Collection = collection;
m_Current = -1;
}
public bool MoveNext()
{
m_Current++;
if(m_Current < m_Collection.m_Cities.Length)
return true;
else
return false;
}
public string Current
{
get
{
if(m_Current == -1)
throw new InvalidOperationException();
return m_Collection.m_Cities[m_Current];
}
}
public void Dispose(){}
}
}
CityCollection cities = new CityCollection();
//Para este loop foreach:
foreach(string city in cities)
{
Trace.WriteLine(city);
}
//O compiler gera este código equivalente:
IEnumerable enumerable = cities;
IEnumerator enumerator = enumerable.GetEnumerator();
using(enumerator)
{
while(enumerator.MoveNext())
{
Trace.WriteLine(enumerator.Current);
}
}
O segundo problema (e também o mais difícil) consiste na implementação do iterador. Embora essa implementação seja objetiva para os casos simples (conforme mostrado na Listagem 2), ela se torna um desafio nas estruturas de dados mais avançadas, tais como árvores binárias, que requerem cruzamento recursivo e manutenção de estado de iteração através da recursão. Além disso, se você necessitar de várias opções de iteração, tais como head-to-tail e tail-to-head em uma lista vinculada, o código da lista vinculada ficará inchado com várias implementações de iterador. Os iteradores do C# 2.0 foram projetados para solucionar exatamente esses tipos de problema. Com os iteradores, você pode fazer com que o compilador C# gere a implementação do IEnumerator por você. O compilador C# pode gerar automaticamente uma classe aninhada para manter o estado de iteração. Você pode usar os iteradores em uma coleção genérica ou em uma coleção específica a um tipo. Tudo que você precisa fazer é dizer ao compilador o que produzir em cada iteração. Assim como acontece quando se fornece manualmente um iterador, você precisa expor um método GetEnumerator, geralmente implementando IEnumerable ou IEnumerable.
Para dizer ao compilador o que gerar, você usa a nova instrução yield return do C#. Por exemplo, veja aqui como usar os iteradores C# na coleção city no lugar da implementação manual da Listagem 1:
public class CityCollection : IEnumerable
{
string[] m_Cities = {"New York","Paris","London"};
public IEnumerator GetEnumerator()
{
for(int i = 0; i