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

How can I use Activator.CreateInstance to create a List<T> where T is unknown at runtime?

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

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

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

我需要能够将集合传递给对 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 );

调用上面的代码时,它会爆炸并出现以下异常:

我明白它为什么这样做了——当用类型参数定义列表时,没有期望可枚举对象的列表的构造函数。

问题是我无法转换对象,因为我在 运行 时不知道类型。 Activator.CreateInstance 似乎只 return 一个对象,这对 List<Foo> 来说很好,因为我将使用依赖对象和属性来设置它们,所以盒装对象对它们来说非常合适,但是尝试创建列表时会破坏所有内容(并且可能会破坏任何其他具有期望类型参数的构造函数的内容)。

我在这里尝试完成的事情的正确方法是什么?

符合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( ) { }
    }
}

Obviously, I do not yet properly understand how to use the dynamic type, because this is still just returning an object.

是的,你不明白。 dynamic 只不过是戴着一顶花哨帽子的对象。 return 类型的 dynamic 表示 "I return object, but callers should allow operations on the returned object that would not normally be allowed on object, and those operations will be resolved at runtime".

如果这不是你的意思,那么不要使用dynamic

I get why it is doing that - there is no constructor for a list expecting an enumerable of objects when the list is defined with a type argument.

正确。在您的代码中:

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

valuesdynamic[],这又只是 object[] 戴着一顶滑稽的帽子。因此,您使用 object[] 调用 List<Foo> 的构造函数,而不是需要的 IEnumerable<Foo>

What is the proper method for what I am trying to get done here?

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

您手头有一个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> 来获得一个与 Foo[] 具有相同内容的 List<Foo> - - 我不知道。这似乎 太多步骤 比制作一个 List<Foo>() 没有参数的构造函数并直接填充它更简单的步骤!但这就是你问的问题,所以这就是得到回答的问题。

这听起来很像我们所说的 "XY question"。你对如何解决你的问题有一些疯狂的想法,你问的是这个疯狂的想法而不是问问题,所以答案本身就有点疯狂。真正的问题是什么?

使用这种方法:

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 调用中犯的错误:

尝试构建列表时,您需要将值数组作为一个元素嵌入到对象数组中。因此 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;
}