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 |
为了在函数中创建泛型类型的实例,必须用"new"标志对其进行约束。
1 |
但是,只有当您想要调用没有参数的构造函数时,这才有效。这里不是这样。相反,您必须提供另一个允许基于参数创建对象的参数。最简单的是函数。
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 |
在.NET 3.5和之后,您可以使用Activator类:
1 |
由于没有人费心发布"反思"答案(我个人认为这是最好的答案),下面是:
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 |
使用此方法,您也可以将任何构造函数逻辑放入默认(空)构造函数中。
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 |
您可以通过定义这个接口来使用属性注入:
1 2 3 4 | public interface ITakesAListItem { ListItem Item { set; } } |
然后您可以将您的方法更改为:
1 2 3 4 5 6 7 8 9 10 |
另一种选择是Jaredpar描述的
旧问题,新答案;-)
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 |
另一个示例:将类型作为数组传递
1 2 3 4 5 6 7 8 |
表达式的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 |
使用
如果只想用构造函数参数初始化成员字段或属性,在c>=3中,可以非常容易地做到:
1 2 3 4 5 6 7 8 9 10 |
这和加里·舒特勒说的是同一件事,但我想做一个补充说明。
当然,您可以使用属性技巧来做更多的事情,而不仅仅是设置字段值。属性"set()"可以触发设置其相关字段所需的任何处理,以及对对象本身的任何其他需要,包括检查是否要在使用对象之前进行完全初始化,以模拟完整的构造(是的,这是一个丑陋的解决方法,但它克服了m$的new()限制)。
我不能保证这是一个计划中的洞还是一个意外的副作用,但它是有效的。
很有趣的是,微软人如何为这门语言添加新功能,似乎没有做全面的副作用分析。这一切都是很好的证据…
您需要添加where t:new(),让编译器知道t保证提供默认的构造函数。
1 |
我发现我遇到一个错误"创建类型参数t的实例时无法提供参数",因此我需要执行以下操作:
1 |
如果您有权访问将要使用的类,那么可以使用我使用的这种方法。
创建具有备用创建者的接口:
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 |
如果您没有访问权限,请包装目标类:
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 |
将有效地允许您使用参数化类型构造对象。在这种情况下,我假设我想要的构造函数只有一个
我相信您必须用WHERE语句来约束T,以便只允许使用新构造函数的对象。
现在它接受任何东西,包括没有它的对象。