Mira si se llama un método dentro de un método usando la reflexión

Estoy trabajando con la reflexión y actualmente tengo un MethodBody . ¿Cómo puedo verificar si se llama un método específico dentro del MethodBody?

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

Use Mono.Cecil . Es un ensamblaje independiente que funcionará tanto en Microsoft .NET como en Mono. (Creo que utilicé la versión 0.6 o menos cuando escribí el código a continuación)

Digamos que tiene varias asambleas

 IEnumerable assemblies; 

Obtener estos usando AssemblyFactory (cargar uno?)

El siguiente fragmento enumeraría todos los usos de métodos en todos los tipos de estos ensamblajes

 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(); 

Esto devolverá todas las referencias a los métodos (incluyendo el uso en la reflexión, o para construir expresiones que pueden o no ser ejecutadas). Como tal, esto probablemente no sea muy útil, excepto para mostrarle lo que se puede hacer con la API Cecil sin demasiado esfuerzo 🙂

Tenga en cuenta que esta muestra supone una versión algo más antigua de Cecil (la que se encuentra en las versiones mono convencionales). Las versiones más nuevas son

  • más sucinto (mediante el uso de fuertes colecciones genéricas tipadas)
  • Más rápido

Por supuesto, en su caso podría tener una única referencia de método como punto de partida. Supongamos que desea detectar cuándo se puede llamar realmente ‘mytargetmethod’ directamente dentro del ‘punto de partida’:

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

Búsqueda de árbol de llamadas

Aquí hay un fragmento de trabajo que le permite buscar recursivamente métodos (seleccionados) que se llaman entre sí (indirectamente).

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

Notas

  • hacer algún error manejando una Resolve() (tengo un método de extensión TryResolve() para el propósito)
  • Opcionalmente, seleccione los usos de MethodReferences en una operación de llamada (call, calli, callvirt …) solamente ( vea //TODO )

Uso típico:

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

Tenga en cuenta que la compleción de una función como DetectRequestUsage para adaptarse a sus necesidades depende por completo de usted (edite: pero vea aquí ). Puede hacer lo que quiera, y no lo olvide: tendrá a su disposición la stack de llamadas completa y estáticamente analizada, para que pueda hacer cosas geniales con toda esa información .

Antes de generar código, debe verificar si ya existe

Hay algunos casos en los que capturar una excepción es mucho más barato que evitar que se genere. Este es un buen ejemplo. Puede obtener el IL para el cuerpo del método, pero Reflection no es un desensamblador. Tampoco es un desensamblador una solución real, tendría que desmontar todo el árbol de llamadas para implementar su comportamiento deseado. Después de todo, una llamada a un método en el cuerpo podría llamar un método, etcétera. Es mucho más simple detectar la excepción que arrojará el jitter cuando comstack el IL.

Uno puede usar la clase 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); 

El 1 se puede ajustar y determina el número de fotogtwigs que le interesan.