C# - Usando yield return


  Neste artigo vamos recordar para que serve e como usar a palavra chave yield e yield return na linguagem C#.

A palavra-chave "yield" é usada em um método para criar um iterador. Um iterador permite que você percorra uma coleção de itens de forma conveniente, fornecendo um item por vez, conforme necessário, em vez de carregar todos os itens na memória de uma vez.

O "yield" é usado em conjunto com a declaração "yield return" para retornar um valor da sequência sendo iterada. Quando o método encontra a declaração "yield return", ele retorna o valor especificado e pausa a execução do método até que o próximo valor seja solicitado. Isso permite que a iteração ocorra sob demanda, economizando recursos de memória.

A seguir temos um exemplo básico do uso de yield em um projeto Console criado no VS 2022 Community usando o C# 11 e o recurso Top Level Stataments:

Console.WriteLine("\nUsando yield return\n");

foreach (var numero in GetNumeros())
{
   Console.WriteLine(numero);
}

Console.ReadKey();

static IEnumerable<int> GetNumeros()
{
  
yield return 1;
  
yield return 2;
  
yield return 3;
}

Neste exemplo, o método GetNumeros retorna um iterador de IEnumerable<int>. A cada chamada de yield return, um número é retornado da sequência. O método Main usa um loop foreach para iterar sobre os números retornados e imprimir cada um deles.

Quando o método GetNumeros é chamado, ele não retorna imediatamente todos os números de uma vez. Em vez disso, ele retorna um número de cada vez, pausando a execução após cada yield return até que o próximo número seja solicitado, e, isso permite que você itere sobre a sequência de números sem carregar todos eles na memória ao mesmo tempo.

Assim, a palavra-chave "yield" é uma ferramenta poderosa para lidar com coleções grandes ou sequências infinitas de dados, permitindo que você itere sobre eles de forma eficiente e sob demanda.

Como o yield funciona ?

A palavra-chave "yield" é usada em conjunto com a declaração "yield return" para criar um iterador e o método que usa "yield" e "yield return" deve retornar um tipo de dados que implementa a interface IEnumerable ou IEnumerable<T>.

Quando o método é chamado, ele não é executado imediatamente até o final. Em vez disso, a execução do método é pausada no ponto onde ocorre a declaração "yield return" e um valor é retornado. Esse valor é retornado como parte da sequência que está sendo iterada.

Quando o iterador é percorrido, seja com um loop foreach ou manualmente usando o método GetEnumerator() e MoveNext(), o método é executado até encontrar a próxima declaração "yield return". Nesse ponto, o valor especificado é retornado e a execução do método é pausada novamente.

Esse processo de pausar e continuar a execução do método ocorre até que não haja mais declarações "yield return" para serem encontradas. Quando o método atinge o final ou encontra uma declaração "yield break", a iteração é finalizada.

A palavra-chave "yield" é uma forma conveniente de criar um iterador em C#, permitindo que você crie sequências de dados de forma eficiente e com uso mínimo de memória, pois os valores são retornados sob demanda, à medida que são solicitados. Isso é especialmente útil para lidar com grandes conjuntos de dados ou sequências infinitas.

