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 terIEnumerable<T> ObterTodos(). - A classe
Repositorio<T>implementaIRepositorio<T>e tem um métodoAdicionar(T item). - A classe
Repositorio<T>tem um métodoCriarNovo()que retornanew T()se T tiver construtor sem parâmetros, oudefault(T)caso contrário (usewhere T : new()para permitirnew T()). - No
Main, crie umRepositorio<Produto>, adicione itens, atribua a uma variávelIRepositorio<object>(covariância) e imprima todos. TesteCriarNovo()paraProdutoe paraint(usandoRepositorio<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
- Definimos a interface
IRepositorio<out T>com o modificadorout, permitindo covariância. - A classe
Repositorio<T>implementa a interface e usa uma lista interna. - O método
Adicionaradiciona itens à lista. ObterTodosretorna a lista comoIEnumerable<T>.CriarNovousanew T()porque a restriçãowhere T : new()garante que T tem construtor sem parâmetros.- No
Main, criamos um repositório deProduto, adicionamos itens e atribuímos aIRepositorio<object>graças à covariância. - Chamamos
CriarNovoparaProduto(cria instância com Nome nulo) e paraint(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
outna interface: sem ele, a atribuiçãoIRepositorio<object> repoObject = repoProduto;não compila. - Tentar usar
new T()sem a restriçãonew(): erro de compilação CS0304. - Usar
default(T)em vez denew T()quando se espera uma instância: para classes,default(T)retorna null, enquantonew T()cria um objeto. - Não implementar
ObterTodoscorretamente: 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