views:

320

answers:

4

I've been trying to create a simple base class that encapsulates some of my conventions for database access. I generally create a sproc named "products_retrieve_product" to select a product based on productID. I would like the method "Retrieve" in the base class to return the type that the derived class supplies in it's definition. Is what I'm trying to accomplish possible with 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;
    }
}
A: 

No, it is not, because what you really need to be able to do is something like this in the class definition:

public class MyBaseClass<T> : T

Which currently isn't possible with generics.

What you need to do is separate the factory from what the factory produces (you need a separate class which will build T and then you should provide helper methods to work on T, possibly extension methods).

casperOne
+1  A: 

Sure. In your example, if I wanted my Foo class to return Bars when Retrieve(...) is called:

public class Foo : MyBaseClass<Bar>{}
Alun Harford
The part I'm stuck on is how the "Retrieve" method casts the object into the derived type.
Mark Fruhling
You shouldn't need to cast it at all, since it is already declared as the return type of the method: "return this;" does the work you need.
Jeff Sternal
I think you are talking about the method being in the derived class, but I would like the base class to implement a method that returns a type based on the derived class.
Mark Fruhling
Oh, sorry - I missed that. You need a generic constraint to do that:public class MyBaseClass<T> where T : MyBaseClass<T>{}
Alun Harford
+1  A: 

I think that you want to do something like this:

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());
    }
}

When you run this program, the type name of Foo is printed on the command line. Obviously this is a contrived example, but maybe you can do something more useful when loading from a database in MyBaseClass.Retrieve().

The key is to add a constraint on T so that it must be an instance of the class itself. This way you can specify the subclass as the generic type when subclassing MyBaseClass<T>.

I'm not entirely sure if this is a good idea or not, but it looks like it can be done.

Andy
A: 

Hmm, we're using reflection to get the classname. BOLoader is doubtlessly using reflection to load some properties. Why not fully commit to reflection?

BOLoader doesn't care about this game of "return type". Why should we?

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);
            }
        }
    }
}

Then you just say:

Foo myFoo = new Foo();
myFoo.Retrieve(2);
David B