何时在运行时发生方法解析/查找?

时间:2017-02-22 20:51:22

标签: c#

我有以下代码在调试模式下向消息添加调试信息:

Tester DLL中的代码

private string GetMessage(Foo foo)
{
    var messageBuilder = new StringBuilder();

    // add message info

#if DEBUG
    messageBuilder.AppendLine(foo.GetDebuggingInfo());
#endif

    return messageBuilder.ToString();
}

受测试的图书馆

通过托管C ++ Wrapper项目从C#调用的C ++库

#ifdef DEBUG
string Foo::GetDebuggingInfo()
{
    ...
}
#endif

现在我遇到的问题是Foo在一个单独的库中声明,GetDebuggingInfo也是有条件编译的。出于向后兼容性的原因,测试器DLL针对所有先前发布的版本运行正在测试的库,以确保我们的服务器发送的输出将继续按预期在所有不同版本的客户端中被摄取。然后将测试代码与要测试的库的所需版本一起复制到文件夹,而不是编译器编译的版本。一切都运行良好,直到我的代码到达GetMessage,当我遇到MethodNotFoundException时就是这样。

我第一次尝试解决这个问题,我想在调用函数之前使用反射来确保函数存在,如下所示:

#if DEBUG
    if (foo.GetType().GetMethod("GetDebuggingInfo", BindingFlags.Instance | BindingFlags.Public) != null)
        messageBuilder.AppendLine(foo.GetDebuggingInfo());
#endif

我认为即使Tester DLL是Debug版本而且Test下的DLL是发布版本,也会阻止调用该方法。还是同样的问题。添加一些额外的跟踪调试语句后,我发现GetMessage甚至没有输入。在意识到这一点后,我尝试将有问题的方法调用移入其自身的功能中,一切正常。

#if DEBUG
    // reflection check remains to keep the call from blowing
    // up when Tester is DEBUG and Under Test is RELEASE
    if (foo.GetType().GetMethod("GetDebuggingInfo", BindingFlags.Instance | BindingFlags.Public) != null)
        AddDebuggingInfo(messageBuilder, foo);
#endif

#if DEBUG
private void AddDebuggingInfo(StringBuilder messageBuilder, Foo foo) =>
    messageBuilder.AppendLine(foo.GetDebuggingInfo());
#endif

所以即使我有一个满足我需求的工作解决方案,我也不明白我的最终解决方案是如何工作的,当我发现反射检查足以防止代码抛出MethodNotFoundException时。那么在运行时,方法何时被查找并解决?

2 个答案:

答案 0 :(得分:2)

当该对象的容器被JIT时,所有对象都被“绑定”,通常一个函数将在第一次执行时被JIT。

当您将GetDebuggingInfo移动到AddDebuggingInfo时,只有在您实际输入AddDebuggingInfo方法时,才会尝试绑定GetDebuggingInfo并失败。

另一种解决方法是,如果声明变量dynamic

,您甚至可以推迟绑定
#if DEBUG
    dynamic tmpFoo = foo;
    if (foo.GetType().GetMethod("GetDebuggingInfo", BindingFlags.Instance | BindingFlags.Public) != null)
        messageBuilder.AppendLine(tmpFoo.GetDebuggingInfo());
#endif

动态导致绑定发生在函数的调用点,而不是在包含函数的内容中。

另一种选择就是使用你要检查null的MethodInfo来调用。

#if DEBUG
    var info = foo.GetType().GetMethod("GetDebuggingInfo", BindingFlags.Instance | BindingFlags.Public);
    if (info != null)
    {
        var text = (string)info.Invoke(foo, null);
        messageBuilder.AppendLine(text);
    } 
#endif

答案 1 :(得分:0)

查看[ConditionalAttribute]

它执行#if .... #endif代码的功能,但它也会删除对装饰方法的调用。

一个限制:装饰方法必须返回void。因此,您最终会将StringBuilder作为参数传递。

  

将ConditionalAttribute应用于方法指示编译器不应将对该方法的调用编译为Microsoft中间语言(MSIL),除非定义了与ConditionalAttribute关联的条件编译符号。如果将此属性应用于不返回void的方法,则会在Visual Studio中出现编译错误。

static void Main()
{    
    var sb = new StringBuilder();           
    Console.WriteLine("Calling AppendDebugInfo1");
    AppendDebugInfo1(sb);
    Console.WriteLine("Calling AppendDebugInfo2");
    AppendDebugInfo2(sb);
    Console.WriteLine(sb);
}

[Conditional("DEBUG")]
public static void AppendDebugInfo1(StringBuilder sb)
{
    sb.AppendLine("DEBUG is defined");
}

[Conditional("DEBUG"), Conditional("SUPERDEBUG"))]  
public static void AppendDebugInfo2(StringBuilder sb)
{
    sb.AppendLine("DEBUG or SUPERDEBUG is defined! whoaaaaa!");
}