从字符串创建开放构造类型

时间:2012-11-20 03:16:28

标签: c# system.reflection

假设我有以下课程。

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

我可以得到方法的参数类型如下

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

如何从字符串中手动创建包含相同开放类型的数组作为paramTypes?来自

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

如果我有MyClass<int>,我可以针对每个参数执行类似Type.GetType(fullQualifiedNameOfArg)的操作,这里我想保留通用参数T :

  • 我无法创建“a”:我无法做Type.GetType("T")
  • 我几乎可以创建“b”:我可以Type.GetType("List `1"),但“T”的信息尚不存在
  • 我不知道如何创建“c”

在将Mono.Cecil类型转换为.net类型时,我最终需要这个:Cecil为我提供了一个名为"MyMethod"的方法的信息,其中包含参数"T""List<T>"和{ {1}}。然后我想使用反射获取该方法(如果有几个方法具有相同的名称和参数编号,我必须检查args以了解它是哪一个),这就是为什么我想要一种方法来转换什么塞西尔告诉我.Net知道什么,能够与"List<Tuple<T, string>>"中的内容进行比较。

我还看到其他几个人在询问如何将Mono.Cecil类型转换为.Net类型,这也是我认为我会尝试的原因。

5 个答案:

答案 0 :(得分:4)

您可以使用字符串获取T,通过使用字符串名称GetType调用MyClass然后获取结果类型的泛型参数来执行此操作。从那里,您可以使用MakeGenericType构建其他开放泛型类型。您必须首先通过构造最嵌套的类型从内到外工作。要在不同的方法中自动执行此操作,需要进行一些字符串解析才能获得嵌套类型。为了比较.Net方法与Cecil方法,@ Tengiz可能有更好的方法。

要运行代码,请更新MyClass的字符串名称,以便为您的环境创建正确的命名空间。

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();
}

答案 1 :(得分:2)

我发现这很有意思,我必须自己创造一些东西,然后把它呈现给世界......经过几个小时的探索,这就是我得到的......

Type:GetMethodByString

的扩展方法

这很简单:获取一个类型,然后调用方法传递一个代表你想要的方法的字符串:

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

示例程序

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");
    }
}

为什么我没有尝试按名称获取类型

不幸的是,我发现无法获得给定字符串的类型,除非您对所表示的类型有很多猜测......所以,这是不可能的。

这就解释了为什么我做了一个找到方法的方法。它更精确......但最终可能会失败,在非常罕见和奇怪的情况下:

  • 如果你创建了一个自己的List,然后是同一个方法的两个重载,一个采用.Net List,另一个采用你创建的List ...然后就失败了

为什么不解析输入字符串

我发现为了查找方法,只需要一个固定的语法字符串,这样我就可以从方法生成它并比较......它有一些限制:

  • 必须使用该类型的名称,因此C#alliases不起作用(string必须命名为“String”,int必须命名为“Int32”而不是“int”)

修改

效果

此解决方案的性能不是很好,但缓存无法解决。该方法可以使用字典,使用Type和字符串作为复合键,并在尝试通过构建大量字符串并比较所有字符串来查找方法之前查看。

如果您需要缓存字典上的线程安全,请使用ConcurrentDictionary<TKey, TValue> ...非常好的类。

编辑2:创建了缓存版本

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这有帮助! =)

答案 2 :(得分:1)

我不认为.NET允许你创建类型“T”,其中T是类型参数,尚未指定。因此,无法创建来自输入字符串数组的Type(s)数组。

但是,在你的问题的第二部分中,我读到你想要识别那些以字符串形式给出的类型的方法。通过遍历参数,创建另一个描述方法参数的字符串数组,然后比较结果和输入数组,可以解决该任务,如下所示:

    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));
        }
    }

在[console]输出中,我看到您的输入字符串匹配该函数。

BTW,如果遇到性能问题,可以对这段代码进行大量优化,例如使用字符串的高效方法,释放CSharpCodeProvider实例等等。但代码足以解决给予质疑的任务。

答案 3 :(得分:1)

你无法做你想做的事情,但是通过从不同的方向进入可以获得相同结果的相对简单的方法

字符串不能唯一标识类型

这是将字符串转换为类型的基本问题:当您看到T时,您不知道它来自何处。以下是有效的类定义:

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

Make的两个重载具有看起来相同的参数,但它们不相等。此外,如果没有先获得通用T的{​​{1}} - 循环依赖,绝对无法获得通用Make<T>的{​​{1}}。

你能做什么?

您可以构建一个匹配器来告诉您某个类型的实例,包括无界的泛型,而不是进行不可能的MethodInfo - &gt; Make<T>转换type,匹配给定的字符串表示:

string

使用此方法,您可以浏览具有特定名称的所有可用方法,并针对字符串数组中的字符串逐个检查其参数列表的类型:

Type

如何实施static bool MatchType(string str, Type type) 方法?

您可以使用类似于Recursive Descent Parsing的技巧:对您的字符串进行标记,然后在您通过标记链时匹配您的类型的元素。在参数化类时,获取泛型参数并以递归方式匹配它们。您需要注意数组类型,但这也相对简单。看看:

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))
    );

答案 4 :(得分:0)

我不太清楚你到底需要什么,但我相信你可以使用以下技术:

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;
}