Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create open constructed type from string

Let's say I have the following class.

MyClass<T>
{
    public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) {}
}

I can get the type of the arguments of the method as follow

Type testType = typeof(MyClass<>);
MethodInfo myMethodInfo = testType.GetMethod("MyMethod");
Type[] paramTypes = myMethodInfo.GetParameters().Select(pi => pi.ParameterType);

How can I manually create an array containing the same open types as paramTypes from a string? For ex from

var typesAsStr = new string[] {"T", "List`1[T]", "List`1[Tuple`2[T, string]]"};

If I had MyClass<int>, I could do something like Type.GetType(fullQualifiedNameOfArg) for each argument, but here I want to keep the generic argument T:

  • I can't create "a": I can't do Type.GetType("T")
  • I can almost create "b": I can do Type.GetType("List `1"), but the info on "T" is not yet present
  • I don't know how to create "c"

I ended up needing this when converting a Mono.Cecil type into a .net type: Cecil gives me the info on a method named "MyMethod" with arguments "T", "List<T>" and "List<Tuple<T, string>>". I then want to get that method using reflection (if there are several methods with the same name and argument numbers, I have to check the args to know which one it is), that's why I'd want to have a way to transform what Cecil tells me into what .Net knows, to be able to compare with what's in paramTypes.

I've also seen several other people asking how to convert a Mono.Cecil type into a .Net one, so that's also why I thought I'd try.

like image 918
user276648 Avatar asked Feb 19 '23 10:02

user276648


1 Answers

You can get T using strings, you do it by calling GetType with the string name of MyClass and then getting the generic arguments of the resulting type. From there you can build up the other open generic types using MakeGenericType. You have to work from the inside out by constructing the most nested types first. To do it automatically across differing methods would require some string parsing to get to the nested types. For the sake of comparing .Net methods against Cecil methods, @Tengiz might have a better approach.

To run the code, update the string name of MyClass to have the correct namespace for your environment.

private static void Main(string[] args) {
    // change 'yournamespace'
    Type testType = Type.GetType("yournamespace.MyClass`1");
    Type[] testTypeGenericArgs = testType.GetGenericArguments();

    // Get T type from MyClass generic args
    Type tType = testTypeGenericArgs[0];

    Type genericListType = Type.GetType("System.Collections.Generic.List`1");

    // create type List<T>
    Type openListType = genericListType.MakeGenericType(testTypeGenericArgs[0]);
    Type genericTuple = Type.GetType("System.Tuple`2");
    Type stringType = Type.GetType("System.String");

    // create type Tuple<T, string>
    Type openTuple = genericTuple.MakeGenericType(new[] { tType, stringType });

    // create type List<Tuple<T, string>>
    Type openListOfTuple = genericListType.MakeGenericType(openTuple);

    Type[] typesFromStrings = new[] { tType, openListType, openListOfTuple };

    // get method parameters per example
    Type myClassType = typeof(MyClass<>);
    MethodInfo myMethodInfo = myClassType.GetMethod("MyMethod");
    Type[] paramTypes = myMethodInfo.GetParameters().Select(pi => pi.ParameterType).ToArray();

    // compare type created from strings against types
    // retrieved by reflection
    for (int i = 0; i < typesFromStrings.Length; i++) {
        Console.WriteLine(typesFromStrings[i].Equals(paramTypes[i]));
    }

    Console.ReadLine();
}
like image 120
WarrenG Avatar answered Mar 06 '23 02:03

WarrenG