Create an open constructed type from a string

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 method arguments by following

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

How to manually create an array from the same open types as paramTypes from a string? For ex of

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

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

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

I had to do this when converting Mono.Cecil type to .net type: Cecil gives me information about a method called "MyMethod" with arguments "T" , "List<T>" and "List<Tuple<T, string>>" . Then I want to get this method using reflection (if there are several methods with the same names and arguments, I have to check the arguments to find out which one), so I would like to have a way to transform Cecil tells me that .Net knows to be able to compare with that in paramTypes .

I also saw several other people asking how to convert the Mono.Cecil type to .Net, and so I thought I'd give it a try.

+4
source share
5 answers

You can get T using strings, you do this by calling GetType with the string name MyClass , and then getting the general arguments of the resulting type. From there, you can create other open generic types using MakeGenericType . You must work from the inside out by creating the most nested types first. To do this automatically using different methods, you need some parsing of the string to access the nested types. To compare .NET methods with Cecil methods, @Tengiz may have a better approach.

To run the code, update the MyClass string name 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(); } 
+4
source

I found it so interesting that I myself needed to create something and present it to the world ... and after a couple of hours of research, this is what I got ...

Extension Method for Type: GetMethodByString

It is very simple: get the type, and then call the method that passes the string that represents the method you want:

 var type = typeof(MyType<>); type.GetMethodByString("MyMethod(T, List`1[T], List`1[Tuple`2[T, String]])") 

Program example

 class Program { public static void Main() { var t1 = typeof(MyType<>); var mi11 = t1.GetMethodByString("MyMethod(T, List`1[T], List`1[Tuple`2[T, String]])"); var mi12 = t1.GetMethodByString("Method[X](X, T)"); var mi13 = t1.GetMethodByString("Method(List`1[T], Int32 ByRef)"); var t2 = typeof(MyType); var mi21 = t2.GetMethodByString("Method[X, T](List`1[X], Tuple`2[X, List`1[T]])"); } class MyType<T> { public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) { } public void Method(List<T> t, out int i) { i = 0; } public void Method<X>(X x, T t) { } } class MyType { public int Method<X, T>(List<X> x, Tuple<X, List<T>> tuple) { return 1; } } } 

TypeExtensions

 public static class TypeExtensions { public static MethodInfo GetMethodByString( this Type type, string methodString) { return type.GetMethods() .Where(mi => MethodToString(mi) == methodString) .SingleOrDefault(); } public static string MethodToString(MethodInfo mi) { var b = new StringBuilder(); b.Append(mi.Name); if (mi.IsGenericMethodDefinition) b.AppendFormat("[{0}]", string.Join(", ", mi.GetGenericArguments() .Select(TypeToString))); b.AppendFormat("({0})", string.Join(", ", mi.GetParameters() .Select(ParamToString))); return b.ToString(); } public static string TypeToString(Type t) { var b = new StringBuilder(); b.AppendFormat("{0}", t.Name); if (t.IsGenericType) b.AppendFormat("[{0}]", string.Join(", ", t.GetGenericArguments() .Select(TypeToString))); return b.ToString(); } public static string ParamToString(ParameterInfo pi) { return TypeToString(pi.ParameterType).Replace("&", " ByRef"); } } 

Why I did not try to get types by name

Unfortunately, I did not find a way to get the type specified by the string, unless you are aware of what type will be represented ... so this is completely impossible.

This explains why I applied the method to find the method. This is much more accurate ... but in the end it can end in failure, in very rare and bizarre circumstances:

  • if you create your own list, and then two overloads of the same method, one of which contains the .Net list and the other is the list you created ... then it does not work.

Why not parse the input string

I found that to search for a method, it is enough to have a fixed syntax line so that I can generate it from the method and compare ... which have some limitations:

  • should use a type name, so C # alliases will not work ( string should be named "String", int should be named "Int32", not "int")

EDIT

Performance

This solution is not very realistic, but nothing the cache can solve. A method can use a dictionary using both a type and a string as a composite key, and look there before trying to find a method by combining many lines and comparing them all.

If you need thread safety in the cache dictionary, use ConcurrentDictionary<TKey, TValue> ... a very nice class.

EDIT 2: Created a cached version

 static ConcurrentDictionary<Type, Dictionary<string, MethodInfo>> cacheOfGetMethodByString = new ConcurrentDictionary<Type, Dictionary<string, MethodInfo>>(); public static MethodInfo GetMethodByString( this Type type, string methodString) { var typeData = cacheOfGetMethodByString .GetOrAdd(type, CreateTypeData); MethodInfo mi; typeData.TryGetValue(methodString, out mi); return mi; } public static Dictionary<string, MethodInfo> CreateTypeData(Type type) { var dic = new Dictionary<string, MethodInfo>(); foreach (var eachMi in type.GetMethods()) dic.Add(MethodToString(eachMi), eachMi); return dic; } 

