关于.net:将参数传递给模板化类型的C#generic new()

Passing arguments to C# generic new() of templated type

我试图在添加到列表时通过它的构造函数创建一个新的t类型的对象。

我收到一个编译错误:错误消息是:

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

但是我的类确实有一个构造函数参数!我怎样才能做到这一点?

1
2
3
4
5
6
7
8
9
10
public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)
   {
       tabListItems.Add(new T(listItem)); // error here.
   }
   ...
}


为了在函数中创建泛型类型的实例,必须用"new"标志对其进行约束。

1
public static string GetAllItems<T>(...) where T : new()

但是,只有当您想要调用没有参数的构造函数时,这才有效。这里不是这样。相反,您必须提供另一个允许基于参数创建对象的参数。最简单的是函数。

1
2
3
4
5
6
7
8
9
public static string GetAllItems<T>(..., Func<ListItem,T> del) {
  ...
  List<T> tabListItems = new List<T>();
  foreach (ListItem listItem in listCollection)
  {
    tabListItems.Add(del(listItem));
  }
  ...
}

你可以这样称呼它

1
GetAllItems<Foo>(..., l => new Foo(l));


在.NET 3.5和之后,您可以使用Activator类:

1
(T)Activator.CreateInstance(typeof(T), args)


由于没有人费心发布"反思"答案(我个人认为这是最好的答案),下面是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)
   {
       Type classType = typeof(T);
       ConstructorInfo classConstructor = classType.GetConstructor(new Type[] { listItem.GetType() });
       T classInstance = (T)classConstructor.Invoke(new object[] { listItem });

       tabListItems.Add(classInstance);
   }
   ...
}

编辑:由于.NET 3.5的Activator.CreateInstance,此答案已被弃用,但在较旧的.NET版本中仍然有用。


对象初始值设定项

如果带参数的构造函数除了设置属性之外没有做任何事情,那么您可以在C 3中这样做,或者更好地使用对象初始值设定项,而不是调用构造函数(如前所述,这是不可能的):

1
2
3
4
5
6
7
8
9
10
public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)
   {
       tabListItems.Add(new T() { YourPropertyName = listItem } ); // Now using object initializer
   }
   ...
}

使用此方法,您也可以将任何构造函数逻辑放入默认(空)构造函数中。

activator.createInstance()。

或者,可以这样调用activator.createInstance():

1
2
3
4
5
6
7
8
9
10
11
public static string GetAllItems<T>(...) where T : new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)
   {
        object[] args = new object[] { listItem };
        tabListItems.Add((T)Activator.CreateInstance(typeof(T), args)); // Now using Activator.CreateInstance
   }
   ...
}

注意,如果执行速度是最高优先级,并且您可以维护另一个选项,那么Activator.CreateInstance可能会有一些性能开销,您可能希望避免这些开销。


这在你的情况下是行不通的。只能指定具有空构造函数的约束:

1
public static string GetAllItems<T>(...) where T: new()

您可以通过定义这个接口来使用属性注入:

1
2
3
4
public interface ITakesAListItem
{
   ListItem Item { set; }
}

然后您可以将您的方法更改为:

1
2
3
4
5
6
7
8
9
10
public static string GetAllItems<T>(...) where T : ITakesAListItem, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)
   {
       tabListItems.Add(new T() { Item = listItem });
   }
   ...
}

另一种选择是Jaredpar描述的Func方法。


旧问题,新答案;-)

ExpressionTree版本:(我认为是最快最干净的解决方案)

就像WellyTambunan所说,"我们也可以使用表达式树来构建对象。"

这将为给定的类型/参数生成"构造函数"(函数)。它返回委托并接受参数类型作为对象数组。

这里是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// this delegate is just, so you don't have to pass an object array. _(params)_
public delegate object ConstructorDelegate(params object[] args);

