将参数传递给模板类型的C#通用new()


409

添加到列表中时,我正在尝试通过其构造函数创建类型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.
   } 
   ...
}


2
关于将此功能转化为语言的建议:github.com/dotnet/roslyn/issues/2206
Ian Kemp

在Microsoft的文档中,请参阅Compiler Error CS0417
DavidRR

1
将此功能转换为语言的建议已移至:github.com/dotnet/csharplang/issues/769
减少活动

Answers:


410

为了在函数中创建泛型类型的实例,必须使用“ 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));

从泛型类内部调用时,这将如何工作?我已经在下面的答案中发布了我的代码。我在内部不知道具体的类,因为它是一个泛型类。有没有办法解决这个问题。我不想使用使用属性初始化程序语法的其他建议,因为它将绕过我在构造函数中的逻辑
ChrisCa

加入我的代码的另一个问题stackoverflow.com/questions/1682310/...
ChrisCa

21
当前,这是C#最令人讨厌的限制之一。我想使我的类不可变:仅使用私有setter会使类不可能因副作用而处于无效状态。我也喜欢使用Func和lambda,但是我知道在商业世界中这仍然是一个问题,因为通常程序员还不知道lambda,这会使您的课程更难以理解。
Tuomas Hietanen 09年

1
谢谢。就我而言,当我调用该方法时,我知道构造函数的参数,我只需要解决Type参数无法使用参数构造的限制,所以我使用了thunk。thunk是方法的可选参数,我仅在提供时使用它:T result = thunk == null ? new T() : thunk(); 对我来说,这样做的好处是将T创建的逻辑整合在一个地方,而不是有时T在方法内部,有时在方法外部。
卡尔·G

我认为这些是C#语言决定对程序员说不的地方,并一直禁止回答!尽管这种方法创建对象有点尴尬,但是我现在必须使用它。
AmirHossein Rezaei,

331

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

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

1
我们还可以使用表达式树来构建对象
Welly Tambunan

4
什么是参数?一个东西[]?
罗德尼·巴巴蒂

3
是的,args是一个object [],您可以在其中指定要提供给T的构造函数的值:“ new object [] {par1,par2}”
TechNyquist 2013年


3
警告:如果仅出于此目的而拥有专用的构造函数Activator.CreateInstance,则看起来您的构造函数根本没有使用,有人可能会尝试“清理”并删除它(在运行时导致错误)将来的随机时间)。您可能要考虑在使用此构造函数的地方添加一个伪函数,以便在尝试删除它时收到编译错误。
jrh

51

由于没有人愿意张贴“思考”答案(我个人认为这是最佳答案),因此:

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


我的理解是,大多数性能下降是首先获取了ConstructorInfo。如果不对它进行概要分析,请不要相信我的意思。如果真是这样,只需存储ConstructorInfo供以后重用,就可以减轻通过反射进行重复实例化的性能影响。
Kelsie 2012年

19
我认为缺少编译时检查更值得关注。
Dave Van den Eynde 2012年

1
@詹姆斯我同意,我很惊讶没有将此作为“答案”。实际上,我搜索了这个问题,希望找到一个很好的简单示例(例如您的示例),因为自从进行反思以来已经有很长时间了。无论如何,我是+1,但Activator也是+1。我研究了Activator的功能,结果发现该功能是经过精心设计的反映。:)
Mike

GetConstructor()调用很昂贵,因此值得在循环之前进行缓存。这样,通过在循环内仅调用Invoke(),比调用两者甚至使用Activator.CreateInstance()都快得多。
Cosmin Rus

30

对象初始化器

如果带有参数的构造函数除了设置属性之外没有做任何事情,您可以在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保护它的不变式(给定T具有> 0依赖性或必需值的变量,现在您可以创建T处于无效/不可用状态的实例。除非T像DTO och viewmodel这样简单的东西,否则我要避免这种情况。
萨拉2016年

20

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

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

不足之处

当将所有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 +/- 8倍ConstructorInfo并且比使用 +/- 20倍Activator


如果要使用构造函数public MyClass(T data)构造MyClass <T>,您对如何做有任何见解。在这种情况下,Expression.Convert会引发异常,如果我使用通用约束基类进行转换,则Expression.New会引发异常,因为构造函数信息适用于通用类型
Mason

@Mason(花点时间回答;-)), var myConstructor = CreateConstructor(typeof(MyClass<int>), typeof(int));这很好。我不知道。
Jeroen van Langen

19

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

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描述的方法。


这会绕过构造函数中采用参数的任何逻辑,对吗?我想做类似Jared的方法,但是在类中内部调用该方法,所以不知道具体的类型是什么...
hmmm

3
正确,这将调用T()默认构造函数的逻辑,然后只需将属性设置为“ Item”。如果您尝试调用非默认构造函数的逻辑,则将无济于事。
斯科特·斯塔福德,2010年

7

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

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

1
UPDATE:正确的错误信息是:“T”:创建变量的实例时不能提供的参数
LB.

那是因为您没有使用空白的构造函数,而是向对象传递了一个参数。如果不指定泛型类型具有new(object)参数,就无法处理它。

然后,您将需要:1.使用反射2.将参数传递给初始化方法而不是构造函数,该初始化方法属于您的类型实现的接口,并且该接口包含在where T:...中宣言。选项1对其余代码的影响最小,但是选项2提供了编译时检查。
理查德

不要使用反射!其他答案中概述的其他方法也可以给您带来相同的效果。
加里·舒特勒

@Garry-我同意反射不一定是最好的方法,但是它确实允许您以最少的代码其余部分更改来实现所需的功能。也就是说,我非常喜欢@JaredPar的工厂委托方法。
理查德

7

如果您只想使用构造函数参数初始化成员字段或属性,则在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()限制)。

我不能确定这是计划的孔还是意外的副作用,但它可以工作。

MS人士如何在语言中添加新功能却似乎并没有进行完整的副作用分析,这非常有趣。整个通用的东西很好地证明了这一点...


1
这两个约束都是必需的。InterfaceOrBaseClass使编译器知道字段/属性BaseMemberItem。如果注释了“ new()”约束,它将触发错误:错误6无法创建变量类型'T'的实例,因为它没有new()约束
fljx '04

我遇到的情况与此处提出的问题并不完全相同,但是此答案将我带到了我需要去的地方,并且看来效果很好。
RubyHaus 2010年

5
每当有人称微软为“ M $”时,我的灵魂就会遭受一小部分痛苦。
Mathias Lykkegaard Lorenzen 2015年

6

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

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

5

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

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

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

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


0

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

如果经常遇到这样的情况,我希望链中的每个类都初始化其唯一属性,然后调用其父级的Initialize()方法,该方法又会初始化父级的唯一属性,依此类推。当具有不同的类但具有相似的层次结构时(例如,映射到DTO或从DTO映射的业务对象),这特别有用。

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

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

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

-4

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

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


1
您可能想更改此答案,因为在您回答后此答案已被编辑为问题,从而使该答案脱离上下文。
穿梭车
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.