Como retorno um tipo genérico através de um método em uma classe base?
-
03-07-2019 - |
Pergunta
Eu tenho tentado criar uma classe base simples que encapsula algumas das minhas convenções para o acesso ao banco de dados. Geralmente, crio um SPROC chamado "Products_retieve_product" para selecionar um produto baseado no ProductID. Eu gostaria que o método "recupere" na classe base retorne o tipo que a classe derivada fornece em sua definição. O que estou tentando realizar é possível com os genéricos?
public class MyBaseClass <T>
{
private string _className;
public MyBaseClass ()
{
_className = this.GetType().Name;
}
public virtual T Retrieve(int ID)
{
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand(String.Format("{0}s_Retrieve_{0}", _className));
db.AddInParameter(dbCommand, String.Format("@{0}ID", _className), DbType.Int32, ID);
using (IDataReader dr = db.ExecuteReader(dbCommand))
{
if (dr.Read())
{
BOLoader.LoadDataToProps(this, dr);
}
}
return (T)this;
}
}
Solução
Eu acho que você quer fazer algo assim:
class MyBaseClass<T> where T : MyBaseClass<T>, new()
{
public T Retrieve()
{
return new T();
}
}
class Foo : MyBaseClass<Foo>
{
}
class Program
{
public static void Main()
{
Foo f = new Foo();
Foo f2 = f.Retrieve();
Console.WriteLine(f2.ToString());
}
}
Quando você executa este programa, o nome de tipo de foo é impresso na linha de comando. Obviamente, este é um exemplo artificial, mas talvez você possa fazer algo mais útil ao carregar de um banco de dados em MyBaseClass.Retrieve()
.
A chave é adicionar uma restrição a T para que ela deve ser uma instância da própria classe. Dessa forma, você pode especificar a subclasse como tipo genérico ao subclasse MyBaseClass<T>.
Não tenho certeza se isso é uma boa ideia ou não, mas parece que pode ser feito.
Outras dicas
Claro. No seu exemplo, se eu quisesse que minha aula Foo retornasse as barras quando recuperar (...) é chamado:
public class Foo : MyBaseClass<Bar>{}
Hmm, estamos usando a reflexão para obter o nome da classe. Boloader está sem dúvida usando a reflexão para carregar algumas propriedades. Por que não se comprometer totalmente com a reflexão?
Boloader não se importa com este jogo do "Tipo de Retorno". Por que deveríamos?
public static class MyBaseClassExtender
{
public static void Retrieve(this MyBaseClass entity, int ID)
{
string className = entity.GetType().Name;
Database db = DatabaseFactory.CreateDatabase();
DbCommand dbCommand = db.GetStoredProcCommand(String.Format("{0}s_Retrieve_{0}", className));
db.AddInParameter(dbCommand, String.Format("@{0}ID", className), DbType.Int32, ID);
using (IDataReader dr = db.ExecuteReader(dbCommand))
{
if (dr.Read())
{
BOLoader.LoadDataToProps(this, dr);
}
}
}
}
Então você apenas diz:
Foo myFoo = new Foo();
myFoo.Retrieve(2);
Não, não é, porque o que você realmente precisa ser capaz de fazer é algo assim na definição da classe:
public class MyBaseClass<T> : T
Que atualmente não é possível com genéricos.
O que você precisa fazer é separar a fábrica do que a fábrica produz (você precisa de uma classe separada que construirá T e, em seguida, você deve fornecer métodos auxiliares para trabalhar em t, possivelmente métodos de extensão).