public static ConstructorDelegate CreateConstructor(Type type, params Type[] parameters)
{
    // Get the constructor info for these parameters
    var constructorInfo = type.GetConstructor(parameters);

    // define a object[] parameter
    var paramExpr = Expression.Parameter(typeof(Object[]));

    // To feed the constructor with the right parameters, we need to generate an array
    // of parameters that will be read from the initialize object array argument.
    var constructorParameters = parameters.Select((paramType, index) =>
        // convert the object[index] to the right constructor parameter type.
        Expression.Convert(
            // read a value from the object[index]
            Expression.ArrayAccess(
                paramExpr,
                Expression.Constant(index)),
            paramType)).ToArray();

    // just call the constructor.
    var body = Expression.New(constructorInfo, constructorParameters);

    var constructor = Expression.Lambda<ConstructorDelegate>(body, paramExpr);
    return constructor.Compile();
}

MyClass示例:

1
2
3
4
5
6
7
8
9
10
11
public class MyClass
{
    public int TestInt { get; private set; }
    public string TestString { get; private set; }

    public MyClass(int testInt, string testString)
    {
        TestInt = testInt;
        TestString = testString;
    }
}

用途:

1
2
3
4
5
// you should cache this 'constructor'
var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10,"test message");

enter image description here

另一个示例:将类型作为数组传递

1
2
3
4
5
6
7
8
var type = typeof(MyClass);
var args = new Type[] { typeof(int), typeof(string) };

// you should cache this 'constructor'
var myConstructor = CreateConstructor(type, args);

// Call the `myConstructor` fucntion to create a new instance.
var myObject = myConstructor(10,"test message");

表达式的DebugView

1
2
3
4
5
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
    .New TestExpressionConstructor.MainWindow+MyClass(
        (System.Int32)$var1[0],
        (System.String)$var1[1])
}

这相当于生成的代码:

1
2
3
4
5
6
public object myConstructor(object[] var1)
{
    return new MyClass(
        (System.Int32)var1[0],
        (System.String)var1[1]);
}

小下坡

当所有的valuetypes参数像对象数组一样传递时,它们都被装箱。

简单性能测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
private void TestActivator()
{
    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = Activator.CreateInstance(typeof(MyClass), 10,"test message");
    }
    sw.Stop();
    Trace.WriteLine("Activator:" + sw.Elapsed);
}

private void TestReflection()
{
    var constructorInfo = typeof(MyClass).GetConstructor(new[] { typeof(int), typeof(string) });

    Stopwatch sw = Stopwatch.StartNew();
    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = constructorInfo.Invoke(new object[] { 10,"test message" });
    }

    sw.Stop();
    Trace.WriteLine("Reflection:" + sw.Elapsed);
}

private void TestExpression()
{
    var myConstructor = CreateConstructor(typeof(MyClass), typeof(int), typeof(string));

    Stopwatch sw = Stopwatch.StartNew();

    for (int i = 0; i < 1024 * 1024 * 10; i++)
    {
        var myObject = myConstructor(10,"test message");
    }

    sw.Stop();
    Trace.WriteLine("Expression:" + sw.Elapsed);
}

TestActivator();
TestReflection();
TestExpression();

结果:

1
2
3
Activator: 00:00:13.8210732
Reflection: 00:00:05.2986945
Expression: 00:00:00.6681696

使用Expressions比调用ConstructorInfo快+/-8倍,比使用Activator快+/-20倍。


如果只想用构造函数参数初始化成员字段或属性,在c>=3中,可以非常容易地做到:

1
2
3
4
5
6
7
8
9
10
public static string GetAllItems<T>(...) where T : InterfaceOrBaseClass, new()
{
   ...
   List<T> tabListItems = new List<T>();
   foreach (ListItem listItem in listCollection)  
   {
       tabListItems.Add(new T{ BaseMemberItem = listItem }); // No error, BaseMemberItem owns to InterfaceOrBaseClass.
   }  
   ...
}

这和加里·舒特勒说的是同一件事,但我想做一个补充说明。

当然,您可以使用属性技巧来做更多的事情,而不仅仅是设置字段值。属性"set()"可以触发设置其相关字段所需的任何处理,以及对对象本身的任何其他需要,包括检查是否要在使用对象之前进行完全初始化,以模拟完整的构造(是的,这是一个丑陋的解决方法,但它克服了m$的new()限制)。

