views:

142

answers:

3

I have a class in C# with a template and static method similar to

class BClass<T>
{
  public static BClass<T> Create()
  {
    return new BClass<T>();
  }
 }

From this I derive a class and specify a template parameter to the base class

class DClass : BClass<int> { }

A problem occurs when I try to use the static method to create an instance of D

class Program
{
  static void Main(string[] args)
  {
    DClass d = DClass.Create();
  }
}

Gives a compiler error "Cannot implicitly convert type 'Test.BClass<int> ' to 'Test.DClass'."

Adding the below cast leads to a runtime casting exception.

DClass d = (DClass)DClass.Create();

Is there any succint way to allow the static method to create instances of the derived class? Ideally I would like the equivalent of a c++ typedef and I don't want the below syntax (which does work).

BClass<int> d = DClass.Create();
+7  A: 

It seems that what you want is for DClass to be an alias for BClass<int>. But that's not what you have here. What you have is that DClass derives from BClass<int>. Thus calling DClass.Create(); creates a BClass<int>, which is not a DClass (it's the other way around).

This might make it clearer. Suppose you had this hierarchy:

class Rectangle {
    static Rectangle Create() {
        return new Rectangle();
    }
}

class Square : Rectangle { }

// code elsewhere
var shape = Square.Create(); // you might want this to return a square,
                             // but it's just going to return a rectangle

One option to get something like the functionality you want might be to define your Create method like this:

static TClass Create<TClass>() where TClass : BClass<T>, new() {
    return new TClass();
}

// code elsewhere
var shape = DClass.Create<DClass>();

That's a bit messy, though (and also requires that you write a parameterless constructor for DClass). If you're dead set on using a Create method for the instantiation of your objects, consider writing a factory class for it.

Dan Tao
+1  A: 
class BClass<T>
    {
        public static T1 Create<T1, T2>() where T1 : BClass<T2>, new()
        {
            return new T1();
        }
    }

    class DClass : BClass<int> { }

    class Program
    {
        static void Main(string[] args)
        {
            DClass d = DClass.Create<DClass, int>();
        }
    }
Itay
+4  A: 

Yes it is possible, by having a type reference to the type itself. Note that in the .NET world we are talking of generics, not templates, which do have important differences in the way they work.

class BClass<T, TSelf> where TSelf: BClass<T, TSelf>, new() {
    public static TSelf Create() {
        return new TSelf();
    }
}

class DClass: BClass<int, DClass> {}

class Program {
    static void Main(string[] args) {
        DClass d = DClass.Create();
    }
}
Lucero
Didn't think a class could reference itself as a type parameter in its superclass? Chicken-and-egg paradox or something...
Peter Lillevold
Clever AND a bit bizarre! ;) The only disadvantage I see here is that it seems there'd never be any way to instantiate a plain old `BClass<T, TSelf>`, as in the OP's original code.
Dan Tao
@Peter, I actually have used this successfully quite a lot. For instance it also allows you to create a better clone method because it eliminates the need to re-cast, etc.
Lucero
@Dan Tao, right, that basically only works for "abstract" classes which are then inherited.
Lucero
@Lucero, that is pretty cool. Guess it was similar to what I've tried before, `MyClass : ISomeGenericInterface<MyClass>`... which does not work :)
Peter Lillevold
@Peter, `MyClass: ISomeGenericInterface<MyClass>` does work fine for me. You must have tried something else. ;)
Lucero
@Lucero - ouch... man, what was it then?? perhaps `MyClass<T> : T`. Ohwell :)
Peter Lillevold
This is a good answer, but note that if you have another class `EClass : DClass`, then `EClass.Create()` will return a `DClass` instance, not an `EClass` instance, which may or may not be a problem.
kvb