关于c#:代码在发布与调试模式下的行为方式不同

Code is behaving differently in Release vs Debug Mode

我们有一些单元测试在发布模式和调试模式下运行时失败。如果我在发布模式下附加了一个调试器,测试就通过了。这里有太多的代码要发布,所以我只是在调试发布模式问题时寻找最佳实践。我检查过:

  • 调试和释放预处理器指令,但我没有找到任何指令。
  • 条件方法

解决方案:在本例中,这是因为我比较了浮点变量是否相等。如果不进行主要的重构,我无法将浮点数更改为十进制,因此我添加了一个扩展方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public static class FloatExtension
{
    public static bool AlmostEquals(this float f1, float f2, float precision)
    {
        return (Math.Abs(f1 - f2) <= precision);
    }

    public static bool AlmostEquals(this float f1, float f2)
    {
        return AlmostEquals(f1, f2, .00001f);
    }

    public static bool AlmostEquals(this float? f1, float? f2)
    {
        if (f1.HasValue && f2.HasValue)
        {
            return AlmostEquals(f1.Value, f2.Value);
        }
        else if (f1 == null && f2 == null)
        {
            return true;
        }
        return false;
    }
}


有一件事可能会导致你所看到的行为,那就是一个导致比赛条件的错误。附加调试程序可以更改代码的时间,从而不再触发争用条件。

要修复它,请在多个线程访问数据时适当地使用同步。

I am comparing some float values in the IsEqual method.

听起来是个很糟糕的主意。您不应该为了相等而比较浮点数,因为浮点数计算不是100%精确的,您可以得到表示和舍入错误。比较看它们是否足够接近。对于涉及金钱的计算,您可能希望使用decimal类型。


因为它似乎与浮点相关,所以有很多事情会出错。见:C-32位和64位的数学运算结果不一致和.NET上的双精度问题

有太多的东西可以用浮点数来处理。比较浮点数是否相等是一个普遍的问题,你应该检查一下比合理的epsilon小的差别。


这通常是因为默认情况下,调试构建没有进行优化,即使启用了它,调试时的行为也非常不同。可以从"属性"->"生成"选项卡上的所有程序集的项目设置中禁用"优化代码"。

当然还有其他的改变会引起差异,比如你提到的条件方法就是其中之一。我发现这些很少是导致问题的原因,对我来说,它几乎总是优化器。

优化器的经典gotcha包括"内联"的方法,这样它们就无法出现在调用堆栈上。这会导致在使用System.Diagnostics.StackFrame类确定当前执行点时出现问题。同样,这也会影响methodbase.getcurrentmethod或其他依赖于执行方法的函数/行为的结果。

当然,我看到优化器所做的很多事情我根本无法解释。一个这样的例子在"hashderivedBytes"后被记录和讨论——取代了rfc2898派生字节,但为什么呢?但我从未解开这个谜。我只知道,当用于生成一系列派生字节时,优化器只是简单地中断了rfc2898派生字节。奇怪的是,只有当生成的字节不能被所使用的哈希算法(20)的大小平均整除,并且在前20个字节之后才产生不正确的结果时,这种情况才会发生。

事实上,对编译器来说,对代码产生不利影响的优化并不是一件新鲜事。大多数老学校的C++开发者会直接告诉你,然后,就像我一样,进入一些关于他们是如何工作的冗长的故事;


你应该问自己的问题-

  • 我的代码是线程化的吗?时间差异将影响输出
  • 是否有人使用副作用的表达式调用debug.assert()?
  • 哪些对象实现IDisposable(),并以更改状态的方式执行某些操作?
  • 您是否正在调用非托管代码?
  • 在这种情况下,3号很可能是个坏孩子。垃圾收集在调试和发布中可能非常不同,您可能会发现当对象被垃圾收集时,它会影响稍后单元测试的结果。

    仅供参考,如果您使用的是nunit和testdriven.net,那么这两个函数以不同的顺序运行测试。


    为了增加我的两分钱,我最近发现我在测试调用的SQL过程中进行了日期比较。这些日期都是在测试过程之前自动生成的,并且值被插入到数据库中,因此有时它们完全相同(使用runtests时),导致表联接返回空值。不是我所期望的。显然,在调试模式下,由于我正在缓慢地进行调试,自动生成的时间会有所不同,这意味着我从未遇到过错误。我通过插入

    线程。线程。睡眠(520)

    在任何地方,行动之间肯定会有延迟。问题解决了。


    正如马克所暗示的,这通常是一个与时间有关的问题的结果,通常是一个竞争条件或同步问题。

    处理此类问题的一种常见方法是在受影响的区域使用"print"语句来显示正在发生的事情。如果打印语句(Console.WriteLineResponse.Write、logging或其他)使问题消失,请将值存储在全局变量中,并在问题出现后打印全局变量。

    最近一次发生在我身上的是从串行端口读取的代码。调试活动导致的时间变化足以影响串行端口中字节的缓冲方式,这改变了缓冲区的解析方式。由于print语句改变了时间,我不得不将数据存储起来,以便稍后输出。