【问题标题】:Get types used inside a C# method body获取 C# 方法体内使用的类型
【发布时间】:2011-04-14 18:26:41
【问题描述】:

有没有办法获取 C# 方法中使用的所有类型?

例如,

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

将返回:条形图、字符串和 TEST。

我现在只能得到使用 EnvDTE.CodeFunction 的方法正文。也许有比尝试解析这段代码更好的方法来实现它。

【问题讨论】:

    标签: c# .net visual-studio envdte


    【解决方案1】:

    我将借此机会发布我所做的概念证明,因为有人告诉我它无法完成 - 在这里和那里进行一些调整,将其扩展到提取方法中所有引用的类型 - 为它的大小和缺少前言道歉,但它有些评论:

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

    【讨论】:

      【解决方案2】:

      如果您可以访问此方法的 IL,您也许可以做一些合适的事情。也许看看开源项目ILSpy,看看你是否可以利用他们的任何工作。

      【讨论】:

      • 据我所知我不能,它可能会在现有项目的构建阶段之前使用。
      • @Silx,很公平。那么您可以访问什么?要 100% 确定涉及哪些类型,您将需要整个类的字符串形式(至少是 using 语句)以及项目引用的程序集列表。
      • 我可以获得整个类的代码(不确定部分类,但我们假设它是可能的)我已经检查了程序集并且可以访问所有外部,如 .dll,但同一解决方案中的跨项目引用可能不会在解决方案构建之前呈现。
      【解决方案3】:

      正如其他人所提到的,如果您有 DLL,则可以使用类似于 ILSpy does in its Analyze feature 的内容(遍历程序集中的所有 IL 指令以查找对特定类型的引用)。

      否则,如果不将文本解析为 C# 抽象语法树并使用解析器,就无法做到这一点 - 可以很好地理解代码的语义以了解示例中的“Bar”是否确实可以从该方法访问的类型的名称(在其“使用”范围内),或者可能是方法的名称、成员字段等...... SharpDevelop 包含一个 C# 解析器(称为“NRefactory”)并且还包含这样的解析器,您可以通过查看 this thread 来考虑采用该选项,但请注意,将其设置为正常工作需要大量工作。

      【讨论】:

        【解决方案4】:

        我刚刚发布了一个像这样的 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;
            }
        

        这里省略了一些细节

        • IsBusinessCallIsConstructorCallTryResolve 的实现,因为它们是微不足道的,仅用作说明

        希望有帮助

        【讨论】:

          【解决方案5】:

          我能想到的最接近的就是表达式树。看看the documentation from Microsoft

          但是它们非常有限,仅适用于简单的表达式,而不适用于带有语句体的完整方法。

          编辑:由于张贴者的目的是找到类耦合和使用的类型,我建议使用像 NDepend 这样的商业工具来进行代码分析作为一个简单的解决方案。

          【讨论】:

          • 任何建议如何将字符串解析为表达式树或 lamda?
          • 您打算将此功能用于什么用途?表达式树可能对您的需求太有限了。它们更适用于在运行时动态修改代码。
          • 我需要一个方法内部使用的类型列表。它将用于查找类耦合(引用,调用任何你称之为的东西)。所以基本上我需要检查 A 类是否在其体内的任何地方调用 B 类。由于项目暴力搜索中可能有成千上万的类。
          【解决方案6】:

          这绝对不能通过反射(GetMethod()、表达式树等)来完成。正如您所提到的,使用 EnvDTE 的 CodeModel 是一个选项,因为您在那里逐行获得C#,但是在 Visual Studio 之外使用它(即处理一个已经存在的函数,而不是在您的编辑器窗口中)几乎是不可能的,恕我直言。

          但我可以推荐Mono.Cecil,它可以逐行处理CIL 代码(在方法内),您可以在您引用的任何程序集中的任何方法上使用它。然后,您可以检查每一行是否是变量声明(如 string x = "test" 或 methodCall,您可以获取这些行中涉及的类型。

          【讨论】:

            【解决方案7】:

            通过反射,您可以get the method。这将返回一个MethodInfo 对象,并且使用此对象您无法获取方法中使用的类型。所以我认为答案是你无法在C# 获得这个原生。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2011-08-13
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2023-01-20
              • 2013-07-07
              相关资源
              最近更新 更多