views:

598

answers:

4

I've got a number of classes that inherit from Item<T>.

Each class has a Create() method that I would like to move up into Item<T>.

Yet the following code gets the error "Cannot create an instance of the variable type 'T' because it does not have the new() constraint":

T item = new T(loadCode);

What is the correction syntax to do this?

public abstract class Item<T> : ItemBase
{

    public static T Create(string loadCode)
    {
        T item = new T(loadCode);

        if (!item.IsEmpty())
        {
            return item;
        }
        else
        {
            throw new CannotInstantiateException();
        }
    }
+9  A: 

It's not possible. You can only use new() constraint to force existence of a constructor.

A workaround is to take a Func<InputType, T> (Func<string,T> in your example) delegate as input parameter that'll create the object for us.

public static T Create(string loadCode, Func<string,T> construct)
{
    T item = construct(loadCode);

    if (!item.IsEmpty())
    {
        return item;
    }
    else
    {
        throw new CannotInstantiateException();
    }
}

and call it with: Item<T>.Create("test", s => new T(s));

Mehrdad Afshari
isn't the OP simply asking how he can get that piece of code compiled without errors?
Philippe Leybaert
@activa: There's simply no way to make that work directly. The idea is fundamentally unsupported in C#.
Mehrdad Afshari
You're right. I misread the question. Slap me
Philippe Leybaert
+1  A: 

You could use Activator.CreateInstance to create the instance, although it does not do any compile-time checks.

Groo
The first idea doesn't work. A derived class can have a separate set of constructors. Activator.CreateInstance will work but it doesn't force anything at compile time so the code might perfectly compile and surprisingly fail at runtime which is not a good thing.
Mehrdad Afshari
Right. It would have to call base(ItemBase), but the derived class itself wouldn't have to have that specific ctor. My fault, will change that.
Groo
+2  A: 

Correction: I made a slight mistake, you need another constraint on T for this solution, see code below.

You can only put a constraint on for parameterless constructor, but maybe something like this could work:

public interface ILoadable
{
    void Load(string loadCode);
}

public abstract class Item<T> : ItemBase
    where T : ILoadable, new()
{
    public static T Create(string loadCode)
    {
        T item = new T();
        item.Load(loadCode);

        if (!item.IsEmpty())
        {
            return item;
        }
        else
        {
            throw new CannotInstantiateException();
        }
    }
}

And then you implement the constructor code that depends on the loadCode in the override to Load(...) in the descendant classes.

jerryjvl
But the compiler has no way of knowing that Load() exists on T. You'd need an additional constraint (an interface for example)
Philippe Leybaert
You are right... I got distracted by the wording of the question into thinking that T was of type Item<T>... duh.
jerryjvl
A: 

The error is because the compiler cannot rely on the fact that the generic class T has anything other than the default constructor.

You can overcome this using reflection as follows:

ConstructorInfo c = typeof(T).GetConstructor(new Type[] { typeof(string) });
T t = (T)c.Invoke(new object[] { loadCode });

As your T type must therefore have a constructor which takes a string then I would also restrict your class so that it must inherit from a class with a constructor:

class Item<T> : ItemBase where T : BaseClassWithConstructor
samjudson
As I said in another comment, this works but is unsafe. And your restriction doesn't mean anything for a derived class. A base class will have a parameterless constructor and the derived classes might not have one.
Mehrdad Afshari