451

添加到列表时,我试图通过其构造函数创建一个类型为 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.
   } 
   ...
}
4

15 回答 15

434

为了在函数中创建泛型类型的实例,您必须使用“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));
于 2009-05-08T15:11:04.287 回答
360

在 .Net 3.5 和之后您可以使用激活器类:

(T)Activator.CreateInstance(typeof(T), args)
于 2011-04-08T18:02:03.380 回答
54

由于没有人费心发布“反思”答案(我个人认为这是最好的答案),所以这里是:

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

于 2010-06-16T15:39:06.843 回答
33

对象初始化器

如果带有参数的构造函数除了设置属性之外没有做任何事情,则可以在 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
   } 
   ...
}

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

激活器.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 可能会产生一些性能开销,您可能希望避免这些开销。

于 2012-02-22T20:39:40.100 回答
32

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

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

正如Welly 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");

在此处输入图像描述


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

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

小缺点

所有值类型参数在像对象数组一样传递时都会被装箱。


简单的性能测试:

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+/- 8 倍,比使用快ConstructorInfo+/- 20 倍Activator

于 2016-10-10T15:07:58.390 回答
20

这在您的情况下不起作用。您只能指定它具有空构造函数的约束:

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

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

于 2009-05-08T15:13:01.207 回答
8

如果您只是想使用构造函数参数初始化成员字段或属性,在 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. 
   }  
   ... 
} 

这与加里·舒特勒所说的相同,但我想补充一点。

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

我无法确定这是计划中的漏洞还是意外的副作用,但它确实有效。

很有趣的是,MS 人如何为语言添加新功能并且似乎没有进行完整的副作用分析。整个通用的东西就是一个很好的证据......

于 2010-04-30T17:45:53.223 回答
7

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

public static string GetAllItems<T>(...) where T: new()
于 2009-05-08T15:06:56.073 回答
7

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

创建一个具有替代创建者的界面:

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);
    }
}
于 2017-07-13T15:04:51.480 回答
6

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

var x = Activator.CreateInstance(typeof(T), args) as T;
于 2015-11-06T11:13:17.563 回答
0

这有点糊涂,当我说有点糊涂时,我的意思可能是令人反感,但是假设您可以为参数化类型提供一个空的构造函数,那么:

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

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

于 2010-11-12T11:38:39.893 回答
0

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

如果经常发现自己处于希望链中的每个类初始化其唯一属性的情况,然后调用其父级的 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;
    }
}
于 2017-01-30T13:53:07.557 回答
0

如果您只需要从 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);
    } 
    ...
}
于 2019-10-01T10:20:39.157 回答
-1

补充性能信息

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

结论:表达方式较快。

结果:

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

表达式方法的代码示例:

var list = ReadList<DataModel>(SQLStatement, Connection);

方法 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();
            }
于 2021-08-01T18:49:25.690 回答
-4

我相信您必须使用 where 语句来约束 T 以仅允许具有新构造函数的对象。

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

于 2009-05-08T15:08:05.120 回答