例外はStackTraceを記録するとき、遡りながら追記していく

# 前書き

この記事は、2023のC#アドカレの12/8の記事です。
今年は、完走賞に挑戦してみたいと思います。Qiita君ぬい欲しい！

https://qiita.com/advent-calendar/2023/present-calendar#qiita_award

# TL;DR;

- 例外のStackTraceは、呼び出し履歴を遡りながら記録されていく
- `new StackTrace()` はその場で全ての呼び出し履歴を記録する

# はじめに

C#の言語には例外機構があり、例外オブジェクト（Exception）にはStackTraceが記録されています。StackTraceは、その処理が、どのような関数の呼び出しを経てたどり着いてきたのかという情報です。例外以外にも、StackTraceクラスをnewすることで、手動で記録することができます。

しかし、StackTraceのnewで手動で記録した場合と、Exceptionに含まれていたStackTraceには差異があります。具体例を見ていきましょう。

# `new StackTrace()`した時に記録されるStackTrace

その場で呼び出し履歴のすべてが記録されています。

```csharp
A();
static void A() => B();
static void B() => C();
static void C() => D();
static void D() => E();
static void E()
{
  var stackTrace = new StackTrace();
  Console.WriteLine($"manually captured StackTraces\n{stackTrace}");
}
```

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AEQEsBDAcygHsBnYKgY2YG488BBABQBKHvkwA2EgBYigoUQC8APiIAhYaNKTUM9fOVEAwhrxbpR4YpUUTY7TJv6VAUVtmdRV0LwBvPEQCiADcaMCJWGnYAawAVMEiAU0UiKASAdyIAZWBI2Pj2BNtAkkwATgEAEgAiAFsaKAgaABsmgE8idhoAB2AIMAS4LJzouMTmAB0oHwiR/ISAXyqRPHmgA==

```Java:output
manually captured StackTraces
   at Program.<<Main>$>g__E|0_4()
   at Program.<<Main>$>g__D|0_3()
   at Program.<<Main>$>g__C|0_2()
   at Program.<<Main>$>g__B|0_1()
   at Program.<<Main>$>g__A|0_0()
   at Program.<Main>$(String[] args)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters)
   at SharpLab.Container.Execution.ExecuteCommandHandler.ExecuteAssembly(Byte[] assemblyBytes)
   at SharpLab.Container.Execution.ExecuteCommandHandler.Execute(ExecuteCommand command)
   at SharpLab.Container.Program.Run(Stream stdin, Stream stdout, Action beforeCommand)
   at SharpLab.Container.Program.SafeMain()
   at SharpLab.Container.Program.Main()
```

# 例外をキャッチしたときのStackTrace

例外機構によって遡ってきた分しか記録されていません。

```csharp
A();
static void A() => B();
static void B()
{
    try { C(); }
    catch(Exception e)
    {
        Console.WriteLine($"exception captured StackTraces on C\n{e.StackTrace}\n");
    }
}
static void C() => D();
static void D() => E();
static void E()
{
    Console.WriteLine($"manually captured StackTraces\n{new StackTrace()}");
    
    try { throw new Exception("hoge"); }
    catch(Exception e)
    {
        Console.WriteLine($"exception captured StackTraces on E\n{e.StackTrace}\n");
        throw;
    }
}
```

https://sharplab.io/#v2:C4LgTgrgdgNAJiA1AHwAICYCMBYAUKgBgAJVMA6AEQEsBDAcygHsBnYKgY2YG488BBABQBKHvkwA2EgBYigoUQC8APiIAhYaNKTUM9ULwBvPERNFgYAJ5EDRAMIaiAX2On2NYOwAWAgKIAPdgBTAAc2RigiQP1cU2sXWJNSAE4BABIAIkCAkLCIt1CIMEC4IgBlYBp2AGsAFTBKwOYicLsAHSgDQLJyytr6oMd29JF4pzxnMW0Ze3llIgoNPC1peeFFFR9FyZXN6KMY03MrG2BPMEYAdyIoQKv/INCqcIF0z0Y6QOGuMYOTNw9vPcck8IlFRvsEqZkmlMtlHi18sBCsUyhVqnUGk0Wj52p1umi+g1BlAvqNYqdzhdRLEJo4gA===

```java
exception captured StackTraces on E
   at Program.<<Main>$>g__E|0_4()

exception captured StackTraces on C
   at Program.<<Main>$>g__E|0_4()
   at Program.<<Main>$>g__D|0_3()
   at Program.<<Main>$>g__C|0_2()
   at Program.<<Main>$>g__B|0_1()
```

# rethrow問題ではない

C#の初心者向けのTipsとして、例外をrethrowするとき、`throw e` と書くな、という話があります。

こうしてしまうと、そこまで記録してきたStackTraceが消えてしまいます。本記事ではそのことを主張したいわけではありません。それはそうとして、その時に記録されているStackTraceに何が含まれているのか、という話題です。

```csharp
try
{
    ThrowableMethod();
}
catch(Exception e)
{
    throw e; // ここまで記録してきたStackTraceが消えるrethrow
    throw; // ここまで記録してきたStackTraceを温存するrethrow
}
```