tags:

views:

1525

answers:

5

If BaseFruit has a constructor that accepts an int weight, can I instantiate a piece of fruit in a generic method like this?

public void AddFruit<T>()where T: BaseFruit{
    BaseFruit fruit = new T(weight); /*new Apple(150);*/
    fruit.Enlist(fruitManager);
}

An example is added behind comments. It seems I can only do this if I give BaseFruit a parless constructor and then fill in everything through member variables. In my real code (not about fruit) this is rather impractical.

-Update-
So it seems it can't be solved by constraints in any way then. From the answers there are three candidate solutions:

  • Factory Pattern
  • Reflection
  • Activator

I tend to think reflection is the least clean one, but I can't decide between the other two.

+3  A: 

Yes; change your where to be:

where T:BaseFruit, new

However, this only works with parameterless constructors. You'll have to have some other means of setting your property (setting the property itself or something similar).

Adam Robinson
Darn, I got Robinsoned...
Jon Skeet
@Jon Skeet: That came very close to making me laugh out loud (at work!).
Michael Myers
+6  A: 

You can't use any parameterised constructor. You can use a parameterless constructor if you have a "where T : new()" constraint.

It's a pain, but such is life :(

This is one of the things I'd like to address with "static interfaces". You'd then be able to constrain T to include static methods, operators and constructors, and then call them.

Jon Skeet
I got Skeeted by 10 seconds...sheesh...
Adam Robinson
On the contrary - you beat me, as far as I can see :)
Jon Skeet
You're all fruity! :)
Patrick McDonald
I really just wanted to say Skeeted.
Adam Robinson
At least you CAN do such constraints - Java always disappoints me.
Marcel J.
+3  A: 

As Jon pointed out this is life for constraining a non-parameterless constructor. However a different solution is to use a factory pattern. This is easily constrainable

interface IFruitFactory<T> where T : BaseFruit {
  T Create(int weight);
}

public void AddFruit<T>( IFruitFactory<T> factory ) where T: BaseFruit {    
  BaseFruit fruit = factory.Create(weight); /*new Apple(150);*/    
  fruit.Enlist(fruitManager);
}

Yet another option is to use a functional approach. Pass in a factory method.

public void AddFruit<T>(Func<int,T> factoryDel) where T : BaseFruit { 
  BaseFruit fruit = factoryDel(weight); /* new Apple(150); */
  fruit.Enlist(fruitManager);
}
JaredPar
Good suggestion - although if you're not careful you can end up in the hell of the Java DOM API, with factories galore :(
Jon Skeet
@Jon, wouldn't want that :)
JaredPar
Yes, this is a solution I was concidering myself. But I was hoping for something in the line of constraints. Guess not then..
borisCallens
@boris, unfortunately the constraint language you are looking for does not exist at this point in time
JaredPar
+4  A: 

You can do by using reflection:

public void AddFruit<T>()where T: BaseFruit
{
  ConstructorInfo constructor = typeof(T).GetConstructor(new Type[] { typeof(int) });
  if (constructor == null)
  {
    throw new InvalidOperationException("Type " + typeof(T).Name + " does not contain an appropriate constructor");
  }
  BaseFruit fruit = constructor.Invoke(new object[] { (int)150 }) as BaseFruit;
  fruit.Enlist(fruitManager);
}

EDIT: Added constructor == null check.

rstevens
Although I don't like the overhead of the reflection, as others have explained, this just is the way it is currently. Seeing how this constructor won't be called too much, I could go with this. Or the factory. Don't know yet.
borisCallens
+1  A: 

Additionally a simpler example:

return (T)Activator.CreateInstance(typeof(T), new object[] { weight});

Note that using the new() constraint on T is only to make the compiler check for a public parameterless constructor at compile time, the actual code used to create the type is the Activator class.

You will need to ensure yourself regarding the specific constructor existing, and this kind of requirement may be a code smell (or rather something you should just try to avoid in the current version on c#).

meandmycode
Since this constructor is on the baseclass (BaseFruit) I know it will have a constructor. But indeed, if one day I decide basefruit needs more parameters, I could be screwed. Will look into the ACtivator class though. Didn't hear of it before.
borisCallens
This one worked out fine. There's also a CreateInstance<T>() procedure, but that doesn't have an overload for parameters for some rason..
borisCallens