将parameter passing给模板types的C#genericsnew()
我试图通过它的构造函数创build一个typesT的新对象添加到列表中时。
我得到一个编译错误:错误信息是:
'T':创buildvariables实例时不能提供参数
但是我的类有一个构造参数! 我怎样才能做这个工作?
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. } ... }
为了在一个函数中创build一个genericstypes的实例,你必须用“new”标志来限制它。
public static string GetAllItems<T>(...) where T : new()
但是,只有当你想调用没有参数的构造函数时才能工作。 不是这里的情况。 相反,你将不得不提供另一个参数,允许基于参数创build对象。 最简单的是一个function。
public static string GetAllItems<T>(..., Func<ListItem,T> del) { ... List<T> tabListItems = new List<T>(); foreach (ListItem listItem in listCollection) { tabListItems.Add(del(listItem)); } ... }
你可以这样称呼它
GetAllItems<Foo>(..., l => new Foo(l));
在.Net 3.5中,你可以使用激活类:
(T)Activator.CreateInstance(typeof(T), args)
由于没有人打扰张贴'反思'的答案(我个人认为是最好的答案),这里是:
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或者更好的方法使用对象初始值设定项而不是调用构造函数(这是不可能的,如前所述):
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() :
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可能会有一些性能开销 ,如果执行速度是最高优先级,而另一个选项是可维护的,则可能需要避免。
这不适合你的情况。 你只能指定它有一个空的构造函数的约束:
public static string GetAllItems<T>(...) where T: new()
你可以做的是通过定义这个接口来使用属性注入:
public interface ITakesAListItem { ListItem Item { set; } }
那么你可以改变你的方法是这样的:
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 }); } ... }
另一种select是JaredPar描述的Func
方法。
很老的问题,但新的答案;-)
ExpressionTree版本 🙁 我认为是最简单和最干净的解决scheme)
像Welly Tambunan说的那样, “我们也可以用expression树来构build对象”
这将为给定的types/参数生成一个“构造函数”(function)。 它返回一个委托并接受参数types作为一个对象数组。
这里是:
// 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:
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; } }
用法:
// 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");
另一个例子:将数组作为数组传递
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");
Expression的DebugView
.Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) { .New TestExpressionConstructor.MainWindow+MyClass( (System.Int32)$var1[0], (System.String)$var1[1]) }
这相当于生成的代码:
public object myConstructor(object[] var1) { return new MyClass( (System.Int32)var1[0], (System.String)var1[1]); }
小的缺点
当所有的值types参数像对象数组一样被传递时,它们被装箱。
简单的性能testing:
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();
结果:
Activator: 00:00:13.8210732 Reflection: 00:00:05.2986945 Expression: 00:00:00.6681696
使用Expressions
比调用ConstructorInfo
快 +/- 8倍,比使用Activator
快 +/- 20倍
你需要添加T:new()来让编译器知道T保证提供一个默认的构造函数。
public static string GetAllItems<T>(...) where T: new()
如果您只是想用构造函数参数初始化成员字段或属性,那么在C#> = 3中,您可以非常容易:
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. } ... }
Garry Shutler说这是同样的事情,但我想提出一个附加说明。
当然,你可以使用属性技巧来做更多的事情,而不仅仅是设置一个字段值。 一个属性“set()”可以触发设置其相关字段所需的任何处理以及对象本身的任何其他需要,包括检查在使用对象之前是否发生完全初始化,模拟完整的构造是的,这是一个丑陋的解决方法,但它克服了M $的新()限制)。
我不能保证它是一个计划的洞或意外的副作用,但它的作品。
M $人为语言添加了新的function,而且似乎没有进行完整的副作用分析,这是非常有趣的。 整个通用的东西是这个的一个很好的证据…
我发现我得到一个错误“创buildtypes参数T的实例时不能提供参数”,所以我需要这样做:
var x = Activator.CreateInstance(typeof(T), args) as T;
我有时使用类似于使用属性注入的答案的方法,但保持代码更清洁。 它不包含具有一组属性的基类/接口,而只包含一个(虚拟)Initialize()方法,该方法充当“穷人的构造函数”。 然后你可以让每个类像构造函数一样处理它自己的初始化,这也增加了一个处理inheritance链的简单方法。
如果经常发现自己想要链中的每个类初始化其独特属性,然后调用其父类的Initialize()方法,然后初始化父类的唯一属性等等。 当具有不同的类时,这是特别有用的,但具有类似的层次结构,例如映射到/从DTO:s的业务对象。
使用常用字典进行初始化的示例:
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; } }
这是一种糟糕的,当我说的那种泥泞我可能意味着反抗,但是假设你可以提供你的参数化types一个空的构造函数,然后:
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); }
将有效地允许你从参数化types和参数构造一个对象。 在这种情况下,我假设我想要的构造函数只有一个typesobject
参数。 我们使用允许的空构造函数创build一个T的虚拟实例,然后使用reflection来获得其他构造函数之一。
如果能够访问您要使用的课程,您可以使用我使用的这种方法。
创build一个具有另一个创build者的界面:
public interface ICreatable1Param { void PopulateInstance(object Param); }
用空的创build者创build你的类并实现这个方法:
public class MyClass : ICreatable1Param { public MyClass() { //do something or nothing } public void PopulateInstance (object Param) { //populate the class here } }
现在使用你的通用方法:
public void MyMethod<T>(...) where T : ICreatable1Param, new() { //do stuff T newT = new T(); T.PopulateInstance(Param); }
我相信你必须使用where语句来限制T,只允许具有新构造函数的对象。
现在它接受任何东西,包括没有它的物体。