Reputation: 4408
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
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
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
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