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.
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();
}
I found this so interesting, that I had to create something myself, and present it to the world... and after a couple hours of exploration, here is what I got...
The extension method for Type: GetMethodByString
This is very simple: get a type and then call the method passing a string that represents the method you want:
var type = typeof(MyType<>);
type.GetMethodByString("MyMethod(T, List`1[T], List`1[Tuple`2[T, String]])")
Sample program
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 didn't try to get types by name
Unfortunately, I found no way to get a type given a string, unless you guess a lot about the type being represented... so, it is quite impossible.
That explains why I did a method to find the method instead. It is much more precise... but it could eventually fail, in very rare and bizarre circumstances:
if you create a List of your own, and then two overloads of the same method, one taking the .Net List and the other taking the List you have created... then it fails
Why not parsing the input string
I found that for the purpose of looking up a method, it is enough to have a fixed syntax string, so that I can generate it from the method and compare... that have some limitations:
must use the name of the type, so C# alliases won't work (string must be named "String", int must be named "Int32" not "int")
EDIT
Performance
This solution is not very performatic, but nothing that a cache cannot solve. The method could use a dictionary, using both the Type and the string as a composite key, and look in there before trying to find the method by bulding a lot of strings and comparing all of them.
If you need thread safety on the cache dictionary, use a ConcurrentDictionary<TKey, TValue>... 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 this helps! =)
I don't think .NET allows you to create a type "T" where T is a type argument, which is yet to be specified. So, the array of Type(s) from input string array cannot be created.
However, in the second part of your question, I read that you want to identify the method which has those types given as string. That task is solvable by iterating though the arguments, creating another array of strings describing the method arguments, 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's 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));
}
}
In the [console] output I see that your input string matches the function.
BTW, a lot of optimizations can be applied to this code if you face the performance problems, such as efficient way of working with strings, releasing CSharpCodeProvider instance maybe, etc. But the code is enough to solve the given task as questioned.
You cannot do what you are trying to do, but there is a relatively easy way of achieving the same result by entering from a different direction
Strings do not identify types uniquely
This is the basic problem with converting strings to types: when you see a T, you have no idea 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;
}
}
Two overloads of Make have parameters that look identical, yet they do not compare as equal. Moreover, there is absolutely no way of getting the T of the generic Make<T> without first getting the MethodInfo for the generic Make<T> - a circular dependency.
What can you do?
Instead of going for the impossible string->Type conversion, you can build a matcher that tells you if an instance of a type, including an unbounded generic type, matches a given string representation:
static bool MatchType(string str, Type type)
With this method in hand, you can walk through all available methods with a particular name, and check the types of their parameter lists one by one against the strings in your array of strings:
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(p.s, p.t.ParameterType))
);
How do you implement the MatchType method?
You can use a technique similar to Recursive Descent Parsing: tokenize your string, and then match elements of your type as you go through the chain of tokens. When a class is parameterized, get generic parameters and match them recursively. You need to pay attention to array types, but that is relatively simple as well. 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;
}
It is not quite clear to me what the exactly you need, but i believe 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;
}
i neeed something like this in C#.. have list in class but decide what will be in list during runtime
class A
{
List<?> data;
Type typeOfDataInList;
}
public void FillData<DataTyp>(DataTyp[] data) where DataTyp : struct
{
A a = new A();
A.vListuBudouDataTypu = typeof(DataTyp);
A.data = new List<A.typeOfDataInList>();
A.AddRange(data);
}
Is this possible to do something like this ?
class A<T>
{
public readonly List<T> Data = new List<T>();
public Type TypeOfDataInList { get; private set; }
public A()
{
TypeOfDataInList = typeof(T);
}
public void Fill(params T[] items)
{
data.AddRange(items);
}
}
If you don't know the type or have multiple objects of different types, declare an instance of A like this:
A<object> myClass = new A<object>();
myClass.Fill(new object(), new object());
Otherwise if you know the type, you can do this:
A<int> myInts = new A<int>();
myInts.Fill(1, 2, 5, 7);
Yes.
class A
{
IList data;
Type typeOfDataInList;
}
public void FillData<T>(T[] data) where T : struct
{
A a = new A();
A.typeOfDataInList = typeof(T);
A.data = new List<T>(data);
}
It would be better to make the A class generic:
class A<T>
{
IList<T> data;
Type typeOfDataInList;
}
public void FillData<T>(T[] data) where T : struct
{
A<T> a = new A<T>();
a.typeOfDataInList = typeof(T);
a.data = new List<T>(data);
}
You are going to need to use reflection to instantiate an IList<T> where T is not known until runtime.
See the following MSDN article, which explains it better than I could (scroll down to the section on how to construct a generic type):
http://msdn.microsoft.com/en-us/library/b8ytshk6.aspx
Here is a short example:
Type listType = typeof(List<>);
Type runtimeType = typeof(string); // just for this example
// assert that runtTimeType is something you're expecting
Type[] typeArgs = { runtimeType };
Type listTypeGenericRuntime = listType.MakeGenericType(typeArgs);
IEnumerable o = Activator.CreateInstance(listTypeGenericRuntime) as IEnumerable;
// loop through o, etc..
You might want to consider a generic class:
public class A<T> where T : struct
{
public List<T> data;
public Type type;
}
public void FillData<DataType>(DataType[] data) where DataType : struct
{
A<DataType> a = new A<DataType>();
a.data = new List<DataType>();
a.AddRange(data);
}
System.Collections.Generic.List<T> ?