18

C# メソッド内で使用されるすべての型を取得する方法はありますか?

例えば、

public int foo(string str)
{
    Bar bar = new Bar();
    string x = "test";
    TEST t = bar.GetTEST();
}

戻り値: Bar、string、および TEST。

EnvDTE.CodeFunction を使用して取得できるのは、メソッド本体のテキストだけです。このコードを解析しようとするよりも、それを達成するためのより良い方法があるかもしれません。

4

7 に答える 7

11

この機会に、私が行った概念実証を投稿します。なぜなら、誰かがそれはできないと私に言ったからです - あちこちで少し微調整するだけで、これを拡張してすべてを抽出するのは比較的簡単です。メソッド内の参照された型 - サイズが大きく、序文がないことをお詫びしますが、多少のコメントがあります。

void Main()
{
    Func<int,int> addOne = i => i + 1;
    Console.WriteLine(DumpMethod(addOne));
    Func<int,string> stuff = i =>
    {
        var m = 10312;        
        var j = i + m;
        var k = j * j + i;
        var foo = "Bar";
        var asStr = k.ToString();
        return foo + asStr;
    };
    Console.WriteLine(DumpMethod(stuff));

    Console.WriteLine(DumpMethod((Func<string>)Foo.GetFooName));

    Console.WriteLine(DumpMethod((Action)Console.Beep));
}

public class Foo
{
    public const string FooName = "Foo";
    public static string GetFooName() { return typeof(Foo).Name + ":" + FooName; }
}

public static string DumpMethod(Delegate method)
{
    // For aggregating our response
    StringBuilder sb = new StringBuilder();

    // First we need to extract out the raw IL
    var mb = method.Method.GetMethodBody();
    var il = mb.GetILAsByteArray();

    // We'll also need a full set of the IL opcodes so we
    // can remap them over our method body
    var opCodes = typeof(System.Reflection.Emit.OpCodes)
        .GetFields()
        .Select(fi => (System.Reflection.Emit.OpCode)fi.GetValue(null));

    //opCodes.Dump();

    // For each byte in our method body, try to match it to an opcode
    var mappedIL = il.Select(op => 
        opCodes.FirstOrDefault(opCode => opCode.Value == op));

    // OpCode/Operand parsing: 
    //     Some opcodes have no operands, some use ints, etc. 
    //  let's try to cover all cases
    var ilWalker = mappedIL.GetEnumerator();
    while(ilWalker.MoveNext())
    {
        var mappedOp = ilWalker.Current;
        if(mappedOp.OperandType != OperandType.InlineNone)
        {
            // For operand inference:
            // MOST operands are 32 bit, 
            // so we'll start there
            var byteCount = 4;
            long operand = 0;
            string token = string.Empty;

            // For metadata token resolution            
            var module = method.Method.Module;
            Func<int, string> tokenResolver = tkn => string.Empty;
            switch(mappedOp.OperandType)
            {
                // These are all 32bit metadata tokens
                case OperandType.InlineMethod:        
                    tokenResolver = tkn =>
                    {
                        var resMethod = module.SafeResolveMethod((int)tkn);
                        return string.Format("({0}())", resMethod == null ? "unknown" : resMethod.Name);
                    };
                    break;
                case OperandType.InlineField:
                    tokenResolver = tkn =>
                    {
                        var field = module.SafeResolveField((int)tkn);
                        return string.Format("({0})", field == null ? "unknown" : field.Name);
                    };
                    break;
                case OperandType.InlineSig:
                    tokenResolver = tkn =>
                    {
                        var sigBytes = module.SafeResolveSignature((int)tkn);
                        var catSig = string
                            .Join(",", sigBytes);
                        return string.Format("(SIG:{0})", catSig == null ? "unknown" : catSig);
                    };
                    break;
                case OperandType.InlineString:
                    tokenResolver = tkn =>
                    {
                        var str = module.SafeResolveString((int)tkn);
                        return string.Format("('{0}')",  str == null ? "unknown" : str);
                    };
                    break;
                case OperandType.InlineType:
                    tokenResolver = tkn =>
                    {
                        var type = module.SafeResolveType((int)tkn);
                        return string.Format("(typeof({0}))", type == null ? "unknown" : type.Name);
                    };
                    break;
                // These are plain old 32bit operands
                case OperandType.InlineI:
                case OperandType.InlineBrTarget:
                case OperandType.InlineSwitch:
                case OperandType.ShortInlineR:
                    break;
                // These are 64bit operands
                case OperandType.InlineI8:
                case OperandType.InlineR:
                    byteCount = 8;
                    break;
                // These are all 8bit values
                case OperandType.ShortInlineBrTarget:
                case OperandType.ShortInlineI:
                case OperandType.ShortInlineVar:
                    byteCount = 1;
                    break;
            }
            // Based on byte count, pull out the full operand
            for(int i=0; i < byteCount; i++)
            {
                ilWalker.MoveNext();
                operand |= ((long)ilWalker.Current.Value) << (8 * i);
            }

            var resolved = tokenResolver((int)operand);
            resolved = string.IsNullOrEmpty(resolved) ? operand.ToString() : resolved;
            sb.AppendFormat("{0} {1}", 
                    mappedOp.Name, 
                    resolved)
                .AppendLine();                    
        }
        else
        {
            sb.AppendLine(mappedOp.Name);
        }
    }
    return sb.ToString();
}

