【问题标题】:Look if a method is called inside a method using reflection查看是否在使用反射的方法内部调用了方法
【发布时间】:2011-04-21 08:15:31
【问题描述】:

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

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

【问题讨论】:

  • 没有办法直接使用反射来做到这一点。有人早些时候发布了similar question,您可能想查看这些答案,看看是否有任何帮助您做您想做的事情。
  • 你能解释一下你在这里试图解决的问题吗?我倾向于发现,当某些事情看起来真的很困难时,我正试图以错误的方式解决它。反射主要用于发现对象的“形状”,而不是其实现方式。
  • 这是一个复杂的情况,但我正在构建一个 Blend 插件。这个插件必须能够生成一些 C# 代码(调用方法的代码)。在它生成代码之前,它必须检查它是否已经存在。
  • @Lazurus:当它存在时,我会在我的插件中显示类名。
  • @Guffa:我不想调用方法。如果 MethodBody 中没有对特定方法的调用,我想在 MethodBody 内部生成 C# 代码。

标签: c# .net reflection


【解决方案1】:

使用 Mono.Cecil。它是一个独立的程序集,可以在 Microsoft .NET 和 Mono 上运行。 (我想我在写下面的代码时使用的是 0.6 或更高版本)

假设您有许多程序集

IEnumerable<AssemblyDefinition> assemblies;

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

下面的 sn-p 将枚举所有类型的这些程序集中方法的所有用法

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

调用树搜索

这是一个有效的 sn-p,它允许您递归搜索到(选定的)相互调用(间接)的方法。

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()
  • 仅在调用操作(call、calli、callvirt ...)中选择性地选择MethodReferences 的用法(参见//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)。 你可以做任何你想做的事,不要忘记:你将拥有完整的静态分析调用堆栈供你使用,所以你实际上可以用所有这些信息做一些非常简洁的事情

【讨论】:

  • 我找不到 BaseCodeVisitor 类,你能告诉我在哪个程序集中我能找到这个吗?
  • 您需要Mono.Cecil。它是一个独立的程序集,可以在 Microsoft .NET 和 Mono 上运行。 (我想我在写这段代码时使用的是 0.6 或更高版本
  • 我知道这是一个旧线程,但我刚刚尝试了这段代码,也找不到 BaseCodeVisitor 类。我引用了 Mono.Cecil,并导入了命名空间。我查看了 DLL 本身(使用反编译器)并且没有同名的类。我有最新的 Momo.Cecil.dll,它是 0.9.5.0。有任何想法吗?谢谢
  • @AvrohomYisroel 我想界面已经改变。我没有时间去了解怎么做,但我想这不会太戏剧化。 (我知道所有的集合类型都已经过大修以使用 .NET 泛型)
  • 您有您使用的 DLL 二进制文件的副本吗?我试图从 GitHub 获得 0.6 版本,但要么我对源代码控制的理解被打破,要么那个网站很糟糕。我不知道如何下载特定版本。我想要的只是一个可以与这里的代码一起工作的二进制文件。如果你有,请发邮件给我mryossu@hotmail.com - 谢谢:)
【解决方案2】:

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

在某些情况下,捕获异常比阻止异常生成要方式便宜。这是一个典型的例子。您可以获得方法体的 IL,但 Reflection 不是反汇编程序。反汇编程序也不是真正的解决方案,您需要反汇编整个调用树来实现您想要的行为。毕竟,主体中的方法调用本身可以调用方法等等。在编译 IL 时捕获抖动将引发的异常要简单得多。

【讨论】:

    【解决方案3】:

    可以使用 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可以调整,决定你感兴趣的帧数。

    【讨论】:

    • 我曾经因为尝试这个而被咬过 - 调试和发布版本中的帧号可能不同,我花了一段时间才弄明白。 (可以使用属性关闭此优化,但这可能会导致性能下降)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-07-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多