Come posso restituire un tipo generico tramite un metodo in una classe base?
-
03-07-2019 - |
Domanda
Ho provato a creare una semplice classe base che incapsula alcune delle mie convenzioni per l'accesso al database. Generalmente creo uno sproc chiamato "products_retrieve_product" per selezionare un prodotto in base a productID. Vorrei il metodo "Recupera" nella classe base per restituire il tipo fornito dalla classe derivata nella sua definizione. Ciò che sto cercando di realizzare è possibile con Generics?
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;
}
}
Soluzione
Penso che tu voglia fare qualcosa del genere:
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 si esegue questo programma, il nome del tipo di Foo viene stampato sulla riga di comando. Ovviamente questo è un esempio inventato, ma forse puoi fare qualcosa di più utile quando carichi da un database in MyBaseClass.Retrieve ()
.
La chiave è aggiungere un vincolo su T in modo che debba essere un'istanza della classe stessa. In questo modo è possibile specificare la sottoclasse come tipo generico durante la sottoclasse MyBaseClass<T>.
Non sono del tutto sicuro che questa sia una buona idea o meno, ma sembra che possa essere fatto.
Altri suggerimenti
Certo. Nel tuo esempio, se volessi che la mia classe Foo restituisse Bars quando Retrieve (...) si chiama:
public class Foo : MyBaseClass<Bar>{}
Hmm, stiamo usando reflection per ottenere il nome della classe. BOLoader utilizza senza dubbio la riflessione per caricare alcune proprietà. Perché non impegnarsi completamente nella riflessione?
A BOLoader non interessa questo gioco di "quot tipo di ritorno". Perché dovremmo?
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);
}
}
}
}
Quindi dici solo:
Foo myFoo = new Foo();
myFoo.Retrieve(2);
No, non lo è, perché ciò che devi davvero essere in grado di fare è qualcosa di simile nella definizione della classe:
public class MyBaseClass<T> : T
Che attualmente non è possibile con generici.
Quello che devi fare è separare la fabbrica da ciò che la fabbrica produce (hai bisogno di una classe separata che costruirà T e quindi dovresti fornire metodi di supporto per lavorare su T, possibilmente metodi di estensione).