查看是否在使用reflection的方法内调用方法

我正在使用reflection,目前有一个MethodBody 。 如何检查MethodBody中是否调用了特定方法?

Assembly assembly = Assembly.Load("Module1"); Type type = assembly.GetType("Module1.ModuleInit"); MethodInfo mi = type.GetMethod("Initialize"); MethodBody mb = mi.GetMethodBody(); 

使用Mono.Cecil 。 它是一个独立的程序集,可以在Microsoft .NET和Mono上运行。 (当我编写下面的代码时,我认为我使用的是0.6版或者之后的版本)

假设你有许多组件

 IEnumerable assemblies; 

使用AssemblyFactory获取这些(加载一个?)

以下代码段将枚举所有类型的这些程序集中方法的所有用法

 methodUsages = assemblies .SelectMany(assembly => assembly.MainModule.Types.Cast()) .SelectMany(type => type.Methods.Cast()) .Where(method => null != method.Body) // allow abstracts and generics .SelectMany(method => method.Body.Instructions.Cast()) .Select(instr => instr.Operand) .OfType(); 

这将返回对方法的所有引用(因此包括在reflection中使用,或构造可能执行或可能不执行的表达式)。 因此,这可能不是很有用,除了向您展示Cecil API可以做些什么而不需要太多努力:)

请注意,此示例假设Cecil(主流单声道版本中的版本)的版本较旧。 较新的版本是

  • 更简洁(通过使用强类型的通用集合)
  • 快点

当然,在您的情况下,您可以使用单个方法引用作为起点。 假设您想要检测何时可以直接在’startingpoint’中调用’mytargetmethod’:

 MethodReference startingpoint; // get it somewhere using Cecil MethodReference mytargetmethod; // what you are looking for bool isCalled = startingpoint .GetOriginalMethod() // jump to original (for generics eg) .Resolve() // get the definition from the IL image .Body.Instructions.Cast() .Any(i => i.OpCode == OpCodes.Callvirt && i.Operand == (mytargetmethod)); 

呼叫树搜索

