我有以下代码在调试模式下向消息添加调试信息:
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
时。那么在运行时,方法何时被查找并解决?
答案 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)
它执行#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!");
}