获取在C#方法主体内部使用的类型

| 有没有一种方法可以在C#方法中使用所有类型? 例如,
public int foo(string str)
{
    Bar bar = new Bar();
    string x = \"test\";
    TEST t = bar.GetTEST();
}
将返回:Bar,字符串和TEST。 我现在所能获得的就是使用EnvDTE.CodeFunction的方法主体文本。也许有比尝试解析此代码更好的方法。     
已邀请:
我将借此机会发布我所做的概念验证,因为有人告诉我这无法完成-到处进行一些调整,将其扩展到提取方法中所有引用的类型-对于它的大小和缺少序言表示歉意,但是它有些注释:
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;
    }
}
    
如果您可以使用此方法访问IL,则可能可以执行一些适当的操作。也许看看开源项目ILSpy,看看是否可以利用他们的任何工作。     
正如其他人提到的那样,如果您拥有DLL,则可以使用类似于ILSpy在其Analyze功能中所做的操作(迭代程序集中的所有IL指令以查找对特定类型的引用)。 否则,没有办法将文本解析为C#抽象语法树,并采用Resolver-可以充分理解代码语义以了解示例中的“ Bar”确实是一种方法可从该方法访问的类型的名称(在其“使用\”范围内),或者方法的名称,成员字段等... SharpDevelop包含一个C#解析器(称为\“ NRefactory \”),并且也包含这样的解析器,您可以通过查看此线程来研究采用该选项,但是请注意,将其设置为正确工作需要大量工作。     
我刚刚发布了一个类似
how to use Mono.Cecil to do static code analysis
的例子。 我还展示了一个CallTreeSearch枚举器类,该类可以静态分析调用树,查找某些有趣的东西并使用自定义提供的选择器函数生成结果,因此您可以使用\ payload逻辑将其插入,例如
    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;
    }
这省略了一些细节
IsBusinessCall
IsConstructorCall
TryResolve
的实现,因为它们是微不足道的,仅用作说明 希望能有所帮助     
我能想到的最接近的东西是表达式树。查看Microsoft提供的文档。 但是它们非常有限,并且仅适用于简单表达式而不是带有语句主体的完整方法。 编辑:由于发帖人的目的是查找类耦合和使用的类型,因此我建议使用像NDepend这样的商业工具来进行代码分析,作为一种简单的解决方案。     
绝对不能通过反射(GetMethod(),表达式树等)完成此操作。如您所述,使用EnvDTE的CodeModel是一种选择,因为您在那里获得了逐行C#,但是在Visual Studio之外使用它(即,处理已经存在的函数,而不是在编辑器窗口中)几乎是不可能的。 , 恕我直言。 但是我建议使用Mono.Cecil,它可以逐行处理CIL代码(在方法内部),并且可以在引用的任何程序集中的任何方法上使用它。然后,您可以检查每一行是否为变量声明(例如字符串x = \“ test \”或methodCall),并可以获取这些行中涉及的类型。     
通过反射,您可以获得该方法。这将返回MethodInfo对象,并且使用该对象您将无法获取方法中使用的类型。因此,我认为答案是您无法在C#中获得此本机。     

要回复问题请先登录注册