4

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!

Community
  • 1
  • 1
Christian St.
  • 1,751
  • 2
  • 22
  • 41
  • what do you mean by `workaround to manage`? – Daniel A. White Jul 22 '14 at 12:08
  • 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 Jul 22 '14 at 12:10
  • On the second question: *because that is what the signature demands*: `class MyGeneric : BaseClass where T : IGenerate, new()` - the `, new()` says "must be a non-abstract type with a public parameterless constructor" – Marc Gravell Jul 22 '14 at 12:11
  • 1
    You can't do `new T(arg1, arg2,...)`. Well you can use reflection [`Activator.CreateInstance`](http://msdn.microsoft.com/en-us/library/system.activator.createinstance(v=vs.110).aspx) (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 Jul 22 '14 at 12:15
  • @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. Jul 22 '14 at 12:29

2 Answers2

3

Although you can use CreateInstance, it is not recommended because of the performance and lack of compile-time safety. An alternative is to require the constructor be passed as a delegate.

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.

This approach also comes with the advantages of being able to use a different constructor, for example:

GenericMethod((arg) => new Foo(arg, 1));

You can also create the object through a static method rather than a constructor, which can use a simplified syntax if the parameters are the same:

GenericMethod((arg) => Foo.Create(arg));
// or
GenericMethod(Foo.Create);
TheBoxyBear
  • 371
  • 2
  • 15
1

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().

Amir Popovich
  • 29,350
  • 9
  • 53
  • 99