public static class Ext
{
    public static FieldInfo SafeResolveField(this Module m, int token)
    {
        FieldInfo fi;
        m.TryResolveField(token, out fi);
        return fi;
    }
    public static bool TryResolveField(this Module m, int token, out FieldInfo fi)
    {
        var ok = false;
        try { fi = m.ResolveField(token); ok = true; }
        catch { fi = null; }    
        return ok;
    }
    public static MethodBase SafeResolveMethod(this Module m, int token)
    {
        MethodBase fi;
        m.TryResolveMethod(token, out fi);
        return fi;
    }
    public static bool TryResolveMethod(this Module m, int token, out MethodBase fi)
    {
        var ok = false;
        try { fi = m.ResolveMethod(token); ok = true; }
        catch { fi = null; }    
        return ok;
    }
    public static string SafeResolveString(this Module m, int token)
    {
        string fi;
        m.TryResolveString(token, out fi);
        return fi;
    }
    public static bool TryResolveString(this Module m, int token, out string fi)
    {
        var ok = false;
        try { fi = m.ResolveString(token); ok = true; }
        catch { fi = null; }    
        return ok;
    }
    public static byte[] SafeResolveSignature(this Module m, int token)
    {
        byte[] fi;
        m.TryResolveSignature(token, out fi);
        return fi;
    }
    public static bool TryResolveSignature(this Module m, int token, out byte[] fi)
    {
        var ok = false;
        try { fi = m.ResolveSignature(token); ok = true; }
        catch { fi = null; }    
        return ok;
    }
    public static Type SafeResolveType(this Module m, int token)
    {
        Type fi;
        m.TryResolveType(token, out fi);
        return fi;
    }
    public static bool TryResolveType(this Module m, int token, out Type fi)
    {
        var ok = false;
        try { fi = m.ResolveType(token); ok = true; }
        catch { fi = null; }    
        return ok;
    }
}
于 2013-07-18T21:58:11.443 に答える
2

このメソッドの IL にアクセスできる場合は、何か適切なことができる可能性があります。おそらく、オープン ソース プロジェクトILSpyを見て、彼らの成果を活用できるかどうかを確認してください。

于 2011-04-14T18:53:49.527 に答える
1

他の人が述べているように、DLLがある場合は、ILSpyが分析機能で行うのと同様の機能を使用できます(特定のタイプへの参照を見つけるために、アセンブリ内のすべてのIL命令を繰り返し処理します)。