Para usar a palavra-chave "yield",  existem alguns requisitos a serem atendidos:

  1. O método que usa "yield" deve retornar um tipo de dados que implementa a interface IEnumerable ou IEnumerable<T>. Essas interfaces definem a capacidade de iterar sobre uma sequência de elementos.
  2. O método que usa "yield" não pode ser marcado como async, pois o "yield" e o async são incompatíveis. Você precisa escolher entre usar "yield" ou tornar o método assíncrono, mas não pode usar ambos juntos. (A partir do C# 8.0 podemos usar yield com async)
  3. "yield" só pode ser usada em métodos, não em propriedades ou eventos. Portanto, é necessário criar um método separado que retorna um iterador.
  4. "yield" pode ser usada em qualquer bloco de código dentro do método, permitindo que você tenha várias declarações "yield return" em locais diferentes para retornar diferentes valores da sequência.
  5. É possível usar a palavra-chave "yield break" para terminar a iteração antes do final do método. Quando "yield break" é encontrado, a iteração é finalizada e o controle é retornado ao código que está chamando o iterador.

Exemplo prático

Como exemplo prático que ilustra o uso de yield vamos criar um iterador que retorna apenas os arquivos com uma determinada extensão em um diretório específico:
 

Console.WriteLine("\nObtendo os arquivos de uma pasta\n");

Console.WriteLine("Informe o caminho da pasta: (Ex: c:\\dados ");
var
caminho = Console.ReadLine();

Console.WriteLine("Informe a extensão dos arquivos (Ex: .txt ");
var
extensao = Console.ReadLine();

Console.WriteLine("\nArquivos encontrados\n");
foreach
(var file in GetArquivosComExtensao(@caminho, extensao))
{
   Console.WriteLine(file);
}

Console.ReadKey();

static IEnumerable<string> GetArquivosComExtensao(string directoryPath, string fileExtension)
{
  
foreach (var file in Directory.GetFiles(directoryPath))
   {
     
if (Path.GetExtension(file).Equals(fileExtension, StringComparison.OrdinalIgnoreCase))
      {
         
yield return file;
      }
   }
}

Executando o projeto iremos obter:

Neste exemplo, o método GetArquivosComExtensao retorna um iterador de IEnumerable<string>. Ele recebe dois parâmetros: caminho, que é o caminho para o diretório que queremos pesquisar, e extensao, que é a extensão dos arquivos que queremos encontrar.

Dentro do método, usamos o método Directory.GetFiles para obter uma lista de todos os arquivos no diretório especificado. Em seguida, percorremos cada arquivo retornado e verificamos se a extensão do arquivo corresponde à extensão desejada. Se corresponder, usamos a declaração "yield return" para retornar o caminho completo do arquivo.

A seguir definimos o diretório caminho e a extensão extensao  para buscar arquivos no diretório especificado com a extensão ".txt". Em seguida, usamos um loop foreach para percorrer os arquivos retornados pelo iterador GetArquivosComExtensao e imprimi-los no console.

Esse exemplo demonstra como usar o "yield" para lidar com a iteração sobre uma grande quantidade de arquivos em um diretório, retornando apenas os arquivos desejados conforme necessário, em vez de carregar todos eles na memória de uma vez.

A seguir temos as vantagens e desvantagens do yield:

Vantagens:

  1. Eficiência de memória: O uso de "yield" permite que você itere sobre uma sequência de itens sem precisar carregar todos eles na memória de uma vez. Os valores são retornados sob demanda, economizando recursos de memória, especialmente quando se lida com grandes conjuntos de dados ou sequências infinitas.
  2. Iteração sob demanda: O "yield" permite a iteração sob demanda, onde os valores são retornados conforme são solicitados. Isso é útil quando você só precisa de um subconjunto dos itens da sequência, evitando a necessidade de processar todos os itens de uma vez.
  3. Simplificação do código: O uso de "yield" pode simplificar o código, eliminando a necessidade de criar uma lista temporária ou uma estrutura de dados auxiliar para armazenar todos os itens da sequência antes de retorná-los. Isso torna o código mais conciso e legível.

Desvantagens:

  1. Limitado a métodos enumeráveis: A palavra-chave "yield" só pode ser usada em métodos que retornam tipos que implementam IEnumerable ou IEnumerable<T>. Isso significa que o uso de "yield" é limitado a cenários em que você está lidando com coleções iteráveis.
  2. Limitações de funcionalidade: O uso de "yield" tem algumas limitações em relação a manipulações mais avançadas de coleções, como ordenação, filtragem ou transformação de elementos. Você precisa implementar essa lógica separadamente, antes de usar "yield" para retornar os elementos.

E estamos conversados.

"Deus nos ressuscitou com Cristo e com ele nos fez assentar nos lugares celestiais em Cristo Jesus, para mostrar, nas eras que hão de vir, a incomparável riqueza de sua graça, demonstrada em sua bondade para conosco em Cristo Jesus. "
Efésios 2:6-7

Referências:


José Carlos Macoratti