stop-cran
stop-cran

Reputation: 4408

C# Reflection - MakeGenericType for recursive type parameters

At some place of my project I need to make a concrete generic type, taking as arguments a generic type definition (with a single parameter) and a type of that parameter.

For this purpose I've written a method, which is pretty simple:

Type MakeGenericType(Type definition, Type parameter)
{
    return definition.MakeGenericType(parameter);
}

However, at some point, I need to create a type, say, List<List<T>> with given element type T. Although I'm able to create a type List<List<T>> using my method, subsequent attempt to make a concrete type List<List<int>> from it fails - see code below:

var genericList = MakeGenericType(typeof(List<>), typeof(List<>)); // success

MakeGenericType(genericList, typeof(int)); // exception

An unhandled exception of type 'System.InvalidOperationException' occurred in mscorlib.dll

Additional information: System.Collections.Generic.List`1[System.Collections.Generic.List`1[T]] is not a GenericTypeDefinition. MakeGenericType may only be called on a type for which Type.IsGenericTypeDefinition is true.

Moreover, following call won't even compile:

MakeGenericType(typeof(List<List<>>), typeof(int));

I've checked this question regarding difference between IsGenericTypeDefinition and ContainsGenericParameters. However, I still don't have an idea, how to deal with type objects like genericList.

Apparently, using reflection I can construct a type object, which is nothing to do about it - that's very confusing to me.

So the question is, how can I create a concrete type from a generic, which contains generic type definition as a parameter? Is it possible at all?

Upvotes: 3

Views: 3601

Answers (3)

stop-cran
stop-cran

Reputation: 4408

In addition to Ivan Stoev's answer, here's a version of MakeGenericType that can produce and process "partially defined" generic types:

Type MakeGenericType(Type type, Type parameter)
{
    if (!type.ContainsGenericParameters)
        throw new ArgumentException(nameof(type));

    bool replaced = false;

    return MakeGenericType(type, parameter, ref replaced);
}

Type MakeGenericType(Type type, Type parameter, ref bool replaced)
{
    if (type.IsGenericParameter)
        if (replaced)
            return type;
        else
        {
            replaced = true;
            return parameter;
        }

    if (type.IsGenericTypeDefinition)
    {
        var parameters = type.GetTypeInfo().GenericTypeParameters.ToArray();

        parameters[0] = parameter;
        replaced = true;

        return type.MakeGenericType(parameters);
    }

    if (type.IsGenericType && type.ContainsGenericParameters)
    {
        var parameters = type.GenericTypeArguments.ToArray();

        for (int i = 0; i < parameters.Length; i++)
            parameters[i] = MakeGenericType(parameters[i], parameter, ref replaced);

        return type
            .GetGenericTypeDefinition()
            .MakeGenericType(parameters);
    }

    return type;
}

Following calls succeed:

var d0 = MakeGenericType(typeof(Dictionary<,>), typeof(Tuple<,>)); // Dictionary`2[Tuple`2[T1,T2],TValue]
var d1 = MakeGenericType(d0, typeof(double));                      // Dictionary`2[Tuple`2[Double,T2],TValue]
var d2 = MakeGenericType(d1, typeof(float));                       // Dictionary`2[Tuple`2[Double,Single],TValue]
var d3 = MakeGenericType(d2, typeof(Func<,,>));                    // Dictionary`2[Tuple`2[Double,Single],Func`3[T1,T2,TResult]]
var d4 = MakeGenericType(d3, typeof(short));                       // Dictionary`2[Tuple`2[Double,Single],Func`3[Int16,T2,TResult]]
var d5 = MakeGenericType(d4, typeof(object));                      // Dictionary`2[Tuple`2[Double,Single],Func`3[Int16,Object,TResult]]
var d6 = MakeGenericType(d5, typeof(int));                         // Dictionary`2[Tuple`2[Double,Single],Func`3[Int16,Object,Int32]]

Upvotes: 2

Ivan Stoev
Ivan Stoev

Reputation: 205769

You need to decompose the passed type to generic type definitions and build the resulting generic type bottom up using something like this:

static Type MakeGenericType(Type definition, Type parameter)
{
    var definitionStack = new Stack<Type>();
    var type = definition;
    while (!type.IsGenericTypeDefinition)
    {
        definitionStack.Push(type.GetGenericTypeDefinition());
        type = type.GetGenericArguments()[0];
    }
    type = type.MakeGenericType(parameter);
    while (definitionStack.Count > 0)
        type = definitionStack.Pop().MakeGenericType(type);
    return type;
}

Upvotes: 5

cdhowie
cdhowie

Reputation: 169256

The type argument is itself a generic type, so just compose the functions to match:

// List<List<int>>
MakeGenericType(
    typeof(List<>),
    MakeGenericType(
        typeof(List<>),
        typeof(int)
    )
);

Upvotes: 1

Related Questions