Da trilha de C# Este conceito ainda não saiu como card do dia. ir para o card de hoje

C# Genéricos Intermediário

Genéricos

Genéricos permitem escrever código reutilizável e seguro em relação a tipos, sem sacrificar desempenho. Em C#, você define classes, métodos e interfaces com parâmetros de tipo, como List<T> ou Dictionary<TKey, TValue>. O compilador gera código específico para cada tipo concreto usado, evitando boxing e casts desnecessários.

Restrições (where) limitam os tipos que podem ser usados como argumentos genéricos. Por exemplo, where T : class exige um tipo de referência, enquanto where T : IComparable<T> garante que T possa ser comparado. Isso permite chamar métodos específicos dentro do código genérico.

Covariância (out T) permite atribuir uma coleção de um tipo mais derivado a uma variável de um tipo mais base, como IEnumerable<string> para IEnumerable<object>. Contravariância (in T) faz o oposto. Esses modificadores só funcionam em interfaces e delegates.

O operador default obtém o valor padrão de um tipo: null para referências, zero para numéricos, e uma instância com campos zerados para structs. Em genéricos, default(T) é essencial para inicializar variáveis sem saber se T é classe ou struct.

Pontos-chave

  • Genéricos evitam boxing e casts, melhorando desempenho e segurança de tipos.
  • where T : constraint restringe os tipos aceitos e permite chamar membros específicos.
  • Covariância (out) permite converter IEnumerable<Derivado> para IEnumerable<Base>.
  • Contravariância (in) permite converter IComparer<Base> para IComparer<Derivado>.
  • default(T) retorna o valor padrão do tipo T, útil em inicializações genéricas.
GenericosExemplo.cs
using System;
using System.Collections.Generic;

// Classe genérica com restrição: T deve ser classe e ter construtor sem parâmetros
public class Repositorio<T> where T : class, new()
{
    private readonly List<T> itens = new List<T>();

    public void Adicionar(T item) => itens.Add(item);

    public T CriarNovo() => new T(); // possível graças à restrição new()

    public void MostrarTodos()
    {
        foreach (var item in itens)
            Console.WriteLine(item);
    }
}

public class Produto
{
    public string Nome { get; set; }
    public override string ToString() => $"Produto: {Nome}";
}

// Método genérico com restrição a IComparable
public static class Comparador
{
    public static T Max<T>(T a, T b) where T : IComparable<T>
        => a.CompareTo(b) > 0 ? a : b;
}

class Program
{
    static void Main()
    {
        var repo = new Repositorio<Produto>();
        repo.Adicionar(new Produto { Nome = "Caneta" });
        repo.Adicionar(new Produto { Nome = "Caderno" });
        repo.MostrarTodos();

        Console.WriteLine(Comparador.Max(10, 20)); // 20
        Console.WriteLine(Comparador.Max("Ana", "Bia")); // Bia

        // Covariância: IEnumerable<string> para IEnumerable<object>
        IEnumerable<string> strings = new List<string> { "a", "b" };
        IEnumerable<object> objects = strings;
        foreach (var obj in objects)
            Console.WriteLine(obj);

        // default(T)
        T Generico<T>() => default(T);
        Console.WriteLine(Generico<int>()); // 0
        Console.WriteLine(Generico<string>() ?? "null"); // null
    }
}

Exercícios

  1. 1
    Fácil

    Classe genérica Caixa

    Crie uma classe genérica Caixa<T> que armazena um valor do tipo T. Ela deve ter um método Abrir() que retorna o valor armazenado e um método EstaVazia() que indica se o valor é o padrão do tipo T (use default(T)).

    Resolver
  2. 2
    Médio

    Método genérico com restrição IComparable

    Implemente um método genérico Maior<T> que recebe dois argumentos do tipo T e retorna o maior. Use a restrição where T : IComparable<T> para garantir que os tipos possam ser comparados. Teste com int e string.

    Resolver
  3. 3
    Desafio

    Repositório genérico com covariância e default

    Crie uma interface IRepositorio<T> com covariância (out T) que expõe um método ObterTodos() retornando IEnumerable<T>. Implemente uma classe Repositorio<T> que armazena itens em uma lista e implementa a interface. A classe deve ter um método CriarNovo() que retorna default(T) se T for uma classe, ou uma nova instância se T tiver construtor sem parâmetros (use restrição where T : new()). Teste com uma classe Produto e com int.

    Resolver

Continue estudando