Desafio C#

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

Enunciado

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.

Requisitos

  • A interface IRepositorio<out T> deve ser covariante e ter IEnumerable<T> ObterTodos().
  • A classe Repositorio<T> implementa IRepositorio<T> e tem um método Adicionar(T item).
  • A classe Repositorio<T> tem um método CriarNovo() que retorna new T() se T tiver construtor sem parâmetros, ou default(T) caso contrário (use where T : new() para permitir new T()).
  • No Main, crie um Repositorio<Produto>, adicione itens, atribua a uma variável IRepositorio<object> (covariância) e imprima todos. Teste CriarNovo() para Produto e para int (usando Repositorio<int>).

Código inicial

using System;
using System.Collections.Generic;

public interface IRepositorio<out T>
{
    IEnumerable<T> ObterTodos();
}

public class Repositorio<T> : IRepositorio<T> where T : new()
{
    // implemente
}

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

class Program
{
    static void Main()
    {
        // teste aqui
    }
}

Saída esperada

Produto: Caneta
Produto: Caderno
Produto: 
0
Ver dica

Para covariância, a interface deve usar out T. O método CriarNovo pode usar new T() graças à restrição new(). Para int, new int() retorna 0.

Mostrar solução
using System;
using System.Collections.Generic;

public interface IRepositorio<out T>
{
    IEnumerable<T> ObterTodos();
}

public class Repositorio<T> : IRepositorio<T> where T : new()
{
    private readonly List<T> itens = new List<T>();

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

    public IEnumerable<T> ObterTodos() => itens;

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

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

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

        // Covariância: IRepositorio<Produto> para IRepositorio<object>
        IRepositorio<object> repoObject = repoProduto;
        foreach (var obj in repoObject.ObterTodos())
            Console.WriteLine(obj);

        // CriarNovo com Produto
        var novoProduto = repoProduto.CriarNovo();
        Console.WriteLine(novoProduto); // Produto: 

        // CriarNovo com int
        var repoInt = new Repositorio<int>();
        Console.WriteLine(repoInt.CriarNovo()); // 0
    }
}

Passo a passo

  1. Definimos a interface IRepositorio<out T> com o modificador out, permitindo covariância.
  2. A classe Repositorio<T> implementa a interface e usa uma lista interna.
  3. O método Adicionar adiciona itens à lista.
  4. ObterTodos retorna a lista como IEnumerable<T>.
  5. CriarNovo usa new T() porque a restrição where T : new() garante que T tem construtor sem parâmetros.
  6. No Main, criamos um repositório de Produto, adicionamos itens e atribuímos a IRepositorio<object> graças à covariância.
  7. Chamamos CriarNovo para Produto (cria instância com Nome nulo) e para int (cria 0).

Por que funciona

A covariância permite tratar IRepositorio<Produto> como IRepositorio<object> porque Produto é subtipo de object. A restrição new() permite instanciar T dentro do método genérico. default(T) não é usado diretamente, mas new T() para tipos valor retorna o valor padrão (0 para int).

Erros comuns

  • Esquecer o out na interface: sem ele, a atribuição IRepositorio<object> repoObject = repoProduto; não compila.
  • Tentar usar new T() sem a restrição new(): erro de compilação CS0304.
  • Usar default(T) em vez de new T() quando se espera uma instância: para classes, default(T) retorna null, enquanto new T() cria um objeto.
  • Não implementar ObterTodos corretamente: a interface exige o método.

Outra forma de resolver

Se não quiser a restrição new(), pode usar Activator.CreateInstance<T>(), mas isso é menos eficiente e lança exceção se T não tiver construtor sem parâmetros. A restrição é preferível por ser verificada em tempo de compilação.

Saída esperada

Produto: Caneta
Produto: Caderno
Produto: 
0