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:
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.
Comments
Post a Comment