到目前为止,我已经做了一年的专业软件工程师,并获得了计算机科学学位。我知道c++和C中的断言有一段时间了,但直到最近才知道它们存在于c#和。net中。

我们的生产代码不包含任何断言,我的问题是这个…

我应该开始在我们的生产代码中使用断言吗?如果可以,什么时候使用它最合适?这样做会更有意义吗

Debug.Assert(val != null, "message");

or

if ( val == null )
    throw new exception("message");

当前回答

我已经在这里阅读了答案,我认为我应该添加一个重要的区别。使用断言有两种非常不同的方式。一种是作为临时的开发人员快捷方式,表示“这不应该真的发生,所以如果它发生了,就告诉我,这样我就可以决定怎么做”,有点像一个条件断点,用于你的程序能够继续的情况。另一种方法是在代码中假设有效的程序状态。

在第一种情况下,断言甚至不需要出现在最终代码中。您应该使用Debug。在开发期间断言,如果/当不再需要时,您可以删除它们。如果你想要保留它们或者忘记删除它们,没有问题,因为它们在发布汇编中不会有任何后果。

But in the second case, the assertions are part of the code. They, well, assert, that your assumptions are true, and also document them. In that case, you really want to leave them in the code. If the program is in an invalid state it should not be allowed to continue. If you couldn't afford the performance hit you wouldn't be using C#. On one hand it might be useful to be able to attach a debugger if it happens. On the other, you don't want the stack trace popping up on your users and perhaps more important you don't want them to be able to ignore it. Besides, if it's in a service it will always be ignored. Therefore in production the correct behavior would be to throw an Exception, and use the normal exception handling of your program, which might show the user a nice message and log the details.

跟踪。Assert有实现这一点的完美方法。它不会在生产环境中被删除,并且可以使用app.config配置不同的侦听器。 因此,对于开发来说,默认的处理程序就可以了,对于生产来说,您可以创建一个简单的TraceListener(如下所示),它会抛出一个异常并在生产配置文件中激活它。

using System.Diagnostics;

public class ExceptionTraceListener : DefaultTraceListener
{
    [DebuggerStepThrough]
    public override void Fail(string message, string detailMessage)
    {
        throw new AssertException(message);
    }
}

public class AssertException : Exception
{
    public AssertException(string message) : base(message) { }
}

在产品配置文件中:

<system.diagnostics>
  <trace>
    <listeners>
      <remove name="Default"/>
      <add name="ExceptionListener" type="Namespace.ExceptionTraceListener,AssemblyName"/>
    </listeners>
  </trace>
 </system.diagnostics>

其他回答

如果您希望在生产代码中使用断言(即发布构建),您可以使用Trace。Assert而不是Debug.Assert。

这当然会增加生产可执行文件的开销。

此外,如果您的应用程序在用户界面模式下运行,则默认情况下将显示断言对话框,这可能会让您的用户感到不安。

您可以通过删除DefaultTraceListener来覆盖这种行为:查看Trace的文档。MSDN中的侦听器。

总之,

使用调试。自由断言,以帮助在调试版本中捕获错误。 如果你使用Trace。在用户界面模式下断言,您可能希望删除DefaultTraceListener以避免让用户感到不安。 如果你测试的条件是你的应用程序无法处理的,你可能最好抛出一个异常,以确保执行不会继续。请注意,用户可以选择忽略断言。

在我的书里几乎从来没有。 在绝大多数情况下,如果你想检查一切是否正常,那么就扔掉。

我不喜欢的是,它使调试构建在功能上与发布构建不同。如果调试断言失败,但功能在发布中工作,那么这有什么意义呢?如果断言者早已离开公司,没有人知道这部分代码,那就更好了。然后你就得花点时间去探索这个问题,看看它是不是真的是个问题。如果这是一个问题,那为什么那个人不第一时间扔呢?

对我来说,这建议使用调试。声称你把问题推给别人,自己解决问题。如果某件事应该是这样,但事实并非如此,那就扔掉。

我猜可能有一些性能关键的场景,你想要优化你的断言,它们在那里很有用,但是我还没有遇到这样的场景。

将Debug.Assert()放在代码中任何需要进行完整性检查以确保不变量的地方。当编译Release版本时(即没有DEBUG编译器常量),对DEBUG . assert()的调用将被删除,因此它们不会影响性能。

在调用Debug.Assert()之前仍然应该抛出异常。断言只是确保在开发过程中一切都如预期的那样。

您应该始终使用第二种方法(抛出异常)。

同样,如果你是在生产环境中(并且有一个发布版本),抛出一个异常(在最坏的情况下让应用程序崩溃)比处理无效值和可能破坏客户数据(这可能要花费数千美元)要好。

所有的断言应该是代码,可以优化为:

Debug.Assert(true);

因为它检验的是你已经假设为真的东西。例如:

public static void ConsumeEnumeration<T>(this IEnumerable<T> source)
{
  if(source != null)
    using(var en = source.GetEnumerator())
      RunThroughEnumerator(en);
}
public static T GetFirstAndConsume<T>(this IEnumerable<T> source)
{
  if(source == null)
    throw new ArgumentNullException("source");
  using(var en = source.GetEnumerator())
  {
    if(!en.MoveNext())
      throw new InvalidOperationException("Empty sequence");
    T ret = en.Current;
    RunThroughEnumerator(en);
    return ret;
  }
}
private static void RunThroughEnumerator<T>(IEnumerator<T> en)
{
  Debug.Assert(en != null);
  while(en.MoveNext());
}

在上面,有三种不同的空参数方法。第一个接受它为允许的(它只是什么都不做)。第二个则抛出异常供调用代码处理(或不处理,导致错误消息)。第三种假设它不可能发生,并断言它是这样的。

在第一种情况下,没有问题。

在第二种情况下,调用代码有一个问题——它不应该用null调用GetFirstAndConsume,所以它会返回一个异常。

在第三种情况下,这段代码有一个问题,因为在调用它之前,它应该已经检查过en != null,所以它不是真的是一个错误。或者换句话说,它应该是理论上可以优化为Debug.Assert(true)的代码,sicne en != null应该始终为真!