如何使用Activator.CreateInstance创建一个List <t>,其中T在运行时未知?

时间:2018-09-11 21:50:34

标签: c# reflection activator

我正在使用Activator.CreateInstance通过类型变量(在运行时未知)创建对象:

static dynamic CreateFoo( Type t ) =>
    Activator.CreateInstance( t );

很显然,我还没有正确理解如何使用dynamic类型,因为这仍然只是返回一个对象。

我需要能够将集合传递给另一个对Activator.CreateInstance的调用,其中正在创建的类型可能是List<T>

var values = Enumerable.Range( 1, 50 ).Select(
    I => CreateFoo( typeof( Foo ) ) ).ToArray( );
//This will explode.
var foobar = Activator.CreateInstance(
    typeof( List<Foo> ), values );

调用上述方法时,它会爆炸,但以下情况除外:

enter image description here

我知道为什么这样做-当使用类型实参定义列表时,列表的构造函数没有可枚举的对象。

问题是我无法转换对象,因为我在运行时不知道类型。 Activator.CreateInstance似乎只会返回一个对象,这对List<Foo>很好,因为我将使用Dependency Objects和Properties对其进行设置,因此,装箱的对象对它们而言完全合适,但是在尝试时会破坏所有内容创建列表(可能还有其他构造函数需要类型参数的东西)。

什么是我要在这里完成的正确方法?

符合Minimal, Complete and Verifiable Example要求:

using System;
using System.Collections.Generic;
using System.Linq;

namespace MCVEConsole {
    class Program {
        static int Main( string[ ] args ) {
            var values = Enumerable.Range( 1, 50 ).Select(
                I => CreateFoo( typeof( Foo ) ) ).ToArray( );

            //This will compile, but explode when run.
            var foobar = Activator.CreateInstance(
                typeof( List<Foo> ), values );
            return 1;
        }

        static dynamic CreateFoo( Type t ) =>
            Activator.CreateInstance( t );
    }

    class Foo {
        public Foo( ) { }
    }
}

2 个答案:

答案 0 :(得分:6)

  

很显然,我还没有正确理解如何使用动态类型,因为它仍然只是返回一个对象。

是的,您不理解。 dynamic 只不过是戴着花哨帽子的对象。返回类型为dynamic的意思是“我返回了object,但是调用者应允许对返回的对象执行通常在object上不允许的操作,并且这些操作将在运行时解析”

如果这不是您的意思,那么请勿使用dynamic

  

我知道为什么这样做-当使用类型实参定义列表时,列表的构造函数没有可枚举的对象。

正确。在您的代码中:

var values = Enumerable.Range( 1, 50 ).Select(
    I => CreateFoo( typeof( Foo ) ) ).ToArray( );

valuesdynamic[],它又是object[],上面戴着滑稽的帽子。因此,您正在使用List<Foo>而不是需要的object[]来调用IEnumerable<Foo>的构造函数。

  

什么是我要在这里完成的正确方法?

正确的 要做的是停止使用静态类型的语言来进行动态类型的工作,但我怀疑这将是一个不起眼的事情。

您手中有一个object[]。您需要的是t[],其中t是反射提供的类型。 解决反射问题的方法几乎总是使用更多的反射。这样吧。不要这样做:

var values = Enumerable.Range( 1, 50 ).Select(
    I => CreateFoo( typeof( Foo ) ) ).ToArray( );

那用反射不够。这样做:

object foos = Array.CreateInstance(typeof(Foo), 50);

太好了。现在您手头有一个Foo[50],可以再次使用反射将其填入 ,或将其制成dynamic并使用动态运行时来调度数组索引;我假设您可以编写该代码。如果不能,请再问一个问题。

现在您有一个Foo[]填充的IEnumerable<Foo>,然后可以将其传递给List<Foo>构造函数。

您为什么要这么做-创建Foo[]以获得IEnumerable<Foo>以获得List<Foo>,其内容与Foo[]相同-我不知道。在制作List<Foo>()且不向构造函数添加任何参数并直接填充它的简单步骤中,这似乎太多步骤。但这是您提出的问题,所以这就是已回答的问题。

这听起来很像我们所说的“ XY问题”。您对如何解决问题有一些疯狂的想法,而您在问的是疯狂的想法,而不是在问问题,因此答案本身就有点疯狂。真正的问题是什么?

答案 1 :(得分:2)

使用这种方法:

class Program
{
    static void Main(string[] args)
    {
        CreateListFromType(typeof(Foo));
        CreateListFromType(typeof(int));
    }

    static object CreateListFromType(Type t)
    {
        // Create an array of the required type
        Array values = Array.CreateInstance(t, 50);

        // and fill it with values of the required type
        for (int i = 0; i < 50; i++)
        {
            values.SetValue(CreateFooFromType(t), i);
        }

        // Create a list of the required type, passing the values to the constructor
        Type genericListType = typeof(List<>);
        Type concreteListType = genericListType.MakeGenericType(t);

        object list = Activator.CreateInstance(concreteListType, new object[] { values }); 

        // DO something with list which is now an List<t> filled with 50 ts
        return list;
    }


    // Create a value of the required type
    static object CreateFooFromType(Type t)
    {
        return Activator.CreateInstance(t);
    }
}

class Foo
{
    public Foo() { }
}

在这种情况下,无需使用dynamic。我们可以仅使用object作为我们创建的值。非引用类型将使用框存储在object中。

为了创建List<>类型,我们首先可以获取泛型类型的表示形式,然后使用MakeGenericType方法使用泛型类型来创建具体类型。

请注意您在CreateInstance调用中创建列表时犯的错误:

在尝试构建列表时,您需要将values数组作为元素嵌入到对象数组中。这样Activator会在List<t>中寻找一个构造器,该构造器需要一个类型为IEnumerable<t>的单个参数。

按照您编写它的方式,Activator寻找一个构造函数,该构造函数需要50个参数,每个参数的类型均为t。


使用非通用IList接口的较短版本

using System.Collections;

static IList CreateListFromType(Type t)
{
    // Create a list of the required type and cast to IList
    Type genericListType = typeof(List<>);
    Type concreteListType = genericListType.MakeGenericType(t);
    IList list = Activator.CreateInstance(concreteListType) as IList;

    // Add values
    for (int i = 0; i < 50; i++)
    {
        list.Add(CreateFooFromType(t));
    }

    // DO something with list which is now an List<t> filled with 50 ts
    return list;
}

更接近实际用例:动态列表类型

static void Main(string[] args)
{
    CreateListFromType(typeof(List<Foo>));
    CreateListFromType(typeof(ObservableCollection<int>));
}

static IList CreateListFromType(Type listType)
{
    // Check we have a type that implements IList
    Type iListType = typeof(IList);
    if (!listType.GetInterfaces().Contains(iListType))
    {
        throw new ArgumentException("No IList", nameof(listType));
    }

    // Check we have a a generic type parameter and get it
    Type elementType = listType.GenericTypeArguments.FirstOrDefault();
    if (elementType == null)
    {
        throw new ArgumentException("No Element Type", nameof(listType));
    }

    // Create a list of the required type and cast to IList
    IList list = Activator.CreateInstance(listType) as IList;

    // Add values
    for (int i = 0; i < 50; i++)
    {
        list.Add(CreateFooFromType(elementType));
    }

    // DO something with list which is now a filled object of type listType 
    return list;
}