Hoje vim para falar de uma das features mais úteis do C# 2.0, mesmo que muitas pessoas não saibam do que se trata. Vamos falar de iterators!
O que é um iterator?
Para quem ainda não sabe o que é o padrão iterator, podem ler a sua descrição aqui. Um iterator é uma classe/objecto que sabe como percorrer uma estrutura. Portanto, se tivermos uma lista de objectos, um iterator teria o conhecimento de como percorrer essa lista e aceder cada elemento da lista. O iterator é um design pattern bastante conhecido e está por trás de muitas coisas fantásticas que temos hoje no .NET. (assim de repente lembro-me de LINQ). A Microsoft definiu duas interfaces para este design pattern chamadas IEnumerable e IEnumerator. A primeira é implementada no objecto que vamos percorrer, a segunda é no objecto que irá percorrer o primeiro.
Porque é que é uma feature?
Diga-se de passagem, os iterators são um conceito bem conhecido mesmo antes do .NET existir. Sendo um design pattern de programação orientada a objectos faz com que possa ser utilizado em qualquer linguagem deste paradigma. A classe iterator tipicamente tem alguns métodos, por convenção. Para implementar o iterator no .NET 1.0 teríamos que implementar a interface IEnumerator. Depois, na classe que precisa de ser percorrida, teríamos de implementar a interface IEnumerable que tem um método que devolve uma instância do iterator. Será mais fácil vermos o seguinte exemplo:
No exemplo acima podemos ver que a classe Caixa implementa a interface IEnumerable e o seu método GetEnumerator devolve uma instância da classe IteratorCaixa. IteratorCaixa implementa a interface IEnumerator e recebe a variável itens no construtor. Esta é a classe que sabe como percorrer os itens de uma caixa, e desta forma podemos devolver quantos iterators quisermos através do método GetEnumerator. Por outro lado, também nos permite fazer isto:
Reparem como podemos utilizar a instância de Caixa no foreach. Internamente será chamado o método MoveNext da classe IteratorCaixa(devolvido pelo GetEnumerator), convertida a propriedade Current para string e atribuída à variável item em cada passagem. Obviamente que isto nos irá mostrar os itens da nossa caixa.
Portanto, onde está a melhoria? Já tínhamos isto antes!
A resposta está na palavra reservada yield. Agora podemos construir um iterator sem implementar a interface IEnumerator! Vejamos o código abaixo:
O snippet acima faz exactamente a mesma coisa que o primeiro, mas vejam como não foi necessário implementar a interface IEnumerator para ter o mesmo resultado. Isto representa uma poupança de tempo enorme em comparação com o código do .Net 1.0. Mas qual é exactamente o papel do yield? Reparem que de cada vez que o método GetEnumerator é executado, yield grava o estado da execução do método e vai executar o código a partir de onde ficou gravado na próxima chamada ao método. Podem tentar com o debugger e verão que a execução do método é pausada ao chegarmos à palavra yield. Podemos implementar múltiplos métodos que iterators e depois utilizá-los explicitamente no foreach.
Para sair do ciclo, podemos utilizar a palavra yield break no método GetEnumerator e o ciclo acabará ali. Um iterator pode ser utilizado no getter de uma propriedade e terá os mesmo efeitos. O tipo que um método iterator devolve tem que ser IEnumerable, IEnumerator, IEnumerable(T) ou IEnumerator(T). Poderia ter utilizado as interfaces genéricas no post, mas achei melhor não misturar generics e iterators para não confundir o leitor.
Quando precisamos de percorrer uma estrutura de uma forma diferente, é sempre boa ideia implementar um iterator. Eu nunca precisei de fazer isto porque a grande parte das estruturas que utilizo já implementam o IEnumerable, e para algo mais complexo posso utilizar LINQ, mas no C# 2.0 não tínhamos ainda esta evolução e havia mais espaço para iterators.
Este é um tópico um pouco confuso e é fácil perder o fio à meada. Caso hajam dúvidas sobre o assunto deixem comentário, porque eu próprio me lembro de ter alguns problemas em compreender esta matéria no início.
No próximo post irei traduzir o artigo sobre Classes Parciais no Run or Debug.
O que é um iterator?
Para quem ainda não sabe o que é o padrão iterator, podem ler a sua descrição aqui. Um iterator é uma classe/objecto que sabe como percorrer uma estrutura. Portanto, se tivermos uma lista de objectos, um iterator teria o conhecimento de como percorrer essa lista e aceder cada elemento da lista. O iterator é um design pattern bastante conhecido e está por trás de muitas coisas fantásticas que temos hoje no .NET. (assim de repente lembro-me de LINQ). A Microsoft definiu duas interfaces para este design pattern chamadas IEnumerable e IEnumerator. A primeira é implementada no objecto que vamos percorrer, a segunda é no objecto que irá percorrer o primeiro.
Porque é que é uma feature?
Diga-se de passagem, os iterators são um conceito bem conhecido mesmo antes do .NET existir. Sendo um design pattern de programação orientada a objectos faz com que possa ser utilizado em qualquer linguagem deste paradigma. A classe iterator tipicamente tem alguns métodos, por convenção. Para implementar o iterator no .NET 1.0 teríamos que implementar a interface IEnumerator. Depois, na classe que precisa de ser percorrida, teríamos de implementar a interface IEnumerable que tem um método que devolve uma instância do iterator. Será mais fácil vermos o seguinte exemplo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Caixa : IEnumerable | |
{ | |
string[] itens; | |
public Caixa() | |
{ | |
itens = new string[5]; | |
itens[0] = "Roupa"; | |
itens[1] = "Louça"; | |
itens[2] = "Brinquedos"; | |
itens[3] = "Ferramentas"; | |
itens[4] = "Pilhas"; | |
} | |
public IEnumerator GetEnumerator() | |
{ | |
return new IteratorCaixa(itens); | |
} | |
} | |
public class IteratorCaixa : IEnumerator | |
{ | |
int PosicaoActual = -1; | |
string[] itens = null; | |
public IteratorCaixa(string[] itens) | |
{ | |
this.itens = itens; | |
} | |
public bool MoveNext() //Move o iterator para o próximo item | |
{ | |
PosicaoActual++; | |
return (PosicaoActual < itens.Length); | |
} | |
public void Reset() //Reinicia a posição do iterator. | |
{ | |
PosicaoActual = -1; | |
} | |
public object Current //Devolve o objecto actual. | |
{ | |
get | |
{ | |
return itens[PosicaoActual]; | |
} | |
} | |
} |
No exemplo acima podemos ver que a classe Caixa implementa a interface IEnumerable e o seu método GetEnumerator devolve uma instância da classe IteratorCaixa. IteratorCaixa implementa a interface IEnumerator e recebe a variável itens no construtor. Esta é a classe que sabe como percorrer os itens de uma caixa, e desta forma podemos devolver quantos iterators quisermos através do método GetEnumerator. Por outro lado, também nos permite fazer isto:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Caixa c = new Caixa(); | |
foreach (string item in c) | |
{ | |
Console.WriteLine(item); | |
} |
Reparem como podemos utilizar a instância de Caixa no foreach. Internamente será chamado o método MoveNext da classe IteratorCaixa(devolvido pelo GetEnumerator), convertida a propriedade Current para string e atribuída à variável item em cada passagem. Obviamente que isto nos irá mostrar os itens da nossa caixa.
Portanto, onde está a melhoria? Já tínhamos isto antes!
A resposta está na palavra reservada yield. Agora podemos construir um iterator sem implementar a interface IEnumerator! Vejamos o código abaixo:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Caixa : IEnumerable | |
{ | |
string[] itens; | |
public Caixa() | |
{ | |
itens = new string[5]; | |
itens[0] = "Roupas"; | |
itens[1] = "Louça"; | |
itens[2] = "Brinquedos"; | |
itens[3] = "Ferramentas"; | |
itens[4] = "Pilhas"; | |
} | |
public IEnumerator GetEnumerator() | |
{ | |
foreach (string item in itens) | |
yield return item; | |
} | |
} |
O snippet acima faz exactamente a mesma coisa que o primeiro, mas vejam como não foi necessário implementar a interface IEnumerator para ter o mesmo resultado. Isto representa uma poupança de tempo enorme em comparação com o código do .Net 1.0. Mas qual é exactamente o papel do yield? Reparem que de cada vez que o método GetEnumerator é executado, yield grava o estado da execução do método e vai executar o código a partir de onde ficou gravado na próxima chamada ao método. Podem tentar com o debugger e verão que a execução do método é pausada ao chegarmos à palavra yield. Podemos implementar múltiplos métodos que iterators e depois utilizá-los explicitamente no foreach.
Para sair do ciclo, podemos utilizar a palavra yield break no método GetEnumerator e o ciclo acabará ali. Um iterator pode ser utilizado no getter de uma propriedade e terá os mesmo efeitos. O tipo que um método iterator devolve tem que ser IEnumerable, IEnumerator, IEnumerable(T) ou IEnumerator(T). Poderia ter utilizado as interfaces genéricas no post, mas achei melhor não misturar generics e iterators para não confundir o leitor.
Quando precisamos de percorrer uma estrutura de uma forma diferente, é sempre boa ideia implementar um iterator. Eu nunca precisei de fazer isto porque a grande parte das estruturas que utilizo já implementam o IEnumerable, e para algo mais complexo posso utilizar LINQ, mas no C# 2.0 não tínhamos ainda esta evolução e havia mais espaço para iterators.
Este é um tópico um pouco confuso e é fácil perder o fio à meada. Caso hajam dúvidas sobre o assunto deixem comentário, porque eu próprio me lembro de ter alguns problemas em compreender esta matéria no início.
No próximo post irei traduzir o artigo sobre Classes Parciais no Run or Debug.
Comments
Post a Comment