4

假设我有以下课程。

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>做类似的事情,在这里我想保留通用参数 T:Type.GetType(fullQualifiedNameOfArg)

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

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

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

4

5 回答 5

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();
}
于 2012-11-28T18:36:42.637 回答
2

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

类型的扩展方法: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;
        }
    }
}

类型扩展

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

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

不幸的是,我找不到给定字符串的类型,除非您对所表示的类型有很多猜测......所以,这是完全不可能的。

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

  • 如果您创建自己的列表,然后对同一方法进行两次重载,一个采用 .Net 列表,另一个采用您创建的列表......然后它会失败

为什么不解析输入字符串

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

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

编辑

表现

这个解决方案的性能不是很好,但没有什么是缓存无法解决的。该方法可以使用字典,同时使用类型和字符串作为复合键,并在尝试通过构建大量字符串并比较所有字符串来查找方法之前先查看那里。

如果您需要缓存字典上的线程安全,请使用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;
}

希望这有帮助!=)

于 2012-11-29T07:20:37.640 回答
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] 输出中,我看到您的输入字符串函数匹配。

顺便说一句,如果您遇到性能问题,可以对这段代码进行很多优化,例如处理字符串的有效方式,CSharpCodeProvider可能释放实例等。但是代码足以解决所质疑的给定任务。

于 2012-11-28T18:04:11.833 回答
1

你不能做你想做的事,但是有一种相对简单的方法可以通过从不同的方向进入来达到相同的结果

字符串不唯一标识类型

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

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

的两个重载Make具有看起来相同的参数,但它们的比较并不相等。此外,如果不首先获得T泛型,就绝对无法获得泛型的—— 循环依赖。Make<T>MethodInfoMake<T>

你能做什么?

您可以构建一个匹配器来告诉您一个类型的实例(包括无界泛型类型)是否匹配给定的字符串表示,而不是进行不可能的string->转换:Type

static bool MatchType(string str, Type type)

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

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

你如何实现该MatchType方法?

您可以使用类似于Recursive Descent Parsing的技术:标记您的字符串,然后在遍历标记链时匹配您类型的元素。当一个类被参数化时,获取泛型参数并递归匹配它们。您需要注意数组类型,但这也相对简单。看一看:

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;
}
于 2012-11-28T20:34:25.467 回答
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;
}
于 2012-11-20T04:51:05.633 回答