我正在使用反射,目前有一个 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<AssemblyDefinition> assemblies;
使用 AssemblyFactory 获取这些(加载一个?)
以下代码段将枚举所有类型的这些程序集中方法的所有用法
methodUsages = assemblies
.SelectMany(assembly => assembly.MainModule.Types.Cast<TypeDefinition>())
.SelectMany(type => type.Methods.Cast<MethodDefinition>())
.Where(method => null != method.Body) // allow abstracts and generics
.SelectMany(method => method.Body.Instructions.Cast<Instruction>())
.Select(instr => instr.Operand)
.OfType<MethodReference>();
这将返回对方法的所有引用(因此包括在反射中使用,或构造可能执行或可能不执行的表达式)。因此,这可能不是很有用,除了向您展示无需太多努力即可使用 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 e.g.)
.Resolve() // get the definition from the IL image
.Body.Instructions.Cast<Instruction>()
.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<T> : BaseCodeVisitor, IEnumerable<T> where T : class
{
private readonly Func<MethodReference, bool> _methodSelect;
private readonly Func<Instruction, Stack<MethodReference>, T> _transform;
private readonly IEnumerable<MethodDefinition> _startingPoints;
private readonly IDictionary<MethodDefinition, Stack<MethodReference>> _chain = new Dictionary<MethodDefinition, Stack<MethodReference>>();
private readonly ICollection<MethodDefinition> _seen = new HashSet<MethodDefinition>(new CompareMembers<MethodDefinition>());
private readonly ICollection<T> _results = new HashSet<T>();
private Stack<MethodReference> _currentStack;
private const int InfiniteRecursion = -1;
private readonly int _maxrecursiondepth;
private bool _busy;
public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
Func<MethodReference, bool> methodSelect,
Func<Instruction, Stack<MethodReference>, T> resultGen)
: this(startingPoints, methodSelect, resultGen, InfiniteRecursion)
{
}
public CallTreeSearch(IEnumerable<MethodDefinition> startingPoints,
Func<MethodReference, bool> methodSelect,
Func<Instruction, Stack<MethodReference>, 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<MethodReference>(_currentStack.Reverse()));
}
base.VisitInstruction(instr);
}
public IEnumerator<T> 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<MethodDefinition, Stack<MethodReference>>(_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<MethodReference>());
_seen.Add(startingPoint);
}
}
private static bool HasBody(MethodDefinition methodDefinition)
{
return !(methodDefinition.IsAbstract || methodDefinition.Body == null);
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
internal class CompareMembers<T> : IComparer<T>, IEqualityComparer<T>
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(); }
}
}
注释
- 做一些错误处理
Resolve()
(为此我有一个扩展方法TryResolve()
) - 可选择使用
MethodReferences
仅在调用操作(call、calli、callvirt ...)中(参见//TODO
)
典型用法:
public static IEnumerable<T> SearchCallTree<T>(this TypeDefinition startingClass,
Func<MethodReference, bool> methodSelect,
Func<Instruction, Stack<MethodReference>, T> resultFunc,
int maxdepth)
where T : class
{
return new CallTreeSearch<T>(startingClass.Methods.Cast<MethodDefinition>(), methodSelect, resultFunc, maxdepth);
}
public static IEnumerable<T> SearchCallTree<T>(this MethodDefinition startingMethod,
Func<MethodReference, bool> methodSelect,
Func<Instruction, Stack<MethodReference>, T> resultFunc,
int maxdepth)
where T : class
{
return new CallTreeSearch<T>(new[] { startingMethod }, methodSelect, resultFunc, maxdepth);
}
// Actual usage:
private static IEnumerable<TypeUsage> SearchMessages(TypeDefinition uiType, bool onlyConstructions)
{
return uiType.SearchCallTree(IsBusinessCall,
(instruction, stack) => DetectRequestUsage(instruction, stack, onlyConstructions));
}
注意 完成像 DetectRequestUsage 这样的功能来满足您的需求完全取决于您(编辑: but see here )。 您可以为所欲为,不要忘记:您将拥有完整的静态分析调用堆栈供您使用,因此您实际上可以利用所有这些信息做一些非常巧妙的事情!
关于c# - 使用反射查看方法内部是否调用了方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5741350/