sexta-feira, março 11, 2016

Usando yield para encurtar o código

Esses dias estava no trabalho conversando com alguns colegas sobre um artigo que citava as perguntas mais frequentes em entrevistas para analista de sistemas/desenvolvedor/programador no Google, Microsoft, IBM, Yahoo, etc...  nas gigantes de tecnologia.
E uma das questões era como fazer a troca de valores entre duas variáveis numéricas sem o uso de uma variável auxiliar.

Hoje lendo meus e-mails passei pela chamada de um novo post em um grupo que participo no LinkedIn sobre desenvolvimento na plataforma .NET, e deste post me inspirei a fazer este (nada se cria...) e mais ainda, a colocar uma questão em nossa prova de seleção para analista de sistemas no estilo “não use uma variável/coleção auxiliar”.

Pense na classe abaixo:

1:  using System;  
2:  using System.Text;  
3:  namespace com.jeanjmichel.blogspot.testyield  
4:  {  
5:    public class Person  
6:    {  
7:      private int age;  
8:      private char gender;  
9:      private String name;  
10:      public int Age {set; get;}  
11:      public char Gender  
12:      {  
13:        get { return this.gender; }  
14:      }  
15:      public String Name { set; get; }  
16:      public Person(int age, char gender, String name)  
17:      {  
18:        this.age = age;  
19:        this.gender = gender;  
20:        this.name = name;  
21:      }  
22:      override public String ToString()  
23:      {  
24:        StringBuilder sb = new StringBuilder();  
25:        sb.Append(this.name);  
26:        sb.Append(" ");  
27:        sb.Append(this.age);  
28:        sb.Append(" years old");  
29:        sb.Append(this.gender == 'M' ? " man" : " woman");  
30:        return sb.ToString();  
31:      }  
32:    }  
33:  }  

Agora vamos usar a uma classe que utiliza ela:

1:  using System;  
2:  using System.Collections.Generic;  
3:  namespace com.jeanjmichel.blogspot.testyield  
4:  {  
5:    public class Program  
6:    {  
7:      private static List<Person> persons = new List<Person>();  
8:      static void PopulatePersonsList()  
9:      {  
10:        persons.Add(new Person(34, 'M', "Michel, Jean J."));  
11:        persons.Add(new Person(0, 'F', "Michel, Helena M."));  
12:        persons.Add(new Person(60, 'M', "Doe, John"));  
13:        persons.Add(new Person(47, 'F', "Doe, Jane"));  
14:        persons.Add(new Person(41, 'M', "Smith, John"));  
15:      }  
16:      static void Main(string[] args)  
17:      {  
18:        PopulatePersonsList();  
19:        foreach (Person p in persons)  
20:          Console.WriteLine(p);  
21:        Console.ReadKey();  
22:      }  
23:    }  
24:  }  

A saída da execução da classe é:

1:  Michel, Jean J. 34 years old man  
2:  Michel, Helena M. 0 years old woman  
3:  Doe, John 60 years old man  
4:  Doe, Jane 47 years old woman  
5:  Smith, John 41 years old man  

Se quisermos criar um método que retorne uma lista com apenas as pessoas do sexo masculino ou feminino para fazermos algo, como exibir a lista? Claro que não podemos percorrer a lista original excluindo as pessoas do sexo oposto ao pesquisado, então a solução seria criar uma lista auxiliar?

Podemos encurtar esse caminho executando exatamente o desejado, filtrar a lista pelo atributo gender da classe Person, porém sem declarar uma nova lista ou alterar a lista original, dando a impressão de que estamos fazendo o desejado “sem usar uma variável auxiliar”.

Veja isso:

1:      static IEnumerable<Person> FilterPersonsListByGender(char gender)  
2:      {  
3:        foreach (Person p in persons)  
4:          if (p.Gender == gender) yield return p;  
5:      }  

O que faz a palavra reservada yield é retornar quando a expressão for true. Da para usar com break também e parar um laço, por exemplo. Mais detalhes direto na fonte (MSDN): https://msdn.microsoft.com/pt-br/library/9k7k7cf0.aspx.

Assim ficaria a classe final:

1:  using System;  
2:  using System.Collections.Generic;  
3:  namespace com.jeanjmichel.blogspot.testyield  
4:  {  
5:    public class Program  
6:    {  
7:      private static List<Person> persons = new List<Person>();  
8:      static void PopulatePersonsList()  
9:      {  
10:        persons.Add(new Person(34, 'M', "Michel, Jean J."));  
11:        persons.Add(new Person(0, 'F', "Michel, Helena M."));  
12:        persons.Add(new Person(60, 'M', "Doe, John"));  
13:        persons.Add(new Person(47, 'F', "Doe, Jane"));  
14:        persons.Add(new Person(41, 'M', "Smith, John"));  
15:      }  
16:      static IEnumerable<Person> FilterPersonsListByGender(char gender)  
17:      {  
18:        foreach (Person p in persons)  
19:          if (p.Gender == gender) yield return p;  
20:      }  
21:      static void Main(string[] args)  
22:      {  
23:        PopulatePersonsList();  
24:        foreach(Person p in FilterPersonsListByGender('F'))  
25:          Console.WriteLine(p);  
26:        Console.ReadKey();  
27:      }  
28:    }  
29:  }  

E essa é a saída agora que temos:

1:  Michel, Helena M. 0 years old woman  
2:  Doe, Jane 47 years old woman  

Em nenhum momento você verá ser criada uma nova lista :)

Achei legal, resolvi postar, agora é elaborar uma questãozinha legal para a prova e ver se a galera vai conhecer esse o yield ;)