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)

  • I can't create "a": できませんType.GetType("T")
  • "b" をほぼ作成できます: できますType.GetType("List `1")が、"T" に関する情報はまだありません
  • 「c」の作り方がわかりません

Mono.Cecil 型を .net 型に変換するときにこれが必要になりました。Cecil は"MyMethod"、引数"T""List<T>"および"List<Tuple<T, string>>". 次に、リフレクションを使用してそのメソッドを取得したい (同じ名前と引数番号を持つメソッドが複数ある場合は、引数をチェックして、それがどれであるかを確認する必要があります)。そのため、何を変換する方法が必要なのかCecil は、.Net の内容と比較できるように、.Net が知っていることを教えてくれますparamTypes

また、Mono.Cecil 型を .Net 型に変換する方法を尋ねている人が他にも何人かいたので、試してみようと思ったのもそのためです。

4

5 に答える 5

4

文字列を使用して取得できます。文字列名でT呼び出し、結果の型のジェネリック引数を取得します。そこから、 を使用して他のオープン ジェネリック型を構築できます。最初に最もネストされた型を構築することによって、裏返しに作業する必要があります。異なるメソッド間で自動的に行うには、ネストされた型に到達するために文字列の解析が必要になります。.Net メソッドと Cecil メソッドを比較するために、@Tengiz の方が優れたアプローチを採用している可能性があります。GetTypeMyClassMakeGenericType

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

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

名前で型を取得しようとしなかった理由

残念ながら、表現されている型について多くのことを推測しない限り、文字列を指定して型を取得する方法は見つかりませんでした...したがって、それはまったく不可能です。

それが、私が代わりにメソッドを見つけるためのメソッドを実行した理由を説明しています。それははるかに正確です...しかし、非常にまれで奇妙な状況では、最終的に失敗する可能性があります:

  • 独自のリストを作成し、同じメソッドの2つのオーバーロードを作成すると、1つは.Netリストを取得し、もう1つは作成したリストを取得します...失敗します。

入力文字列を解析してみませんか

メソッドを検索するには、固定の構文文字列があれば十分であることがわかりました。これにより、メソッドからメソッドを生成して比較できます...いくつかの制限があります。

  • タイプの名前を使用する必要があるため、C#同盟は機能しません(string「String」という名前にするint必要があり、「int」ではなく「Int32」という名前にする必要があります)

編集

パフォーマンス

このソリューションはあまりパフォーマンスが高くありませんが、キャッシュで解決できないことは何もありません。このメソッドは、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;
}

これが役立つことを願っています!=)

于 2012-11-29T07:20:37.640 に答える
1

やろうとしていることはできませんが、別の方向から入ることで同じ結果を達成する比較的簡単な方法があります

文字列は型を一意に識別しません

Tこれは、文字列を型に変換する際の基本的な問題です。 が表示されると、それがどこから来たのかわかりません。以下は有効なクラス定義です。

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

の 2 つのオーバーロードにMakeは、同じように見えるパラメーターがありますが、比較すると同等ではありません。さらに、最初にジェネリックの を取得せずTにジェネリックの を取得する方法はまったくありません- 循環依存。Make<T>MethodInfoMake<T>

あなたは何ができますか?

不可能なstring->変換を行う代わりに、無制限のジェネリック型を含む型のインスタンスが特定の文字列表現と一致するかどうかを通知するマッチャーTypeを構築できます。

static bool MatchType(string str, Type type)

このメソッドを使用すると、特定の名前を持つ使用可能なすべてのメソッドを確認し、文字列の配列内の文字列に対してパラメーター リストの型を 1 つずつ確認できます。

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 に答える
1

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.

ところで、文字列を効率的に操作したり、CSharpCodeProviderインスタンスを解放したりするなど、パフォーマンスの問題に直面した場合は、このコードに多くの最適化を適用できます。ただし、問題のタスクを解決するにはコードで十分です。

于 2012-11-28T18:04:11.833 に答える
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 に答える