Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

I'm using Activator.CreateInstance to create objects by a type variable (unknown during run time):

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

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

I need to be able to pass a collection to another call to Activator.CreateInstance where the type being created could be a 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 );

When the above is called, it explodes with the following exception:

enter image description here

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.

The problem is that I cannot cast the objects because I don't know the type at runtime. Activator.CreateInstance only ever seems to return an object, which is fine for the List<Foo> because I'll be setting them using Dependency Objects and Properties, so boxed objects are perfectly fine for them, but breaks everything when trying to create a list ( and, likely, anything else with a constructor expecting a type argument ).

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

In compliance with the Minimal, Complete and Verifiable Example requirements:

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( ) { }
    }
}
like image 314
Will Avatar asked Sep 11 '18 21:09

Will


2 Answers

Use this approach:

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

There is no need to use dynamic in this case. We can just use object for the value we create. Non-Reference types will be stored in the object using boxing.

In order to create the List<> type, we can first get a representation of the generic type and then use that to create the concrete type using the MakeGenericType method.

Note the mistake you made in the CreateInstance call to create the list:

When trying to construct the list, you need to embed your values array as an element in an array of object. So that Activator will look for a constructor in List<t> that expects a single parameter of type IEnumerable<t>.

The way you have written it, the Activator looks for a constructor which expects 50 arguments, each of type t.


A shorter version using non-generic IList interface

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

Getting closer to the actual use case: Dynamic List Type

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;
}
like image 166
NineBerry Avatar answered Nov 09 '22 23:11

NineBerry


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

Yes, you do not understand it. dynamic is nothing more than object with a fancy hat on. A return type of dynamic means "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".

If that's not precisely what you mean, then do not use 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.

Correct. In your code:

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

values is dynamic[], which again, is just object[] with a funny hat on. So you are calling the constructor of List<Foo> with an object[], not an IEnumerable<Foo> as is required.

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

The proper thing to do is to stop using a statically-typed language to do dynamically-typed work, but I suspect that will be a non-starter.

You have an object[] in hand. What you want is a t[] where t is a type supplied by reflection. The solution to a problem with Reflection is almost always to use more Reflection. So do that. Do not do this:

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

That doesn't use reflection enough. Do this:

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

Great. Now you have a Foo[50] in hand, and you can fill it again using reflection, or make it dynamic and use the dynamic runtime to dispatch the array indexing; I assume you can write that code. If you cannot, ask another question.

Now you have a filled Foo[] which is IEnumerable<Foo>, which you can then pass to the List<Foo> constructor.

Why on earth you would do that -- create a Foo[] to get an IEnumerable<Foo> to get a List<Foo> that has the same contents as the Foo[] -- I do not know. That seems like far too many steps over the much easier step of making a List<Foo>() with no arguments to the constructor and filling it directly! But that's the question you asked, and so that's the question that got answered.

This sounds a lot like what we call an "XY question". You have some crazy idea about how to solve your problem and you are asking about the crazy idea instead of asking about the problem, and so the answer is a bit crazy itself. What's the real problem?

like image 30
Eric Lippert Avatar answered Nov 10 '22 00:11

Eric Lippert