views:

125

answers:

3

OK, that title is a little unclear, but I can't think of a better way of putting it, other than explaining it...

Say I have a class Animal, with a static, generic method:

public static T Create<T>() where T : Animal {
  // stuff to create, initialize and return an animal of type T
}

And I have subclasses Dog, Cat, Hamster etc. In order to get a Dog, I can write:

Dog d = Animal.Create<Dog>();

or

Dog d = Dog.Create<Dog>();

which is really the same thing. But it seems kinda silly to have to write Dog so many times, since I'm already invoking the static method through the Dog subclass.

Can you think of any clever way of writing a Create() method in the base class so that I could invoke

Dog d = Dog.Create();
Cat c = Cat.Create();
Hamster h = Hamster.Create();

without writing a Create() method in each of the subclasses?

+10  A: 

You can make the Animal class generic.

class Animal<T> where T : Animal<T>
{
    public static T Create()
    {
        // Don't know what you'll be able to do here
    }
}

class Dog : Animal<Dog>
{

}

But how the Animal class knows how to create instances of derived types?

Romain Verdier
You'll probably want to restrict `T` to Animals: `class Animal<T> where T : Animal<T>`
dtb
@dtb : Sure! Response updated, thanks.
Romain Verdier
The code inside Create() will call virtual/abstract methods, that's how it'll create instances of derived types.
Shaul
Great answer, thanks!
Shaul
Actually, I have to comment again, because this idea of having Animal<T> forcing T to be of type Animal<T> is just sheer genius. At first glance it looks like infinite recursion, but it's not; it's a really elegant piece of logic that has just made my life a HECKUVA lot easier. So thanks again - I wish I could vote you up more! :)
Shaul
I'm using this kind of constraint for my presenters, for exemple:interface IPresenter<TPresenter, TView> : IPresenter where TView : IView<TPresenter> where TPresenter : IPresenter<TPresenter, TView>
Romain Verdier
+1  A: 

I would make the Animal class abstract with a static Create method; it's effectively a starting point for a factory. In fact, it looks like you're undoing a factory class.

If you add an abstract Initialize method to the Animal class, the Create method becomes:

public static T Create<T>() where T : Animal {
  T animal = new T();   //may need a "new" in the declaration
  animal.Initialize();  //or Create or whatever or you put this logic
                        //   in the constructor and don't call this at all.
  return animal;
}
Austin Salonen
Animal can't be a static class. However, if you want to delegate the creation to each derived types, it could be a good idea to make Animal abstract, and the method Initialize abstract instead of virtual.
Romain Verdier
Good catch. That wasn't the intent.
Austin Salonen
@Romain -- well put, I've adjusted my answer.
Austin Salonen
+1  A: 

In addition to the other answers on ways around it, you can see using reflection that Create will always still be part of Animal, not the derived class.

Yuriy Faktorovich