Generics em C#: para que servem e como usar na prática

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

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 tipo


Com 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 = string


Principais 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: