Generics em C#: para que servem e como usar na prática
Generics (generalizações) — um dos recursos-chave da linguagem C#, que permite escrever código flexível, reutilizável e type-safe. Neste artigo, vamos explorar para que servem as generalizações, como funcionam internamente e mostrar exemplos práticos de sua aplicação em projetos reais.
O que são Generics e para que servem?
Imagine que você precisa implementar um método que troca dois valores. Sem generalizações, você teria que escrever versões separadas para cada tipo de dado:
// Sem Generics — duplicação de códigovoid SwapInt(ref int a, ref int b) { int temp = a; a = b; b = temp; }void SwapString(ref string a, ref string b) { string temp = a; a = b; b = temp; }// ... e assim para cada tipoCom Generics, esse problema é resolvido de forma elegante:
// Com Generics — um método para todos os tiposvoid Swap<T>(ref T a, ref T b){ T temp = a; a = b; b = temp;}
// Usoint x = 1, y = 2;Swap(ref x, ref y); // T = int
string s1 = "Hello", s2 = "World";Swap(ref s1, ref s2); // T = stringPrincipais vantagens dos Generics:
- Segurança de tipos — erros de tipo são detectados em tempo de compilação
- Desempenho — ausência de boxing/unboxing para tipos por valor
- Reutilização de código — uma classe/método funciona com qualquer tipo
- Legibilidade — o código se torna mais claro e conciso
Classes e estruturas genéricas
O exemplo mais comum de uso de Generics são as coleções. Em vez de escrever sua própria classe para cada tipo, criamos uma generalizada:
public class MyList<T>{ private T[] _items; private int _count;
public MyList(int capacity = 4) { _items = new T[capacity]; _count = 0; }
public void Add(T item) { if (_count == _items.Length) { Array.Resize(ref _items, _items.Length * 2); } _items[_count++] = item; }
public T Get(int index) { if (index < 0 || index >= _count) throw new IndexOutOfRangeException(); return _items[index]; }
public int Count => _count;}
// Usovar numbers = new MyList<int>();numbers.Add(10);numbers.Add(20);
var names = new MyList<string>();names.Add("Alice");Observe: ao criar uma instância, especificamos o tipo concreto entre colchetes angulares <T>. O compilador substitui esse tipo em todos os lugares onde T é usado.
Métodos genéricos e restrições (constraints)
Às vezes é necessário impor restrições ao tipo T. Por exemplo, para garantir que o tipo implemente uma determinada interface ou tenha um construtor sem parâmetros:
// Restrição: T deve implementar IComparable<T>public T GetMax<T>(T a, T b) where T : IComparable<T>{ return a.CompareTo(b) > 0 ? a : b;}
// Restrição: T deve ter um construtor sem parâmetrospublic class Factory<T> where T : new(){ public T Create() { return new T(); }}
// Restrição: T deve ser um tipo por valor (struct)public struct Nullable<T> where T : struct{ private T _value; private bool _hasValue;
public Nullable(T value) { _value = value; _hasValue = true; }
public T Value => _hasValue ? _value : throw new InvalidOperationException(); public bool HasValue => _hasValue;}Principais tipos de restrições:
where T : class— tipo de referênciawhere T : struct— tipo por valorBlogs
Book Recommendations
Leia também
Como corrigir o erro "Headers already sent" no PHP: guia passo a passo
Go reclama "undefined" para função de outro arquivo? Entendendo
Segurança de aplicações Go: top 10 vulnerabilidades
Refatoração de código legado em TypeScript: técnicas avançadas
Como ocultar campos de struct em JSON: Go para iniciantes e profissionais