1
votes

I know, there are a few answers here on SO, which seem to solve my questions, like this and that threads. But in my specific case, there is some difference.

In front my question: Is this a possible/intelligent workaround to manage the new() constraint in generics with parameters?

Assume the following base class:

abstract class BaseClass
{
    internal BaseClass()
    {
        Console.WriteLine("{0}'s ctor (parameterless)", "BaseClass");
    }

    internal BaseClass(object parent)
    {
        Console.WriteLine("{0}'s ctor", "BaseClass");
        Parent = parent;
    }

    public object Parent { get; private set; }
}

and interface:

interface IGenerate
{
    IGenerate GenerateNew(int x, object parent);
}

The base class is only intended to store a parent object, the interface provides a method to return an object of the implementing class calling its constructor, like this:

class ClassX : BaseClass, IGenerate
{
    public ClassX()
    {
        Console.WriteLine("{0}'s ctor (parameterless)", "ClassX");
    }

    public ClassX(int x, object parent)
        : base(parent)
    {
        Console.WriteLine("{0}'s ctor", "ClassX");
        X = x;
    }

    public IGenerate GenerateNew(int x, object parent)
    {
        Console.WriteLine("{0}.GenerateNew()", "ClassX");
        return new ClassX(x, parent);
    }

    public int X { get; private set; }
}

My generic class is intended to generate and store an object of the provided class calling the interfaces method:

class MyGeneric<T> : BaseClass where T : IGenerate, new()
{
    public MyGeneric(int x, object parent)
        : base(parent)
    {
        Console.WriteLine("{0}'s ctor", "MyGeneric");
        Instance = new T().GenerateNew(x, this);
    }

    public IGenerate Instance { get; private set; }
}

Another class inherits the generic:

class ClassXSpecifier : MyGeneric<ClassX>
{
    public ClassXSpecifier(int x, object parent)
        : base(x, parent)
    {
        Console.WriteLine("{0}'s ctor", "ClassXSpecifier");
    }
}

The use of these constructs is something like that:

var classXspecifier = new ClassXSpecifier(5, null);
var classX = (ClassX)classXspecifier.Instance;
Console.WriteLine(classX.X);

Output:

BaseClass's ctor
MyGeneric's ctor
BaseClass's ctor (parameterless)
ClassX's ctor (parameterless)
ClassX.GenerateNew()
BaseClass's ctor
ClassX's ctor
ClassXSpecifier's ctor
5

Again my primary question: Is this a possible/intelligent workaround to manage the new() constraint in generics with parameters?

A secondary question: Why do BaseClass and ClassX need to have a parameterless constructor while they won't be used in any case explicitly? If I remove them, I get the following error:

'ClassX' must be a non-abstract type with a public parameterless constructor in order to use it as parameter 'T' in the generic type or method 'MyGeneric'

Thanks in advance, Christian =)

!!! SOLUTION !!!

The provided answer tempt me to do modifications, that the new() constraint could be removed -> so the parameterless constructors could be removed, too.

I deleted the interface and added a static method into BaseClass to generate new objects:

public static BaseClass GenerateNew(Type T, object[] args)
{
    return (BaseClass)Activator.CreateInstance(T, args);
}

So the generic class could be reduced to

class MyGeneric<T> : BaseClass
{
    public MyGeneric(int x, object parent)
        : base(parent)
    {
        Console.WriteLine("{0}'s ctor", "MyGeneric");
        Instance = GenerateNew(typeof(T), new[] { x, parent });
    }

    public BaseClass Instance { get; private set; }
}

That was it, thanks to all comments, too!

2
what do you mean by workaround to manage?Daniel A. White
I've gotten around this in the past by having another generic parameter TFactory that is contrained on new() and an interface that returns the other type T, where the method signature can take parameters. But this approach is somewhat limited, and I can't remember the scenario I used it in.Adam Houldsworth
On the second question: because that is what the signature demands: class MyGeneric<T> : BaseClass where T : IGenerate, new() - the , new() says "must be a non-abstract type with a public parameterless constructor"Marc Gravell
You can't do new T(arg1, arg2,...). Well you can use reflection Activator.CreateInstance (choose relevant overload). But there're few thing to notice: 1. Reflection may fail. 2. Reflecting happens during runtime so no compiler optimizations there.Leri
@DanielA.White :I want to know, if I'll run into problems in future using these constructs, maybe it's an unclear phrase in the text ;)Christian St.

2 Answers

2
votes

Not the best solution but what I did at one point was to require a function that returns the generic type and provide the constructor as the function parameter:

void GenericMethod<T>(Func<string, T> ctor)
{
    T t = ctor("foo");
}

To call the method, using a class called Foo as the generic type: GenericMethod((arg) => new Foo(arg))

args does not need to be defined prior to calling the generic method and is only used to indicate how the parameters of ctor will be used.It also makes the method more flexible, as the caller may opt to use a method rather than a constructor, like System.Drawing.Image.FromFile() or an existing object using a lambda expression that returns it.

1
votes

Question
Again my primary question: Is this a possible/intelligent workaround to manage the new() constraint in generics with parameters?

Answer
Your passing a type(ClassX) and want to access an instance function(GenerateNew) without creating an instance -> well that's one problem you need to think about. You can create a static factory(and\or use IOC) for creating new object's by types.

Question
Why do BaseClass and ClassX need to have a parameterless constructor while they won't be used in any case explicitly?

Answer
This constraint requires that the generic type that is used is non-abstract and that it has a default (parameterless) constructor allowing you to call it. BTW, you are using the empty ctor by doing new T().