O Generics do .NET Framework implementaram a possibilidade de parâmetros receberem tipos desconhecidos até o momento da execução do método. Os métodos que tratam tipos genéricos desmistificam suas características apenas no momento em que ele estiver sendo utilizado. Além de métodos, também é possível a geração de classes complexas e totalmente genéricas, permitindo escrever códigos que atendam necessidades comuns em um projeto, sem perder a confiabilidade em conversões, boxing ou qualquer outra necessidade fundamental.

Geralmente os tipos genéricos estão identificados com a letra T, em maiúsculo, mas é uma letra convencionalmente utilizada em teste e não obrigatoriamente é necessário utilizá-la.

Utilizando tipos genéricos no .NET Framework

A declaração de um tipo genérico .NET Framework é simples. Como o exemplo a seguir:

Listagem 1: Declarando método genérico


    public void MetodoGenerico<T>(T input)
    {
    }
     

Com o identificado <T>, definimos o tipo que será incluído nesse método. Ao declarar o valor para T na chamada do método, o parâmetro de entrada assumrá automaticamente o mesmo tipo. Ele não precisa obrigatoriamente ser preenchido.

Listagem 2: Chamando Método Genérico no .NET Framework

 
    public void ChamandoMetodoGenerico()
    {
      var program = new Program();
      var inteiro = 2;
      float flutuante = 2.3F;
    
      MetodoGenerico(program);
      MetodoGenerico(inteiro);
      MetodoGenerico<float>(flutuante);
    } 
    

Simples assim, a declaração de classes não tem muitas diferenças na sua declaração, utilizamos a mesma concepção.

Listagem 3: Classe Genérica no .NET Framework

 
        public class Generics<T>
            {
              public Generics()
              {
              }
            
              public Generics(T input)
              {
                var prop = input;
              }
            }
         

Então, para utilizar uma classe que utiliza genérics, pode-se proceder da seguinte forma:

Listagem 4: Chamando uma classe genérica

 
            public void ChamandoClasseGenerica()
{
  Generics<Program> objProgram = new Generics<Program>();
  Generics<int> objInt = new Generics<int>(10);
}
            

Repare que agora o valor para <T> é obrigatório, pois é preciso informar qual será o tipo do objeto instanciado.

As interfaces de classes também podem ser genéricas, a declaração é idêntica à das classes.

A declaração de classes genéricas pode ser feita de forma restritiva, permitindo assim que os tipos que a classe poderá receber durante a sua instância sejam controlados.

Utilizando o operador where é possível realizar esse controle, definindo regras para a declaração do tipo genérico T. A abaixo vemos um exemplo básico de uso do where, onde foi definido que o tipo T deverá, obrigatoriamente, ser uma classe.

Listagem 5: Receber apenas classes

 
    public class Generics<T> where T : class
        {
                public Generics()
                {
                }
        
          public Generics(T input)
          {
            var prop = input;
          }
        }
    

Da mesma forma, por exemplo, é possível estabelecer que uma classe genérica só receba o tipo struct para o argumento T. Existe também a possibilidade de definir que o tipo T tem de herdar de uma interface, como a IDisposable, por exemplo. Assim, quando eu declarar um tipo para a classe, o interpretador irá verificar se o mesmo combina com as opções definidas pela classe genérica.

Listagem 6: Classe genérica com tipos e interfaces


    public class Generics<T> where T : class, IDisposable, IObserver<T>, new()
        {
          public Generics()
          {
          }
        
          public Generics(T input)
          {
            var prop = input;
          }
        }    
}

A classe genérica não é limitada a receber apenas um tipo, um exemplo é a classe Dictionary do framework. Essa classe possibilita receber dois tipos e os mesmos são armazenados em um array de duas posições.

A declaração para esse tipo é feita da seguinte forma:

Listagem 7: Classe genérica com dois parâmetros

 
    public class Generics<T, I>
{
  public Generics()
  {
  }

  public Generics(T input)
  {
    var prop = input;
  }

  public Generics(T input, I input2)
  {
    var prop = input;
    var prop2 = input2;
  }
}
    

Os conceitos de definição de regras par ao tipo T, vistos anteriormente, podem também serem aplicados agora. Por exemplo, para cada tipo de entrada informado para a classe pode ser utilizado o operador where, como podemos ver abaixo.

Listagem 8: Classe com dois parâmetros e operadores

 
    public class Generics<T, I>
  where T : System.NullReferenceException, IConvertible
  where I : struct, ICloneable
{
  public Generics()
  {
  }

  public Generics(T input)
  {
    var prop = input;
  }

  public Generics(T input, I input2)
  {
    var prop = input;
    var prop2 = input2;
  }
}
    

Codificar

Abaixo temo um exemplo de código que realiza a conversão de tipos, utilizando método de extensão. Nele é capturado o tipo definido para o argumento T e o tipo da variável que está sendo passada.

Listagem 9: Converter Genérico

 
    public static T To<T>(this IConvertible obj)
        {
          try
          {
            Type TypeReturn = typeof(T);
            if (TypeReturn.IsGenericType && (TypeReturn.GetGenericTypeDefinition() == typeof(Nullable<>)))
            {
              if (obj == null || (string)obj == string.Empty)
              {
                return (T)(object)null;
              }
              return (T)Convert.ChangeType(obj, Nullable.GetUnderlyingType(TypeReturn));
            }
            return (T)Convert.ChangeType(obj, TypeReturn);
          }
          catch
          {
            return default(T);
          }
        }
    

Ele captura o valor para conversão pelo método de extensão e o T é o tipo para o qual ele deseja que o valor seja convertido. Existe uma validação para os tipos Nullable, caso se deseje converter um True ou False para o tipo Boolean?, por exemplo, também é possível.

Conclusão

Os tipos genéricos têm várias vantagens e utilizações. A reutilização de código é o que mais é chamativo. Permite que funções que seriam repetitivas, por causa dos tipos que seriam passados para elas, se tornem o menor dos problemas.

Essa prática exige certo cuidado, pois o compilador só apontará o erro no momento que o código estiver sendo executado, então é importante tomar cuidado com alguns convertes que não sejam de tipos seguros.