.NET 9からDebug.Assertが、アサーションの条件をデフォルトで表示してくれるようになった

[Debug.Asesrt](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0)はプログラム開発中にロジックのエラーを識別するために使われることが多いメソッドです。Debug.Asesrt(Boolean)の引数がfalseなら、メッセージを出力し呼び出しスタックを表示してくれます。デフォルトでは、Debug.Assertメソッドはデバッグビルドのみで機能します。

さて、このDebug.Assertメソッドを使ったこんなプログラムを用意します。

```csharp
using System.Diagnostics;

public static class Program
{
    public static void Main(string[] args)
    {
        SayHello(" ");
    }
    
    static void SayHello(string name)
    {
        Debug.Assert(!string.IsNullOrWhiteSpace(name));
        Console.WriteLine($"Hello, {name}!");
    }
}
```

.NET 8まではこれを実行すると、次のように表示されました。アサーションに失敗したことと、そのアサーションのプログラムのパスと行数がわかるので、便利ですね。

```
Process terminated. Assertion failed.
   at Program.SayHello(String name) in 「ここにプログラムのパスと行数」
   at Program.Main(String[] args) in 「ここにプログラムのパスと行数」
```

.NET 9から、これがさらに便利になって、条件をデフォルトで表示してくれるようになりました。先ほどのプログラムを.NET 9で実行すると、「!string.IsNullOrWhiteSpace(name)」というアサーションの条件を追加して表示してくれます。

```
Process terminated. Assertion failed.
!string.IsNullOrWhiteSpace(name)
   at Program.SayHello(String name) in 「ここにプログラムのパスと行数」
   at Program.Main(String[] args) in 「ここにプログラムのパスと行数」
```

.NET 9より前でも、「[Assert(Boolean, String)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean-system-string))」を使えば、同じようなことができますが、多少面倒ですね。

```csharp
static void SayHello(string name)
{
    Debug.Assert(!string.IsNullOrWhiteSpace(name), "!string.IsNullOrWhiteSpace(name)");
    Console.WriteLine($"Hello, {name}!");
}
```

デバッグビルドのIL（中間言語）を見て、どういう仕組みになっているか確認してみます。次のコードは、SayHelloメソッドのILです。

```il
  .method private hidebysig static void
    SayHello(
      string name
    ) cil managed
  {
    .maxstack 8

    // [11 5 - 11 6]
    IL_0000: nop

    // [12 9 - 12 92]
    IL_0001: ldarg.0      // name
    IL_0002: call         bool [System.Runtime]System.String::IsNullOrWhiteSpace(string)
    IL_0007: ldc.i4.0
    IL_0008: ceq
    IL_000a: ldstr        "!string.IsNullOrWhiteSpace(name)"
    IL_000f: call         void [System.Runtime]System.Diagnostics.Debug::Assert(bool, string)
    IL_0014: nop

    // [13 9 - 13 46]
    IL_0015: ldstr        "Hello, "
    IL_001a: ldarg.0      // name
    IL_001b: ldstr        "!"
    IL_0020: call         string [System.Runtime]System.String::Concat(string, string, string)
    IL_0025: call         void [System.Console]System.Console::WriteLine(string)
    IL_002a: nop

    // [14 5 - 14 6]
    IL_002b: ret

  } // end of method Program::SayHello
```

「IL_000a: ldstr        "!string.IsNullOrWhiteSpace(name)"」に注目してください。
.NET 9では、こんな感じでアサーションが失敗した場合に表示する文字列が、IL内に定義されています。そして、よくILを見てみると[Debug.Asesrt(Boolean)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean))ではなくて、「[Assert(Boolean, String)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean-system-string))」が呼ばれています。


実は、「[Debug.Asesrt(Boolean)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean))」ではなく「[Assert(Boolean, String)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean-system-string))」が呼ばれているのです。


1. 「[Debug.Asesrt(Boolean)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean))」には、.NET 9から追加された「OverloadResolutionPriority」が付与されている。これにより、オーバーロード解決の優先度が下がっている。コードは[こちら](https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs#L81C10-L81C36)。
2. 「[Assert(Boolean, String)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean-system-string))」の第二引数messageに、CallerArgumentExpressionAttributeを付与して、デフォルト引数としてnullを設定。コードは[こちら](https://github.com/dotnet/runtime/blob/v9.0.0/src/libraries/System.Private.CoreLib/src/System/Diagnostics/Debug.cs#L86-L87)


この2個を組み合わせた時、


```csharp
Debug.Assert(!string.IsNullOrWhiteSpace(name));
```

という呼び出しをした時に、「[Assert(Boolean, String)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#system-diagnostics-debug-assert(system-boolean-system-string))」が呼ばれ、「"!string.IsNullOrWhiteSpace(name)"」という文字列が第二引数に渡されます。

その結果、.NET 9からDebug.Assertがアサーションの条件をデフォルトで表示してくれるようになっています。

[Debug.Asesrt](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0)、.NET 9で進化して、雑に使った時に親切になってナイスですね。

# リンク集

* [Debug.Assert(公式リファレンス)](https://learn.microsoft.com/en-us/dotnet/api/system.diagnostics.debug.assert?view=net-9.0#definition)
* [What's new in .NET libraries for .NET 9(マイクロソフトLearn)](https://learn.microsoft.com/en-us/dotnet/core/whats-new/dotnet-9/libraries#debugassert-reports-assert-condition-by-default)