それ以外の場合は、テキストをC#抽象構文ツリーに解析し、リゾルバーを使用せずにそれを行う方法はありません。これは、コードのセマンティクスを十分に理解して、例の「バー」が実際にそのメソッドから(「using」スコープで)アクセスできるタイプ、またはメソッドの名前、メンバーフィールドなど... SharpDevelopにはC#パーサー(「NRefactory」と呼ばれる)が含まれ、そのようなリゾルバーも含まれます。このスレッドを見ると、そのオプションの追求を検討できますが、正しく機能するように設定するのはかなりの量の作業であることに注意してください。

于 2011-04-16T00:05:16.093 に答える
1

このような広範な例を投稿しましたhow to use Mono.Cecil to do static code analysis

また、呼び出しツリーを静的に分析し、特定の興味深いものを探し、カスタム提供のセレクター関数を使用して結果を生成できる CallTreeSearch 列挙子クラスも示します。これにより、「ペイロード」ロジックにプラグインできます。

    static IEnumerable<TypeUsage> SearchMessages(TypeDefinition uiType, bool onlyConstructions)
    {
        return uiType.SearchCallTree(IsBusinessCall,
               (instruction, stack) => DetectTypeUsage(instruction, stack, onlyConstructions));
    }

    internal class TypeUsage : IEquatable<TypeUsage>
    {
        public TypeReference Type;
        public Stack<MethodReference> Stack;

        #region equality
        // ... omitted for brevity ...
        #endregion
    }

    private static TypeUsage DetectTypeUsage(
        Instruction instruction, IEnumerable<MethodReference> stack, bool onlyConstructions)
    {
        TypeDefinition resolve = null;
        {
            TypeReference tr = null;

            var methodReference = instruction.Operand as MethodReference;
            if (methodReference != null)
                tr = methodReference.DeclaringType;

            tr = tr ?? instruction.Operand as TypeReference;

            if ((tr == null) || !IsInterestingType(tr))
                return null;

            resolve = tr.GetOriginalType().TryResolve();
        }

        if (resolve == null)
            throw new ApplicationException("Required assembly not loaded.");

        if (resolve.IsSerializable)
            if (!onlyConstructions || IsConstructorCall(instruction))
                return new TypeUsage {Stack = new Stack<MethodReference>(stack.Reverse()), Type = resolve};

        return null;
    }

これにより、いくつかの詳細が省略されます

  • の実装IsBusinessCallIsConstructorCallおよびTryResolveこれらは些細なことであり、説明のみを目的としているため

それが役立つことを願っています

于 2011-04-21T09:09:06.410 に答える
0

リフレクションを使用すると、メソッドを取得できます。これはMethodInfoオブジェクトを返します。このオブジェクトでは、メソッドで使用される型を取得できません。したがって、答えは、C#ではこのネイティブを取得できないということだと思います。

于 2011-04-14T18:33:44.453 に答える
0

これは、リフレクション(GetMethod()、式ツリーなど)からは絶対に実行できません。前述のように、EnvDTEのCodeModelを使用することは、行ごとにC#を取得するためのオプションですが、Visual Studioの外部で使用する(つまり、エディターウィンドウではなく、既存の関数を処理する)ことはほぼ不可能です。

ただし、 CILコードを1行ずつ(メソッド内で)処理できるMono.Cecilをお勧めします。これは、参照している任意のアセンブリの任意のメソッドで使用できます。次に、すべての行が変数宣言(string x = "test"やmethodCallなど)であるかどうかを確認し、それらの行に含まれる型を取得できます。

于 2011-04-14T19:50:26.380 に答える
0

私が考えることができる最も近いものは式ツリーです。Microsoft のドキュメントを参照してください。

ただし、それらは非常に制限されており、単純な式でのみ機能し、ステートメント本体を含む完全なメソッドでは機能しません。

編集: ポスターの意図はクラスの結合と使用されている型を見つけることだったので、簡単な解決策としてNDependのような商用ツールを使用してコード分析を行うことをお勧めします。

于 2011-04-14T18:46:43.070 に答える