C#: 有构造函数的泛型类型?

20
我有以下的C#测试代码:
  class MyItem
  {
    MyItem( int a ) {}
  }

  class MyContainer< T >
    where T : MyItem, new()
  {
    public void CreateItem()
    {
      T oItem = new T( 10 );
    }
  }

Visual Studio 无法编译它,在使用 'new' 的那一行出现了错误:

'T': cannot provide arguments when creating an instance of a variable type

在C#中,是否可以创建具有非无参构造函数的泛型类型的对象?在C++模板中做这样的事情不成问题,所以我很好奇为什么在C#中我不能做同样的事情。可能需要额外的“where”或语法不同吗?
5个回答

25

C#和VB.Net都不支持对泛型进行约束,使其必须具有特定参数的构造函数。它只支持限制为空构造函数。

一种解决方法是让调用者传递一个工厂lambda,以创建值。例如:

public void CreateItem(Func<int,T> del) {
  T oItem = del(10);
}

呼叫站点
CreateItem(x => new SomeClass(x));

18
可以使用反射来完成:

Reflection(反射)可实现此操作:

public void CreateItem()
{
  int constructorparm1 = 10;
  T oItem = Activator.CreateInstance(typeof(T), constructorparm1) as T;
}

但是,没有通用的约束条件来确保T实现所需的构造函数,因此我不建议这样做,除非你小心地在实现接口的每个类型中声明该构造函数。


据我回忆,new() 约束转换为 Activator.CreateInstance() 调用。 - Steve Guidi

10

目前没有这样的通用约束,因此不能直接实现(这是CLR的限制)。如果您需要此功能,则必须提供一个工厂类(该类必须具有无参数构造函数),并将其作为第二个泛型类型参数传递。


5

我认为,在这里最好的方法是使用初始化方法,即:

interface ISomeInterface {
    void Init(int i);
}
class Foo : ISomeInterface {
    void ISomeInterface.Init(int i) { /* ... */ }
}
static class Program {
    static T Create<T>(int i) where T : class, ISomeInterface, new() {
        T t = new T();
        t.Init(i);
        return t;
    }
    static void Main() {
        Foo foo = Create<Foo>(123);
    }
}

然而,您可以使用Expression自由操作(但没有编译时支持):

using System;
using System.Linq.Expressions;
class Foo {
    public Foo(int i) { /* ... */ }
}
static class Program {
    static T Create<T>(int i) {
        return CtorCache<T>.Create(i);
    }
    static class CtorCache<T> {
        static Func<int, T> ctor;
        public static T Create(int i) {
            if (ctor == null) ctor = CreateCtor();
            return ctor(i);
        }
        static Func<int, T> CreateCtor() {
            var param = Expression.Parameter(typeof(int), "i");
            var ci = typeof(T).GetConstructor(new[] {typeof(int)});
            if(ci == null) throw new InvalidOperationException("No such ctor");
            var body = Expression.New(ci, param);
            return Expression.Lambda<Func<int, T>>(body, param).Compile();
        }
    }
    static void Main() {
        Foo foo = Create<Foo>(123);
    }
}

请注意,这是为了提高性能而缓存和重用委托。

这与使用 Activator.CreateInstance() 有显著的不同吗?如果我理解正确,两者都没有编译时支持,如果期望的构造函数不存在,则都会抛出运行时错误。 - Greg
@Greg - 是的,如果你使用它很多(比如工厂):一旦创建,该委托将被预缓存和JIT编译 - 不再需要反射。 - Marc Gravell

2

我使用的一种模式是让受限制的类实现一个接口,该接口定义了具有适当签名的Init方法:

interface IMyItem
{
    void Init(int a);
}

class MyItem : IMyItem
{
    MyItem() {}
    void Init(int a) { }
}    

class MyContainer< T >
    where T : MyItem, IMyItem, new()
{
    public void CreateItem()
    {
        T oItem = new T();
        oItem.Init( 10 );
    }
}

通常不是一个好主意,因为现在你可能已经创建了但未初始化的对象。 - Pavel Minaev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接