这是一个工作片段,允许您递归搜索(间接)相互调用的(选定)方法。

 using System; using System.Collections; using System.Collections.Generic; using System.Linq; using Mono.Cecil; using Mono.Cecil.Cil; namespace StackOverflow { /* * breadth-first lazy search across a subset of the call tree rooting in startingPoint * * methodSelect selects the methods to recurse into * resultGen generates the result objects to be returned by the enumerator * */ class CallTreeSearch : BaseCodeVisitor, IEnumerable where T : class { private readonly Func _methodSelect; private readonly Func, T> _transform; private readonly IEnumerable _startingPoints; private readonly IDictionary> _chain = new Dictionary>(); private readonly ICollection _seen = new HashSet(new CompareMembers()); private readonly ICollection _results = new HashSet(); private Stack _currentStack; private const int InfiniteRecursion = -1; private readonly int _maxrecursiondepth; private bool _busy; public CallTreeSearch(IEnumerable startingPoints, Func methodSelect, Func, T> resultGen) : this(startingPoints, methodSelect, resultGen, InfiniteRecursion) { } public CallTreeSearch(IEnumerable startingPoints, Func methodSelect, Func, T> resultGen, int maxrecursiondepth) { _startingPoints = startingPoints.ToList(); _methodSelect = methodSelect; _maxrecursiondepth = maxrecursiondepth; _transform = resultGen; } public override void VisitMethodBody(MethodBody body) { _seen.Add(body.Method); // avoid infinite recursion base.VisitMethodBody(body); } public override void VisitInstructionCollection(InstructionCollection instructions) { foreach (Instruction instr in instructions) VisitInstruction(instr); base.VisitInstructionCollection(instructions); } public override void VisitInstruction(Instruction instr) { T result = _transform(instr, _currentStack); if (result != null) _results.Add(result); var methodRef = instr.Operand as MethodReference; // TODO select calls only? if (methodRef != null && _methodSelect(methodRef)) { var resolve = methodRef.Resolve(); if (null != resolve && !(_chain.ContainsKey(resolve) || _seen.Contains(resolve))) _chain.Add(resolve, new Stack(_currentStack.Reverse())); } base.VisitInstruction(instr); } public IEnumerator GetEnumerator() { lock (this) // not multithread safe { if (_busy) throw new InvalidOperationException("CallTreeSearch enumerator is not reentrant"); _busy = true; try { int recursionLevel = 0; ResetToStartingPoints(); while (_chain.Count > 0 && ((InfiniteRecursion == _maxrecursiondepth) || recursionLevel++ <= _maxrecursiondepth)) { // swapout the collection because Visitor will modify var clone = new Dictionary>(_chain); _chain.Clear(); foreach (var call in clone.Where(call => HasBody(call.Key))) { // Console.Error.Write("\rCallTreeSearch: level #{0}, scanning {1,-20}\r", recursionLevel, call.Key.Name + new string(' ',21)); _currentStack = call.Value; _currentStack.Push(call.Key); try { _results.Clear(); call.Key.Body.Accept(this); // grows _chain and _results } finally { _currentStack.Pop(); } _currentStack = null; foreach (var result in _results) yield return result; } } } finally { _busy = false; } } } private void ResetToStartingPoints() { _chain.Clear(); _seen.Clear(); foreach (var startingPoint in _startingPoints) { _chain.Add(startingPoint, new Stack()); _seen.Add(startingPoint); } } private static bool HasBody(MethodDefinition methodDefinition) { return !(methodDefinition.IsAbstract || methodDefinition.Body == null); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } } internal class CompareMembers : IComparer, IEqualityComparer where T: class, IMemberReference { public int Compare(T x, T y) { return StringComparer.InvariantCultureIgnoreCase.Compare(KeyFor(x), KeyFor(y)); } public bool Equals(T x, T y) { return KeyFor(x).Equals(KeyFor(y)); } private static string KeyFor(T mr) { return null == mr ? "" : String.Format("{0}::{1}", mr.DeclaringType.FullName, mr.Name); } public int GetHashCode(T obj) { return KeyFor(obj).GetHashCode(); } } } 

笔记

  • 做一些error handlingResolve() (我有一个扩展方法TryResolve()为此目的)
  • 可选择MethodReferences在调用操作(call,calli,callvirt …)中选择MethodReferences用法( 参见//TODO

典型用法:

 public static IEnumerable SearchCallTree(this TypeDefinition startingClass, Func methodSelect, Func, T> resultFunc, int maxdepth) where T : class { return new CallTreeSearch(startingClass.Methods.Cast(), methodSelect, resultFunc, maxdepth); } public static IEnumerable SearchCallTree(this MethodDefinition startingMethod, Func methodSelect, Func, T> resultFunc, int maxdepth) where T : class { return new CallTreeSearch(new[] { startingMethod }, methodSelect, resultFunc, maxdepth); } // Actual usage: private static IEnumerable SearchMessages(TypeDefinition uiType, bool onlyConstructions) { return uiType.SearchCallTree(IsBusinessCall, (instruction, stack) => DetectRequestUsage(instruction, stack, onlyConstructions)); } 

请注意 ,像DetectRequestUsage这样的function可以完全满足您的需求(编辑: 但请参见此处 )。 你可以做任何你想做的事情,不要忘记:你将拥有完整的静态分析调用堆栈,所以你实际上可以用所有这些信息做漂亮的事情

在生成代码之前,必须检查它是否已经存在

在某些情况下,捕获exception比阻止exception生成要便宜。 这是一个很好的例子。 您可以获取方法体的IL,但Reflection不是反汇编程序。 反汇编程序也不是一个真正的修复程序,你可以反汇编整个调用树来实现你想要的行为。 毕竟,正文中的方法调用本身可以调用方法,等等。 捕获抖动在编译IL时抛出的exception要简单得多。

可以使用StackTrace类:

 System.Diagnostics.StackTrace st = new System.Diagnostics.StackTrace(); System.Diagnostics.StackFrame sf = st.GetFrame(1); Console.Out.Write(sf.GetMethod().ReflectedType.Name + "." + sf.GetMethod().Name); 

可以调整1并确定您感兴趣的帧数。