C# 反射和查找所有引用

43
给定一个DLL文件,我想能够找到该DLL文件中所有对某个方法的调用。我应该如何做?
实质上,我应该如何以编程方式执行Visual Studio已经完成的操作?
我不想使用类似.NET Reflector这样的工具来完成此操作,但反射是可以的,也可能是必要的。

2
您需要仅检测对该方法的静态调用吗?还是需要检测使用反射和反射发射的调用? - Darin Dimitrov
这是否适用于非托管代码?并非所有的DLL都支持反射。 - James Keesey
1
@Darin:那些东西是锦上添花的,但并非必要。 @James:我不认为我需要担心非托管代码。如果dll不支持反射,我该怎么做? - user420667
6个回答

56
为了找到使用方法MyClass.Foo()的位置,您需要分析所有对包含MyClass的程序集有引用的所有类。我编写了一个简单的概念验证代码来说明这个过程。在我的示例中,我使用这个库(它只是由Jb Evain编写的一个单独的.cs文件):
我编写了一个小测试类来进行分析:
public class TestClass
{
    public void Test()
    {
        Console.WriteLine("Test");
        Console.Write(10);
        DateTime date = DateTime.Now;
        Console.WriteLine(date);
    }
}

我写了这段代码来打印出在TestClass.Test()中使用的所有方法:

MethodBase methodBase = typeof(TestClass).GetMethod("Test");
var instructions = MethodBodyReader.GetInstructions(methodBase);

foreach (Instruction instruction in instructions)
{
    MethodInfo methodInfo = instruction.Operand as MethodInfo;

    if(methodInfo != null)
    {
        Type type = methodInfo.DeclaringType;
        ParameterInfo[] parameters = methodInfo.GetParameters();

        Console.WriteLine("{0}.{1}({2});",
            type.FullName,
            methodInfo.Name,
            String.Join(", ", parameters.Select(p => p.ParameterType.FullName + " " + p.Name).ToArray())
        );
    }
}

它给了我以下输出:

System.Console.WriteLine(System.String value);
System.Console.Write(System.Int32 value);
System.DateTime.get_Now();
System.Console.WriteLine(System.Object value);

这个例子显然远远没有完善,因为它不处理ref和out参数,也不处理泛型参数。我相信还忘记了其他细节。它只是表明可以完成。


我收到了这个错误信息:“值不在预期范围内。”来自这段代码:this.body = method.GetMethodBody(); if (this.body == null) throw new ArgumentException(); 我想知道可能是如何出现没有方法体(或其他可能的情况)。你有什么想法吗? - ekkis
1
@ekkis:抽象方法(抽象类的方法)是没有方法体的。您可以通过检查 MethodBase.IsAbstract 来检查该方法是否为抽象方法。 - Elian Ebbing
1
@ElianEbbing,显然有其他类型的无主方法。看看 System.GetType() - 我不确定这个方法有什么特殊之处,但是 .GetMethodBody() 也为空。我想知道在这些情况下我们应该抛出 ArgumentException 还是只是默默地失败。 - ekkis
@ekkis - 你说得对。看起来核心的 .net 库包含一些没有实体的方法。这些方法被装饰了 [MethodImpl(MethodImplOptions.InternalCall)] 属性,并在 CL 运行时本身中实现。我认为你是对的,如果出现这种情况,GetInstructions() 方法应该返回一个空列表而不是异常。 - Elian Ebbing
3
NuGetو›؟ن»£MethodBodyReaderçڑ„و–¹و،ˆوک¯ن½؟用NuGetهŒ…mono.reflection,ه¹¶ن½؟用var instructions = Disassembler.GetInstructions(methodBase)و‌¥èژ·هڈ–وŒ‡ن»¤م€‚ - Joep Geevers
显示剩余2条评论

5

+1:请注意,您需要在某处搜索IL阅读器 - 据我所知,反射器的不是直接可用的... - Alexei Levenkov
好的文章。但它建议使用Reflector来处理方法依赖关系。我想它们都归结为解析IL代码。 - user420667

3
仅凭反射无法找到给定程序集中方法的所有引用。反射只能为特定方法的代码体提供字节数组 (MethodInfo.GetMethodBody.GetILAsByteArray),您需要自行解析以查找对其他方法的引用。有多个公开可用的 "CIL reader" 库(我没有使用过它们,希望有人能提供更多信息)。
添加 FxCop 选项 - 根据您的情况,您可以重用 FxCop(Visual Studio 代码分析)提供的 CIL 解析逻辑,并在其作为代码分析的一部分运行时添加自定义规则。

1
我建议您反射Visual Studio程序集,看看能否在反向工程的代码库中找到它。我相信VS实际上是在浏览代码而不是反射。正如Michael所说,反射非常适合确定程序集的位,但不适用于这些位的使用者。然而,我还没有重新审查反射以确认我的怀疑。

0

嘿,这是一个假设您想在当前程序集中搜索所有调用的示例。我编写了这个代码,试图获取参数值以便为具有某些默认值的方法设置一些约束条件。但是我无法成功获取参数值,只得到了类型和默认值。

var currentAssembly = Assembly.GetExecutingAssembly();

foreach (var method in currentAssembly.GetTypes().Where(type => type.IsClass)
                                      .SelectMany(cl => cl.GetMethods())
                                      .OfType<MethodBase>()
                                      .SelectMany(mb => mb.GetInstructions())
                                      .Select(inst => inst.Operand).OfType<MethodInfo>()
                                      .Where(mi => mi.Name == "<YourMethodName>"))
{
    //here are your calls.
}

希望它有所帮助。

-2

请参考 Stack Overflow 上的问题Get a list of functions for a DLL

从上面提取的内容(感谢 Jon Skeet):

对于特定的程序集,您可以使用 Assembly.GetTypes 获取类型,然后对于每个类型调用 Type.GetMethods()、Type.GetProperties() 等,或者只是 Type.GetMembers()。

但是,对于插件功能,通常最好有一个公共接口,插件必须实现该接口 - 这减少了需要使用的反射量。使用 Type.IsAssignableFrom() 检查类型是否与特定接口兼容。

您还可以查看托管可扩展性框架,它可以使实现扩展系统更加容易。


2
我不认为这个答案有助于找到调用给定方法的方法。它可以列出目标程序集中的类型、方法、属性等,但OP正在寻找一种检测给定方法是否调用另一个方法的方法。 - Darin Dimitrov
这不是原始发布者所询问的内容。 - Gregory A Beamer
这不是OP所寻找的。这列出了方法,但没有它们的调用。 - jason

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接