传递参数给模板化类型的c#泛型new()

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

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

'T':创建变量实例时不能提供参数

但是我的类确实有构造函数参数!我该怎么做呢?

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.
}
...
}
246912 次浏览

您需要在此处添加T: new(),以让编译器知道T保证提供默认构造函数。

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

我认为必须用where语句约束T,以只允许具有新构造函数的对象。

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

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

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

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

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));

这在你的情况下行不通。你只能指定一个构造函数为空的约束:

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 });
}
...
}

另一个替代方法是由JaredPar描述的Func方法。

如果你只是想用构造函数参数初始化一个成员字段或属性,在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$ new()的限制)

我不能确定这是一个计划好的漏洞还是一个意外的副作用,但它确实有效。

有趣的是,微软的人给语言添加了新功能,似乎没有做一个完整的副作用分析。 整个通用的东西就是一个很好的证据…

因为没有人愿意发表“反思”的答案(我个人认为这是最好的答案),下面是:

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版本中仍然有用。

这有点麻烦,当我说有点麻烦时,我可能是指讨厌,但假设你可以为你的参数化类型提供一个空构造函数,那么:

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的虚拟实例,然后使用反射获取它的其他构造函数之一。

在。net 3.5和之后,你可以使用activator类:

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

对象初始化器

如果你的带形参的构造函数除了设置属性之外没有做任何事情,你可以在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可以有一些性能开销,如果执行速度是最优先考虑的,而另一个选项对你来说是可维护的,你可能想要避免。

我发现我得到了一个错误“在创建类型参数T的实例时不能提供参数”,所以我需要这样做:

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

很老的问题,但新的答案;-)

表达式树版本: (我认为这是最快最干净的解决方案)

就像威利Tambunan说的,我们也可以使用表达式树来构建对象

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

下面就是:

// 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` function to create a new instance.
var myObject = myConstructor(10, "test message");

enter image description here


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

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

.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]);
}

小缺点

所有valuetypes参数在像对象数组一样传递时都是盒装的。


简单性能测试:

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倍

我有时使用类似于使用属性注入的答案的方法,但保持代码更清晰。 它没有一个带有一组属性的基类/接口,只包含一个(虚拟的)Initialize()方法,充当“穷人的构造函数”。 然后你可以让每个类像构造函数一样处理自己的初始化,这也增加了一种处理继承链的方便方式

If经常发现自己处于这样的情况:我希望链中的每个类初始化其唯一属性,然后调用其父类的initialize()方法,该方法反过来初始化父类的唯一属性,以此类推。当具有不同的类,但具有相似的层次结构时,这尤其有用,例如映射到DTO:s的业务对象。

使用普通Dictionary进行初始化的示例:

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;
}
}

如果你可以访问将要使用的类,你可以使用我使用的这种方法。

创建一个具有替代创建者的接口:

public interface ICreatable1Param
{
void PopulateInstance(object Param);
}

让你的类有一个空的创建器并实现这个方法:

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);
}

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

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);
}
}

如果您所需要的只是从ListItem转换到类型T,则可以在T类中作为转换操作符实现此转换。

public class T
{
public static implicit operator T(ListItem listItem) => /* ... */;
}


public static string GetAllItems(...)
{
...
List<T> tabListItems = new List<T>();
foreach (ListItem listItem in listCollection)
{
tabListItems.Add(listItem);
}
...
}

补充业绩资料

使用Jeroen van Langen的表达式方法(见上文)对数据库访问和数据模型类填充进行性能测试,并直接实例化数据模型类。

结论:该法表达速度快。

结果:

    数据模型类的直接实例: 记录:3558,秒:1.2746019
  1. 测试:实例的方法读取列表类型参数: 记录:3558,秒:0.4878858

表达式方法的代码示例:

var list = ReadList<DataModel>(SQLStatement, Connection);
< p >方法ReadList: 注意:所有数据模型类都有一个参数类型为SQLDataReader

的构造函数
public static List<pDataModel> ReadList<pDataModel>(string pSQLStatement, SqlConnection pConnection) where pDataModel : new()
{
// constructor of data model
var lType = typeof(pDataModel);
var lParameters = new Type[] { typeof(SqlDataReader) };
var lDataModelConstructor = CreateConstructor(lType, lParameters);


// read data
List<pDataModel> lDataList = new List<pDataModel>();
using (pConnection)
{
SqlCommand lCommand;
lCommand = new SqlCommand(pSQLStatement, pConnection);
pConnection.Open();
SqlDataReader lReader = lCommand.ExecuteReader();




if (lReader.HasRows)
{
while (lReader.Read())
{
pDataModel lDataItem = (pDataModel)lDataModelConstructor(lReader);
lDataList.Add(lDataItem);
}
}
lReader.Close();
pConnection.Close();
}


return lDataList;
}

直接实例化的代码示例:

           List<DataModel> list= new List<DataModel>();
using (connection)
{
SqlCommand command;
command = new SqlCommand(SQLStatement, connection);
connection.Open();
SqlDataReader reader = command.ExecuteReader();
if (reader.HasRows)
{
while (reader.Read())
{
list.Add(new DataModel(reader));
}
}
reader.Close();
connection.Close();
}

使用c# 11中的静态抽象接口方法特性,可以使用工厂模式构建一个变通方案。例如:

public interface IFactory<TSelf> where TSelf : IFactory<TSelf> {
static abstract TSelf New(int i, string s);
}


public struct Foo : IFactory<Foo> {


public static Foo New(int i, string s) {
return new Foo(i, s);
}


public readonly int I;
public readonly string S;


public Foo(int i, string s) {
I = i;
S = s;
}
}


public static class Maker {
public static T Make<T>(int i, string s) where T : IFactory<T> {
return T.New(i, s);
}
}

这种方法的一个限制是它只能用于您拥有的类,因为您需要在目标类上实现特定的工厂接口。