Hoppe it helps! =)

+2
source

I do not think .NET allows you to create a type "T", where T is an argument of type , which is not yet specified . Thus, an array of Type (s) from an array of input strings cannot be created.

However, in the second part of your question, I read that you want to identify a method that has those types that are specified as a string. This task is solvable by repeating the arguments, creating another array of strings describing the arguments of the method, and then comparing the resulting and input arrays as follows:

  class MyClass<T> { public void MyMethod(T a, List<T> b, List<Tuple<T, string>> c) { } } class Program { static void Main(string[] args) { //input. var typesAsStr = new string[] { "T", "List`1[T]", "List`1[Tuple`2[T, string]]" }; //type to find a method. Type testType = typeof(MyClass<>); //possibly iterate through methods instead? MethodInfo myMethodInfo = testType.GetMethod("MyMethod"); //get array of strings describing MyMethod arguments. string[] paramTypes = myMethodInfo.GetParameters().Select(pi => TypeToString(pi.ParameterType)).ToArray(); //compare arrays of strings (can be improved). var index = -1; Console.WriteLine("Method found: {0}", typesAsStr.All(str => { index++; return index < paramTypes.Length && str == paramTypes[index]; })); Console.ReadLine(); } private static CSharpCodeProvider compiler = new CSharpCodeProvider(); private static string TypeToString(Type type) { if (type.IsGenericType) { return type.Name + "[" + string.Join(", ", type.GetGenericArguments().Select(ga => TypeToString(ga))) + "]"; } else if (type.IsGenericParameter) { return type.Name; } //next line gives "string" (lower case for System.String). //additional type name translations can be applied if output is not what we neeed. return compiler.GetTypeOutput(new CodeTypeReference(type)); } } 

At the output of [console], I see that your input line matches .

By the way, you can apply a lot of optimizations to this code if you encounter performance problems, such as an efficient way to work with strings, freeing an instance of CSharpCodeProvider , possibly, etc. But the code is enough to solve this problem as a doubt.

+1
source

You cannot do what you are trying to do, but there is a relatively simple way to achieve the same result by entering it in a different direction

Strings do not uniquely identify types

This is the main problem with converting strings to types: when you see T , you don't know where it came from. The following is a valid class definition:

 class Simple<T> { public T Make(T blah) { return blah; } public T Make<T>(T blah) { return blah; } } 

The two Make overloads have parameters that look the same, but they do not compare as equal. Moreover, there is no way to get T common Make<T> without first getting MethodInfo for a common Make<T> - a circular dependency.

What can you do?

Instead of moving to an impossible string β†’ Type conversion, you can create a match that indicates whether the instance of the type, including the unlimited generic type, matches the given string representation:

 static bool MatchType(string str, Type type) 

Using this method, you can view all available methods with a specific name and check the types of parameter lists one by one in the rows in your string array:

 var typesAsStr = new [] {"T", "List`1[T]", "List`1[Tuple`2[T, string]]"}; var myMethod = typeof (Simple<>) .GetMethods() .SingleOrDefault(m => m.Name == "MyMethod" && typesAsStr .Zip(m.GetParameters(), (s, t) => new {s, t}) .All(p => MatchType(ps, ptParameterType)) ); 

How do you implement MatchType ?

You can use a technique similar to Recursive descent analysis : tokenize your string, and then match the elements of your type as you go through the token chain. When the class is parameterized, get the general parameters and repeat them recursively. You need to pay attention to the types of arrays, but it is relatively simple. Take a look:

 public static bool MatchType(string str, Type type) { var queue = new Queue<Token>(Tokenize(str)); return MatchRecursive(queue, type) && (queue.Count == 0); } private static bool MatchRecursive(Queue<Token> tokens, Type type) { string baseName; if (!ReadToken(tokens, TokenType.Identifier, out baseName)) return false; var ranks = new List<int>(); while (type.IsArray) { ranks.Add(type.GetArrayRank()); type = type.GetElementType(); } if (type.IsGenericType) { if (!type.Name.StartsWith(baseName+"`") || !DropToken(tokens, TokenType.Tick)) return false; string numStr; int num; if (!ReadToken(tokens, TokenType.Number, out numStr) || !int.TryParse(numStr, out num) || !DropToken(tokens, TokenType.OpenBraket)) return false; var genParams = type.GetGenericArguments(); if (genParams.Length != num) return false; for (var i = 0 ; i < num ; i++) { if (i != 0 && !DropToken(tokens, TokenType.Comma)) return false; if (!MatchRecursive(tokens, genParams[i])) return false; } if (!DropToken(tokens, TokenType.CloseBraket)) return false; } foreach (var rank in ranks) { if (!DropToken(tokens, TokenType.OpenBraket)) return false; for (var i = 0 ; i != rank-1 ; i++) { if (!DropToken(tokens, TokenType.Comma)) return false; } if (!DropToken(tokens, TokenType.CloseBraket)) return false; } return type.IsGenericType || Aliases.Contains(new Tuple<string, Type>(baseName, type)) || type.Name == baseName; } private static readonly ISet<Tuple<string,Type>> Aliases = new HashSet<Tuple<string, Type>> { new Tuple<string, Type>("bool", typeof(bool)), new Tuple<string, Type>("byte", typeof(byte)), new Tuple<string, Type>("sbyte", typeof(sbyte)), new Tuple<string, Type>("char", typeof(char)), new Tuple<string, Type>("string", typeof(string)), new Tuple<string, Type>("short", typeof(short)), new Tuple<string, Type>("ushort", typeof(ushort)), new Tuple<string, Type>("int", typeof(int)), new Tuple<string, Type>("uint", typeof(uint)), new Tuple<string, Type>("long", typeof(long)), new Tuple<string, Type>("ulong", typeof(ulong)), new Tuple<string, Type>("float", typeof(float)), new Tuple<string, Type>("double", typeof(double)), new Tuple<string, Type>("decimal", typeof(decimal)), new Tuple<string, Type>("void", typeof(void)), new Tuple<string, Type>("object", typeof(object)) }; private enum TokenType { OpenBraket, CloseBraket, Comma, Tick, Identifier, Number } private class Token { public TokenType Type { get; private set; } public string Text { get; private set; } public Token(TokenType type, string text) { Type = type; Text = text; } public override string ToString() { return string.Format("{0}:{1}", Enum.GetName(typeof(TokenType), Type), Text); } } private static bool DropToken(Queue<Token> tokens, TokenType expected) { return (tokens.Count != 0) && (tokens.Dequeue().Type == expected); } private static bool ReadToken(Queue<Token> tokens, TokenType expected, out string text) { var res = (tokens.Count != 0) && (tokens.Peek().Type == expected); text = res ? tokens.Dequeue().Text : null; return res; } private static IEnumerable<Token> Tokenize(IEnumerable<char> str) { var res = new List<Token>(); var text = new StringBuilder(); foreach (var c in str) { var pos = "[],`".IndexOf(c); if ((pos != -1 || char.IsWhiteSpace(c)) && text.Length != 0) { res.Add(new Token( char.IsDigit(text[0]) ? TokenType.Number : TokenType.Identifier , text.ToString()) ); text.Clear(); } if (pos != -1) { res.Add(new Token((TokenType)pos, c.ToString(CultureInfo.InvariantCulture))); } else if (!char.IsWhiteSpace(c)) { text.Append(c); } } if (text.Length != 0) { res.Add(new Token( char.IsDigit(text[0]) ? TokenType.Number : TokenType.Identifier , text.ToString()) ); } return res; } 
+1
source

I don’t quite understand what exactly you need, but I believe that you can use the following technique:

 object[] parameters = CreateParameters(typeof(MyClass<>), "MyMethod", typeof(int)); Debug.Assert(parameters[0] is int); Debug.Assert(parameters[1] is List<int>); Debug.Assert(parameters[2] is List<Tuple<int, string>>); //... object[] CreateParameters(Type type, string methodName, Type genericArgument) { object[] parameters = null; MethodInfo mInfo = type.GetMethod(methodName); if(mInfo != null) { var pInfos = mInfo.GetParameters(); parameters = new object[pInfos.Length]; for(int i = 0; i < pInfos.Length; i++) { Type pType = pInfos[i].ParameterType; if(pType.IsGenericParameter) parameters[i] = Activator.CreateInstance(genericArgument); if(pType.IsGenericType) { var arguments = ResolveGenericArguments(pType, genericArgument); Type definition = pType.GetGenericTypeDefinition(); Type actualizedType = definition.MakeGenericType(arguments); parameters[i] = Activator.CreateInstance(actualizedType); } } } return parameters; } Type[] ResolveGenericArguments(Type genericType, Type genericArgument) { Type[] arguments = genericType.GetGenericArguments(); for(int i = 0; i < arguments.Length; i++) { if(arguments[i].IsGenericParameter) arguments[i] = genericArgument; if(arguments[i].IsGenericType) { var nestedArguments = ResolveGenericArguments(arguments[i], genericArgument); Type nestedDefinition = arguments[i].GetGenericTypeDefinition(); arguments[i] = nestedDefinition.MakeGenericType(nestedArguments); } } return arguments; } 
0
source

Source: https://habr.com/ru/post/1447123/


All Articles