我不能保证这是一个计划中的洞还是一个意外的副作用,但它是有效的。

很有趣的是,微软人如何为这门语言添加新功能,似乎没有做全面的副作用分析。这一切都是很好的证据…


您需要添加where t:new(),让编译器知道t保证提供默认的构造函数。

1
public static string GetAllItems<T>(...) where T: new()


我发现我遇到一个错误"创建类型参数t的实例时无法提供参数",因此我需要执行以下操作:

1
var x = Activator.CreateInstance(typeof(T), args) as T;

如果您有权访问将要使用的类,那么可以使用我使用的这种方法。

创建具有备用创建者的接口:

1
2
3
4
public interface ICreatable1Param
{
    void PopulateInstance(object Param);
}

使用空的创建者创建类并实现此方法:

1
2
3
4
5
6
7
8
public class MyClass : ICreatable1Param
{
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        //populate the class here
    }
}

现在使用通用方法:

1
2
3
4
5
6
public void MyMethod<T>(...) where T : ICreatable1Param, new()
{
    //do stuff
    T newT = new T();
    T.PopulateInstance(Param);
}

如果您没有访问权限,请包装目标类:

1
2
3
4
5
6
7
8
9
public class MyClass : ICreatable1Param
{
    public WrappedClass WrappedInstance {get; private set; }
    public MyClass() { //do something or nothing }
    public void PopulateInstance (object Param)
    {
        WrappedInstance = new WrappedClass(Param);
    }
}

我有时使用一种类似于使用属性注入的答案的方法,但保持代码的整洁。它不包含具有一组属性的基类/接口,而只包含一个(虚拟的)initialize()-方法,该方法充当"穷人的构造函数"。然后,您可以让每个类像构造函数那样处理自己的初始化,这也增加了处理继承链的方便方法。

如果经常遇到这样的情况,我希望链中的每个类初始化其唯一属性,然后调用其父级的initialize()-方法,该方法依次初始化父级的唯一属性等等。当具有不同的类,但具有类似的层次结构时,这尤其有用,例如映射到/从dto:s映射的业务对象。

使用通用字典进行初始化的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void Main()
{
    var values = new Dictionary<string, int> { {"BaseValue", 1 }, {"DerivedValue", 2 } };

    Console.WriteLine(CreateObject<Base>(values).ToString());

    Console.WriteLine(CreateObject<Derived>(values).ToString());
}

public T CreateObject<T>(IDictionary<string, int> values)
    where T : Base, new()
{
    var obj = new T();
    obj.Initialize(values);
    return obj;
}

public class Base
{
    public int BaseValue { get; set; }

    public virtual void Initialize(IDictionary<string, int> values)
    {
        BaseValue = values["BaseValue"];
    }

    public override string ToString()
    {
        return"BaseValue =" + BaseValue;
    }
}

public class Derived : Base
{
    public int DerivedValue { get; set; }

    public override void Initialize(IDictionary<string, int> values)
    {
        base.Initialize(values);
        DerivedValue = values["DerivedValue"];
    }

    public override string ToString()
    {      
        return base.ToString() +", DerivedValue =" + DerivedValue;
    }
}


这是一种肮脏的东西,当我说这种肮脏的东西时,我的意思可能是反感,但是假设你可以为参数化类型提供一个空的构造函数,那么:

1
2
3
4
5
6
public static T GetTInstance<T>() where T: new()
{
    var constructorTypeSignature = new Type[] {typeof (object)};
    var constructorParameters = new object[] {"Create a T"};
    return (T) new T().GetType().GetConstructor(constructorTypeSignature).Invoke(constructorParameters);
}

将有效地允许您使用参数化类型构造对象。在这种情况下,我假设我想要的构造函数只有一个object类型的参数。我们使用约束允许的空构造函数创建了一个T的虚拟实例,然后使用反射来获取它的另一个构造函数。


我相信您必须用WHERE语句来约束T,以便只允许使用新构造函数的对象。

现在它接受任何东西,包括没有它的对象。