Generics em C#: para que servem e como usar
Tipos genéricos (Generics) são um dos recursos principais da linguagem C#, que permite criar classes, métodos e interfaces com tipos parametrizados. Em vez de escrever o mesmo código para cada tipo de dado, você o escreve uma vez e especifica o tipo ao usar. Isso melhora o desempenho, a segurança de tipos e a reutilização de código. Neste artigo, vamos analisar para que servem os Generics, como aplicá-los na prática e ver exemplos reais do .NET.
Para que servem os Generics em C#
Antes do surgimento dos Generics em C# (versão 2.0), os desenvolvedores frequentemente usavam o tipo object e a coleção ArrayList. Isso gerava dois problemas:
- Perda de desempenho — boxing e unboxing para tipos por valor (int, double, struct).
- Falta de verificação de tipos em tempo de compilação — você pode acidentalmente colocar uma string em uma lista de números e obter um erro apenas em tempo de execução.
Os Generics resolvem ambos os problemas. Eles permitem criar estruturas de dados type-safe sem custos adicionais desnecessários. Por exemplo, List<int> trabalha com int sem boxing, e o compilador não permitirá adicionar uma string ali.
Conceitos principais dos Generics
Classes genéricas
Uma classe genérica é declarada com um parâmetro de tipo entre colchetes angulares. O parâmetro de tipo é um placeholder que é substituído pelo tipo real ao criar uma instância.
public class Box<T>{ private T _value;
public Box(T value) { _value = value; }
public T GetValue() => _value;}Uso:
var intBox = new Box<int>(42);var stringBox = new Box<string>("Hello");
Console.WriteLine(intBox.GetValue()); // 42Console.WriteLine(stringBox.GetValue());// HelloMétodos genéricos
Um método pode ter seu próprio parâmetro de tipo, mesmo que a classe não seja genérica:
public class Utils{ public static void Swap<T>(ref T a, ref T b) { T temp = a; a = b; b = temp; }}
// Usoint x = 1, y = 2;Utils.Swap(ref x, ref y);Console.WriteLine($"{x} {y}"); // 2 1Interfaces genéricas
Interfaces com parâmetro de tipo permitem criar abstrações flexíveis:
public interface IRepository<T>{ T GetById(int id); void Add(T entity);}
public class UserRepository : IRepository<User>{ public User GetById(int id) => new User(); public void Add(User user) { }}Restrições (Constraints) em Generics
Às vezes é necessário impor restrições sobre os tipos que podem ser usados. Para isso, usa-se a palavra-chave where.
Principais tipos de restrições
where T : class— apenas tipos por referência (string, classes).where T : struct— apenas tipos por valor (int, DateTime, struct).where T : new()— o tipo deve ter um construtor público sem parâmetros.where T : SomeBaseClass— o tipo deve ser ou herdar da classe especificada.where T : ISomeInterface— o tipo deve implementar a interface.
Exemplo com várias restrições:
public class Repository<T> where T : class, IEntity, new(){ public T Create() { return new T(); // permitido graças a new() }}Covariância e Contravariância em Generics
A partir do C# 4.0, tornou-se possível gerenciar a variância de interfaces genéricas usando as palavras-chave in e out.
- Covariância (out) — permite usar