首页 文章

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

提问于
浏览
366

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

13 回答

  • 6

    这不适用于您的情况 . 您只能指定它具有空构造函数的约束:

    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 方法 .

  • 2

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

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

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

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

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

    我有时使用类似于使用属性注入的答案的方法,但保持代码更清洁 . 它不包含具有一组属性的基类/接口,而只包含一个(虚拟)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;
        }
    }
    
  • 373

    这有点肮脏,当我说肮脏时我可能意味着反感,但假设你可以用空构造函数提供参数化类型,那么:

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

  • 7

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

    现在它接受任何包括没有它的物体的东西 .

  • 0

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

    The ExpressionTree version :(我认为紧固和最干净的解决方案)

    就像Welly Tambunan说的那样,"we could also use expression tree to build the object"

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

    这里是:

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

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

    Usage:

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

    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 of Expression

    .Lambda #Lambda1<TestExpressionConstructor.MainWindow+ConstructorDelegate>(System.Object[] $var1) {
        .New TestExpressionConstructor.MainWindow+MyClass(
            (System.Int32)$var1[0],
            (System.String)$var1[1])
    }
    

    This is equivalent to the code that is generated:

    public object myConstructor(object[] var1)
    {
        return new MyClass(
            (System.Int32)var1[0],
            (System.String)var1[1]);
    }
    

    Small downside

    所有valuetypes参数在像对象数组一样传递时都会被装箱 .


    Simple performance test:

    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 times faster 比调用 ConstructorInfo 和/ - 20 times faster 比使用 Activator

  • 1

    Object initializer

    如果带有参数的构造函数除了设置属性之外没有做任何事情,你可以使用object initializer在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可以包含一些performance overhead,如果执行速度是最高优先级而另一个选项可以维护,则可能需要避免 .

  • 16

    如果您只想使用构造函数参数初始化成员字段或属性,则在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 $人们如何为语言添加新功能并且似乎没有进行完整的副作用分析,这非常有趣 . 整个通用的东西都是这个的好证据......

  • -4

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

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

    既然没有人愿意发布“反思”答案(我个人认为是最好的答案),这里有:

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

  • 50

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

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

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

相关问题