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

有没有办法在C#方法中使用所有类型?

例如,

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

会返回:Bar,string和TEST。

我现在可以得到的是使用EnvDTE.CodeFunction的方法正文。 也许有更好的方法来实现它,而不是尝试解析这段代码。

我将借此机会发布我所做的概念validation,因为有人告诉我无法做到 – 在这里和那里进行一些调整,扩展它以提取所有内容是相对微不足道的。引用方法中的类型 – 为它的大小道歉并且没有前言,但它有点评论:

 void Main() { Func addOne = i => i + 1; Console.WriteLine(DumpMethod(addOne)); Func 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)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 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在其Analyzefunction中所做的事情(迭代程序集中的所有IL指令以查找对特定类型的引用)。

否则,如果不将文本解析为C#抽象语法树,并使用解析器,就无法做到这一点 – 这样可以很好地理解代码的语义,以便知道示例中的“Bar”是否确实是可以从该方法访问的类型(在其“使用”范围内),或者可能是方法,成员字段等的名称… SharpDevelop包含一个C#解析器(称为“NRefactory”)并且还包含这样的解析器,您可以通过查看此主题来研究追求该选项,但要注意,将其设置为正常工作是相当多的工作。

我刚刚发布了一个广泛的例子, how to use Mono.Cecil to do static code analysis

我还展示了一个CallTreeSearch枚举器类,它可以静态分析调用树,查找某些有趣的东西并使用自定义提供的选择器函数生成结果,因此您可以使用“有效负载”逻辑插入它,例如

  static IEnumerable SearchMessages(TypeDefinition uiType, bool onlyConstructions) { return uiType.SearchCallTree(IsBusinessCall, (instruction, stack) => DetectTypeUsage(instruction, stack, onlyConstructions)); } internal class TypeUsage : IEquatable { public TypeReference Type; public Stack Stack; #region equality // ... omitted for brevity ... #endregion } private static TypeUsage DetectTypeUsage( Instruction instruction, IEnumerable 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(stack.Reverse()), Type = resolve}; return null; } 

这遗漏了一些细节

  • IsBusinessCallIsConstructorCallTryResolve实现,因为这些是微不足道的,仅作为说明

希望有所帮助

我能想到的最接近的是表达树。 请查看Microsoft的文档 。

它们非常有限,只能处理简单的表达式,而不能处理语句体的完整方法。

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

这绝对不能通过reflection(GetMethod(),Expression Trees等)来完成。 正如你所提到的,使用EnvDTE的CodeModel是一个选项,因为你在那里得到了逐行的C# ,但在Visual Studio之外使用它(即处理现有的函数,而不是在你的编辑器窗口中)几乎是不可能的,恕我直言。

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

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