[C# 9.0 Preview] デリゲートをFunction Pointerに置き換えたら速くなるのか実験した

# 免責事項

- 現時点ではPreviewの機能です。
- Visual Studio Version 16.7.0 Preview 6.0(.NET Core 3.1.400-preview-015203)時点での結果です。
- 効果は当然ですがソフトウェアに依ります。
- 置き換え方が間違っていたらごめんなさい。

# イントロ

C# 9.0から、unsafeコンテキスト限定ですが[Function Pointers](https://github.com/dotnet/csharplang/blob/master/proposals/csharp-9.0/function-pointers.md)が使用できるようになります。
(まだ多少構文が変わるそうですが。ところで日本語ではそのまま「関数ポインター」でいいのだろうか？)

従来からあるデリゲートは高機能ですが、オーバーヘッドが多少あります。[^要出典]
実際のソフトウェアで、Function Pointersに置き換えることで高速化されるか実験してみました。

# 対象ソフトウェア

最近私がPull Requestを送っていた [Cysharp/ZString](https://github.com/Cysharp/ZString)というライブラリでは、値を文字列にする処理を、型毎にデリゲートで保持しています。
そのため、(JITで消え去りそうですけど)少なからずデリゲート呼び出しのコストがかかっているはずです。
Function Pointersにするにはうってつけ？のハズです。

**先に結果だけ書くと、数パーセント速くなってそうでした。**

# 計測

Nugetの最新版(2.2.0)と、それをベースにFunction Pointersに置き換えたコードをBenchmarkDotNetで比較します。
[コードとベンチマークコードはGithubに一応置いておきます](https://github.com/udaken/ZString/tree/Replace-to-function-pointer)が、書き捨てるつもりなのでしばらくしたら消すかもしれません。
この記事の最後にpatchとして置いておきます。

ベンチマーク結果のMethodで、末尾に`_`が付いているのがリリース版(デリゲート版)、`N`が付いているのが置き換えたコード(Function Pointer版)です。

なお、ZString にはUtf16用のクラスとUtf8用のクラスがありますが、面倒なのでUtf16用しか置き換えと計測をしていません。

[ベンチマークコードはFormatBenchmark.cs](https://github.com/udaken/ZString/blob/Replace-to-function-pointer/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs)を`dotnet run -c Release`で実行しています。

## 結果(1回目)

Ratio列に注目してください。
多少速くなっていそうな雰囲気があります。
どうしても計測時環境によりブレがありますので、もう一回測ってみます。

|                          Method |         FormatString |      Mean |    Error |   StdDev | Ratio | RatioSD | Code Size |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------------------------- |--------------------- |----------:|---------:|---------:|------:|--------:|----------:|-------:|------:|------:|----------:|
|                         Format_ | This (...)orld. [62] | 225.71 ns | 1.708 ns | 1.598 ns |  1.00 |    0.00 |    3147 B | 0.0181 |     - |     - |     152 B |
|                         FormatN | This (...)orld. [62] | 192.91 ns | 2.135 ns | 1.997 ns |  0.85 |    0.01 |    3458 B | 0.0181 |     - |     - |     152 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|            Utf16PreparedFormat_ | This (...)orld. [62] |  85.97 ns | 1.762 ns | 2.097 ns |  1.00 |    0.00 |    2597 B | 0.0181 |     - |     - |     152 B |
|            Utf16PreparedFormatN | This (...)orld. [62] |  84.32 ns | 1.686 ns | 1.731 ns |  0.98 |    0.02 |    2567 B | 0.0181 |     - |     - |     152 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
| Utf16StringBuilderAppendFormat_ | This (...)orld. [62] | 201.26 ns | 0.881 ns | 0.824 ns |  1.00 |    0.00 |    7425 B |      - |     - |     - |         - |
| Utf16StringBuilderAppendFormatN | This (...)orld. [62] | 177.24 ns | 0.850 ns | 0.795 ns |  0.88 |    0.01 |    7924 B |      - |     - |     - |         - |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|                         Format_ |         x:{0}, y:{1} | 137.85 ns | 0.466 ns | 0.436 ns |  1.00 |    0.00 |    3147 B | 0.0057 |     - |     - |      48 B |
|                         FormatN |         x:{0}, y:{1} | 135.39 ns | 0.747 ns | 0.699 ns |  0.98 |    0.01 |    3458 B | 0.0057 |     - |     - |      48 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|            Utf16PreparedFormat_ |         x:{0}, y:{1} |  74.40 ns | 0.694 ns | 0.649 ns |  1.00 |    0.00 |    2597 B | 0.0057 |     - |     - |      48 B |
|            Utf16PreparedFormatN |         x:{0}, y:{1} |  74.23 ns | 0.627 ns | 0.586 ns |  1.00 |    0.01 |    2567 B | 0.0057 |     - |     - |      48 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
| Utf16StringBuilderAppendFormat_ |         x:{0}, y:{1} | 141.08 ns | 0.671 ns | 0.628 ns |  1.00 |    0.00 |    7423 B |      - |     - |     - |         - |
| Utf16StringBuilderAppendFormatN |         x:{0}, y:{1} | 131.62 ns | 0.580 ns | 0.543 ns |  0.93 |    0.01 |    7922 B |      - |     - |     - |         - |

```ini
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
AMD Ryzen 7 3700X, 1 CPU, 16 logical and 8 physical cores
.NET Core SDK=3.1.400-preview-015203
  [Host]     : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
  DefaultJob : .NET Core 3.1.6 (CoreCLR 4.700.20.26901, CoreFX 4.700.20.31603), X64 RyuJIT
```

## 結果(2回目)

遅くなっているケースもありますが、傾向として速くなってそうです。

|                          Method |         FormatString |      Mean |    Error |   StdDev | Ratio | RatioSD | Code Size |  Gen 0 | Gen 1 | Gen 2 | Allocated |
|-------------------------------- |--------------------- |----------:|---------:|---------:|------:|--------:|----------:|-------:|------:|------:|----------:|
|                         Format_ | This (...)orld. [62] | 225.79 ns | 1.438 ns | 1.345 ns |  1.00 |    0.00 |    3147 B | 0.0181 |     - |     - |     152 B |
|                         FormatN | This (...)orld. [62] | 192.97 ns | 1.582 ns | 1.321 ns |  0.85 |    0.01 |    3458 B | 0.0181 |     - |     - |     152 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|            Utf16PreparedFormat_ | This (...)orld. [62] |  85.96 ns | 1.667 ns | 1.784 ns |  1.00 |    0.00 |    2597 B | 0.0181 |     - |     - |     152 B |
|            Utf16PreparedFormatN | This (...)orld. [62] |  90.81 ns | 1.874 ns | 1.924 ns |  1.06 |    0.02 |    2567 B | 0.0181 |     - |     - |     152 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
| Utf16StringBuilderAppendFormat_ | This (...)orld. [62] | 191.97 ns | 0.974 ns | 0.911 ns |  1.00 |    0.00 |    7276 B |      - |     - |     - |         - |
| Utf16StringBuilderAppendFormatN | This (...)orld. [62] | 176.76 ns | 0.877 ns | 0.821 ns |  0.92 |    0.01 |    7924 B |      - |     - |     - |         - |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|                         Format_ |         x:{0}, y:{1} | 137.57 ns | 0.816 ns | 0.763 ns |  1.00 |    0.00 |    3147 B | 0.0057 |     - |     - |      48 B |
|                         FormatN |         x:{0}, y:{1} | 137.22 ns | 0.797 ns | 0.746 ns |  1.00 |    0.01 |    3458 B | 0.0057 |     - |     - |      48 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
|            Utf16PreparedFormat_ |         x:{0}, y:{1} |  73.85 ns | 0.731 ns | 0.683 ns |  1.00 |    0.00 |    2597 B | 0.0057 |     - |     - |      48 B |
|            Utf16PreparedFormatN |         x:{0}, y:{1} |  76.60 ns | 0.685 ns | 0.641 ns |  1.04 |    0.01 |    2567 B | 0.0057 |     - |     - |      48 B |
|                                 |                      |           |          |          |       |         |           |        |       |       |           |
| Utf16StringBuilderAppendFormat_ |         x:{0}, y:{1} | 140.42 ns | 0.659 ns | 0.584 ns |  1.00 |    0.00 |    7425 B |      - |     - |     - |         - |
| Utf16StringBuilderAppendFormatN |         x:{0}, y:{1} | 125.47 ns | 0.545 ns | 0.510 ns |  0.89 |    0.00 |    7922 B |      - |     - |     - |         - |


# 結論

- 対象は数パーセントは効果あり。
- しかし、関数ポインターできるのは静的関数のみで、設計にかなり制約が出てくる。
  (例えば、外からデリゲートを渡しづらくなる)

これよりもっとデリゲートを使っているとか、複雑さを犠牲にしてでも1%でも速くしたいとかで無ければ、置き換えるのは最後の手段にしておきましょう。

# コードとベンチマークコード

追記：実はコードは少し前(Preview 4.0か5.0)に作っていて、その時はFunction Pointerと`void*`との相互変換ができませんでした。(ドキュメントでは許可されている)
そのため、ちょっと回りくどいコードになっています。
しかし、Preview 6.0では`void*`との相互変換できるようになっていたため、今ならもう少しオリジナルのコードに近いものにできそうです。

<details>
<div>

```patch:0001-function-pointers.patch
From bab609ea44ed09d24537c279de043d8072615f5d Mon Sep 17 00:00:00 2001
From: udaken <u*********@gmail.com>
Date: Wed, 5 Aug 2020 23:12:23 +0900
Subject: [PATCH] function pointers

---
 global.json                                   |   6 +
 .../BenchmarkVsReleasedVersion.csproj         |   2 +-
 .../BuiltinTypesBenchmark.cs                  |  12 +-
 .../FormatBenchmark.cs                        |  12 +-
 sandbox/BenchmarkVsReleasedVersion/Program.cs |   3 +-
 .../Assets/Scripts/ZString/PreparedFormat.cs  |  64 +++---
 .../Utf16ValueStringBuilder.AppendFormat.cs   |   2 +-
 ...Utf16ValueStringBuilder.CreateFormatter.cs | 217 ++++++++++--------
 .../ZString/Utf16ValueStringBuilder.cs        |  77 ++++---
 src/ZString/PreparedFormat.cs                 |  64 +++---
 src/ZString/PreparedFormat.tt                 |   2 +-
 .../Utf16ValueStringBuilder.AppendFormat.cs   |   2 +-
 .../Utf16ValueStringBuilder.AppendFormat.tt   |   2 +-
 ...Utf16ValueStringBuilder.CreateFormatter.cs | 217 ++++++++++--------
 ...Utf16ValueStringBuilder.CreateFormatter.tt |  42 ++--
 src/ZString/Utf16ValueStringBuilder.cs        |  77 ++++---
 src/ZString/ZString.csproj                    |   2 +
 17 files changed, 437 insertions(+), 366 deletions(-)
 create mode 100644 global.json

diff --git a/global.json b/global.json
new file mode 100644
index 0000000..5679eb1
--- /dev/null
+++ b/global.json
@@ -0,0 +1,6 @@
+{
+  "sdk": {
+	"allowPrerelease" : true,
+	"version" : "3.1.400-preview-015203"
+  }
+}
diff --git a/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj b/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
index 7e34c82..4e03240 100644
--- a/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
+++ b/sandbox/BenchmarkVsReleasedVersion/BenchmarkVsReleasedVersion.csproj
@@ -9,7 +9,7 @@
 
     <ItemGroup>
       <PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.7.1" Condition="$(TargetFramework) != 'netstandard3.1'" />
-      <PackageReference Include="ZString" Version="2.1.3" />
+      <PackageReference Include="ZString" Version="2.2.0" />
       <PackageReference Include="BenchmarkDotNet" Version="0.12.1" />
     </ItemGroup>
 
diff --git a/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs b/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
index 20b8ab4..5de265c 100644
--- a/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/BuiltinTypesBenchmark.cs
@@ -96,13 +96,13 @@ namespace BenchmarkVsReleasedVersion
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }
 
-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
         public object CreatePreparedFormat_()
         {
             return new PF16(_format);
         }
 
-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
         public object CreatePreparedFormatN()
         {
             return new NPF16(_format);
@@ -122,14 +122,14 @@ namespace BenchmarkVsReleasedVersion
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }
 
-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
         public string Utf8PreparedFormat_()
         {
             return _utf8preparedFormat_.Format(
                 _byte, _dt, _dto, _decimal, _double, _guid, _short, _float, _ts, _uint, _ulong, _null, _string, _bool, _enum, _char);
         }
 
-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
         public string Utf8PreparedFormatN()
         {
             return _utf8preparedFormatN.Format(
@@ -154,7 +154,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }
 
-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
         public int Utf8StringBuilderAppendFormat_()
         {
             using var zsh = ZString.CreateUtf8StringBuilder();
@@ -163,7 +163,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }
 
-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
         public int Utf8StringBuilderAppendFormatN()
         {
             using var zsh = NZString.CreateUtf8StringBuilder();
diff --git a/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs b/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
index 14a49c2..a6326cb 100644
--- a/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/FormatBenchmark.cs
@@ -61,13 +61,13 @@ namespace BenchmarkVsReleasedVersion
             return NZString.Format(FormatString, x, y);
         }
 
-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark(Baseline = true)]
         public object CreatePreparedFormat_()
         {
             return new Utf16PreparedFormat<int, int>(FormatString);
         }
 
-        [BenchmarkCategory("CreatePreparedFormat"), Benchmark]
+        //[BenchmarkCategory("CreatePreparedFormat"), Benchmark]
         public object CreatePreparedFormatN()
         {
             return new NewZString::Cysharp.Text.Utf16PreparedFormat<int, int>(FormatString);
@@ -85,13 +85,13 @@ namespace BenchmarkVsReleasedVersion
             return _utf16preparedFormatN.Format(x, y);
         }
 
-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark(Baseline = true)]
         public string Utf8PreparedFormat_()
         {
             return _utf8preparedFormat_.Format(x, y);
         }
 
-        [BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8PreparedFormat"), Benchmark]
         public string Utf8PreparedFormatN()
         {
             return _utf8preparedFormatN.Format(x, y);
@@ -113,7 +113,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }
 
-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark(Baseline = true)]
         public int Utf8StringBuilderAppendFormat_()
         {
             using var zsh = ZString.CreateUtf8StringBuilder();
@@ -121,7 +121,7 @@ namespace BenchmarkVsReleasedVersion
             return zsh.Length;
         }
 
-        [BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
+        //[BenchmarkCategory("Utf8StringBuilderAppendFormat"), Benchmark]
         public int Utf8StringBuilderAppendFormatN()
         {
             using var zsh = NZString.CreateUtf8StringBuilder();
diff --git a/sandbox/BenchmarkVsReleasedVersion/Program.cs b/sandbox/BenchmarkVsReleasedVersion/Program.cs
index 45ee687..8574158 100644
--- a/sandbox/BenchmarkVsReleasedVersion/Program.cs
+++ b/sandbox/BenchmarkVsReleasedVersion/Program.cs
@@ -19,7 +19,8 @@ namespace BenchmarkVsReleasedVersion
         public BenchmarkConfig()
         {
             AddDiagnoser(MemoryDiagnoser.Default);
-            AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1));
+            //AddJob(Job.ShortRun.WithWarmupCount(1).WithIterationCount(1));
+            AddJob(Job.Default);
         }
     }
 
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs b/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
index c343a57..d7232bd 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/PreparedFormat.cs
@@ -4,7 +4,7 @@ using System.Buffers;
 
 namespace Cysharp.Text
 {
-    public sealed partial class Utf16PreparedFormat<T1>
+    public sealed unsafe partial class Utf16PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -80,7 +80,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -171,7 +171,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -277,7 +277,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -398,7 +398,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -534,7 +534,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -685,7 +685,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -851,7 +851,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1032,7 +1032,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1228,7 +1228,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1439,7 +1439,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1665,7 +1665,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1906,7 +1906,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2162,7 +2162,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2433,7 +2433,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2719,7 +2719,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3020,7 +3020,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1>
+    public sealed unsafe partial class Utf8PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3097,7 +3097,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3189,7 +3189,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3296,7 +3296,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3418,7 +3418,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3555,7 +3555,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3707,7 +3707,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3874,7 +3874,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4056,7 +4056,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4253,7 +4253,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4465,7 +4465,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4692,7 +4692,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4934,7 +4934,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5191,7 +5191,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5463,7 +5463,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5750,7 +5750,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
index 515186d..9afcc41 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
@@ -2,7 +2,7 @@
 
 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
         public void AppendFormat<T1>(string format, T1 arg1)
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
index 8d939c7..77b85ea 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
@@ -1,146 +1,167 @@
 ・ｿusing System;
+using System.Runtime.CompilerServices;
 
 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
-            if (type == typeof(System.SByte))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SByteFunc(System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<System.SByte>((System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16))
-            {
-                return new TryFormat<System.Int16>((System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int32))
-            {
-                return new TryFormat<System.Int32>((System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int64))
-            {
-                return new TryFormat<System.Int64>((System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte))
-            {
-                return new TryFormat<System.Byte>((System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt16))
-            {
-                return new TryFormat<System.UInt16>((System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt32))
-            {
-                return new TryFormat<System.UInt32>((System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt64))
-            {
-                return new TryFormat<System.UInt64>((System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Single))
-            {
-                return new TryFormat<System.Single>((System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Double))
-            {
-                return new TryFormat<System.Double>((System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.TimeSpan))
-            {
-                return new TryFormat<System.TimeSpan>((System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTime))
-            {
-                return new TryFormat<System.DateTime>((System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTimeOffset))
-            {
-                return new TryFormat<System.DateTimeOffset>((System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Decimal))
-            {
-                return new TryFormat<System.Decimal>((System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Guid))
-            {
-                return new TryFormat<System.Guid>((System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte?))
-            {
-                return CreateNullableFormatter<System.Byte>();
-            }
-            if (type == typeof(System.DateTime?))
+            FormatterCache<System.SByte>.TryFormatDelegate = &SByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int16Func(System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTime>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.DateTimeOffset?))
+            FormatterCache<System.Int16>.TryFormatDelegate = &Int16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int32Func(System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTimeOffset>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Decimal?))
+            FormatterCache<System.Int32>.TryFormatDelegate = &Int32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int64Func(System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Decimal>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Double?))
+            FormatterCache<System.Int64>.TryFormatDelegate = &Int64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool ByteFunc(System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Double>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16?))
+            FormatterCache<System.Byte>.TryFormatDelegate = &ByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt16Func(System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int16>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int32?))
+            FormatterCache<System.UInt16>.TryFormatDelegate = &UInt16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt32Func(System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int32>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int64?))
+            FormatterCache<System.UInt32>.TryFormatDelegate = &UInt32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt64Func(System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int64>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.SByte?))
+            FormatterCache<System.UInt64>.TryFormatDelegate = &UInt64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SingleFunc(System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.SByte>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Single?))
+            FormatterCache<System.Single>.TryFormatDelegate = &SingleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DoubleFunc(System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Single>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.TimeSpan?))
+            FormatterCache<System.Double>.TryFormatDelegate = &DoubleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool TimeSpanFunc(System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.TimeSpan>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt16?))
+            FormatterCache<System.TimeSpan>.TryFormatDelegate = &TimeSpanFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeFunc(System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt16>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt32?))
+            FormatterCache<System.DateTime>.TryFormatDelegate = &DateTimeFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeOffsetFunc(System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt32>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt64?))
+            FormatterCache<System.DateTimeOffset>.TryFormatDelegate = &DateTimeOffsetFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DecimalFunc(System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt64>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Guid?))
+            FormatterCache<System.Decimal>.TryFormatDelegate = &DecimalFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool GuidFunc(System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Guid>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.IntPtr))
+            FormatterCache<System.Guid>.TryFormatDelegate = &GuidFunc;
+
+            FormatterCache<System.Byte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTime?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTimeOffset?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Decimal?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Double?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.SByte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Single?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.TimeSpan?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Guid?>.TryFormatDelegate = &NullableFormat;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool IntPtrFunc(System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.IntPtr>((System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.IntPtr.Size == 4
+                return  System.IntPtr.Size == 4
                     ? x.ToInt32().TryFormat(dest, out written, default)
-                    : x.ToInt64().TryFormat(dest, out written, default));
+                    : x.ToInt64().TryFormat(dest, out written, default);
             }
-            if (type == typeof(System.UIntPtr))
+            FormatterCache<System.IntPtr>.TryFormatDelegate = &IntPtrFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UIntPtrFunc(System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.UIntPtr>((System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.UIntPtr.Size == 4
+                return  System.UIntPtr.Size == 4
                     ? x.ToUInt32().TryFormat(dest, out written, default)
-                    : x.ToUInt64().TryFormat(dest, out written, default));
+                    : x.ToUInt64().TryFormat(dest, out written, default);
             }
-            return null;
+            FormatterCache<System.UIntPtr>.TryFormatDelegate = &UIntPtrFunc;
+
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
index 7a4368a..1b9ad83 100644
--- a/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
+++ b/src/ZString.Unity/Assets/Scripts/ZString/Utf16ValueStringBuilder.cs
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
 
 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
+    public unsafe partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
     {
         public delegate bool TryFormat<T>(T value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format);
 
@@ -32,6 +32,7 @@ namespace Cysharp.Text
                 newLine2 = newLine[1];
                 crlf = true;
             }
+            RegisterPrimitives();
         }
 
         [ThreadStatic]
@@ -232,7 +233,7 @@ namespace Cysharp.Text
         }
 
         /// <summary>Appends the string representation of a specified value to this instance.</summary>
-        public void Append<T>(T value)
+        public unsafe void Append<T>(T value)
         {
             if (!FormatterCache<T>.TryFormatDelegate(value, buffer.AsSpan(index), out var written, default))
             {
@@ -361,7 +362,7 @@ namespace Cysharp.Text
         /// are removed from this builder.
         /// </remarks>
         public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length);
-        
+
         public void Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue) => Replace(oldValue, newValue, 0, Length);
 
         /// <summary>
@@ -578,58 +579,62 @@ namespace Cysharp.Text
             index += written;
         }
 
+        static class CustomTryFormat<T>
+        {
+            public static TryFormat<T> formatMethod;
+            public static bool Dispatch(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
+            {
+                return formatMethod(value, dest, out written, format);
+            }
+        }
+
         /// <summary>
         /// Register custom formatter
         /// </summary>
         public static void RegisterTryFormat<T>(TryFormat<T> formatMethod)
         {
-            FormatterCache<T>.TryFormatDelegate = formatMethod;
+            CustomTryFormat<T>.formatMethod = formatMethod;
+            FormatterCache<T>.TryFormatDelegate = &CustomTryFormat<T>.Dispatch;
         }
 
-        static TryFormat<T?> CreateNullableFormatter<T>() where T : struct
+        static unsafe bool NullableFormat<T>(T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) where T : struct
         {
-            return new TryFormat<T?>((T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) =>
+            if (x == null)
             {
-                if (x == null)
-                {
-                    written = 0;
-                    return true;
-                }
-                return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
-            });
+                written = 0;
+                return true;
+            }
+            return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
         }
 
-        /// <summary>
-        /// Supports the Nullable type for a given struct type.
-        /// </summary>
-        public static void EnableNullableFormat<T>() where T : struct
+        public unsafe static class FormatterCache<T>
         {
-            RegisterTryFormat<T?>(CreateNullableFormatter<T>());
-        }
+            private static unsafe delegate*<T, Span<char>, out int, ReadOnlySpan<char>, bool> _TryFormatDelegate;
 
-        public static class FormatterCache<T>
-        {
-            public static TryFormat<T> TryFormatDelegate;
-            static FormatterCache()
+            public static delegate*<T , Span<char> , out int , ReadOnlySpan<char> , bool> TryFormatDelegate
             {
-                var formatter = (TryFormat<T>)CreateFormatter(typeof(T));
-                if (formatter == null)
+                get
                 {
-                    if (typeof(T).IsEnum)
+                    if (_TryFormatDelegate == null)
                     {
-                        formatter = new TryFormat<T>(EnumUtil<T>.TryFormatUtf16);
-                    }
-                    else if (typeof(T) == typeof(string))
-                    {
-                        formatter = new TryFormat<T>(TryFormatString);
-                    }
-                    else
-                    {
-                        formatter = new TryFormat<T>(TryFormatDefault);
+                        if (typeof(T).IsEnum)
+                        {
+                            _TryFormatDelegate = &EnumUtil<T>.TryFormatUtf16;
+                        }
+                        else if (typeof(T) == typeof(string))
+                        {
+                            _TryFormatDelegate = &TryFormatString;
+                        }
+                        else
+                        {
+                            _TryFormatDelegate = &TryFormatDefault;
+                        }
                     }
+
+                    return _TryFormatDelegate;
                 }
 
-                TryFormatDelegate = formatter;
+                set => _TryFormatDelegate = value;
             }
 
             static bool TryFormatString(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
diff --git a/src/ZString/PreparedFormat.cs b/src/ZString/PreparedFormat.cs
index c343a57..d7232bd 100644
--- a/src/ZString/PreparedFormat.cs
+++ b/src/ZString/PreparedFormat.cs
@@ -4,7 +4,7 @@ using System.Buffers;
 
 namespace Cysharp.Text
 {
-    public sealed partial class Utf16PreparedFormat<T1>
+    public sealed unsafe partial class Utf16PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -80,7 +80,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -171,7 +171,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -277,7 +277,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -398,7 +398,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -534,7 +534,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -685,7 +685,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -851,7 +851,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1032,7 +1032,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1228,7 +1228,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1439,7 +1439,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1665,7 +1665,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -1906,7 +1906,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2162,7 +2162,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2433,7 +2433,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -2719,7 +2719,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf16PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3020,7 +3020,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1>
+    public sealed unsafe partial class Utf8PreparedFormat<T1>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3097,7 +3097,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3189,7 +3189,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3296,7 +3296,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3418,7 +3418,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3555,7 +3555,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3707,7 +3707,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -3874,7 +3874,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4056,7 +4056,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4253,7 +4253,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4465,7 +4465,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4692,7 +4692,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -4934,7 +4934,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5191,7 +5191,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5463,7 +5463,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15>
     {
         public string FormatString { get; }
         public int MinSize { get; }
@@ -5750,7 +5750,7 @@ namespace Cysharp.Text
         }
     }
 
-    public sealed partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
+    public sealed unsafe partial class Utf8PreparedFormat<T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString/PreparedFormat.tt b/src/ZString/PreparedFormat.tt
index 4087bea..e3af3be 100644
--- a/src/ZString/PreparedFormat.tt
+++ b/src/ZString/PreparedFormat.tt
@@ -13,7 +13,7 @@ namespace Cysharp.Text
 {
 <# foreach(var utf in utfTypes) { var isUtf16 = (utf == "Utf16"); #>
 <# for(var i = 1; i <= TypeParamMax; i++) { #>
-    public sealed partial class <#= utf #>PreparedFormat<<#= CreateTypeArgument(i) #>>
+    public sealed unsafe partial class <#= utf #>PreparedFormat<<#= CreateTypeArgument(i) #>>
     {
         public string FormatString { get; }
         public int MinSize { get; }
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
index 515186d..9afcc41 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.cs
@@ -2,7 +2,7 @@
 
 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
         public void AppendFormat<T1>(string format, T1 arg1)
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
index 12b53b5..474d8b0 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.AppendFormat.tt
@@ -9,7 +9,7 @@ using System;
 
 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder
+    public unsafe partial struct Utf16ValueStringBuilder
     {
 <# for(var i = 1; i <= TypeParamMax; i++) { #>
         /// <summary>Appends the string returned by processing a composite format string, each format item is replaced by the string representation of arguments.</summary>
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
index 8d939c7..77b85ea 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.cs
@@ -1,146 +1,167 @@
 ・ｿusing System;
+using System.Runtime.CompilerServices;
 
 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
-            if (type == typeof(System.SByte))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SByteFunc(System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<System.SByte>((System.SByte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16))
-            {
-                return new TryFormat<System.Int16>((System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int32))
-            {
-                return new TryFormat<System.Int32>((System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Int64))
-            {
-                return new TryFormat<System.Int64>((System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte))
-            {
-                return new TryFormat<System.Byte>((System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt16))
-            {
-                return new TryFormat<System.UInt16>((System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt32))
-            {
-                return new TryFormat<System.UInt32>((System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.UInt64))
-            {
-                return new TryFormat<System.UInt64>((System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Single))
-            {
-                return new TryFormat<System.Single>((System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Double))
-            {
-                return new TryFormat<System.Double>((System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.TimeSpan))
-            {
-                return new TryFormat<System.TimeSpan>((System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTime))
-            {
-                return new TryFormat<System.DateTime>((System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.DateTimeOffset))
-            {
-                return new TryFormat<System.DateTimeOffset>((System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Decimal))
-            {
-                return new TryFormat<System.Decimal>((System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Guid))
-            {
-                return new TryFormat<System.Guid>((System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
-            }
-            if (type == typeof(System.Byte?))
-            {
-                return CreateNullableFormatter<System.Byte>();
-            }
-            if (type == typeof(System.DateTime?))
+            FormatterCache<System.SByte>.TryFormatDelegate = &SByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int16Func(System.Int16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTime>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.DateTimeOffset?))
+            FormatterCache<System.Int16>.TryFormatDelegate = &Int16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int32Func(System.Int32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.DateTimeOffset>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Decimal?))
+            FormatterCache<System.Int32>.TryFormatDelegate = &Int32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool Int64Func(System.Int64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Decimal>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Double?))
+            FormatterCache<System.Int64>.TryFormatDelegate = &Int64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool ByteFunc(System.Byte x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Double>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int16?))
+            FormatterCache<System.Byte>.TryFormatDelegate = &ByteFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt16Func(System.UInt16 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int16>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int32?))
+            FormatterCache<System.UInt16>.TryFormatDelegate = &UInt16Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt32Func(System.UInt32 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int32>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Int64?))
+            FormatterCache<System.UInt32>.TryFormatDelegate = &UInt32Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UInt64Func(System.UInt64 x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Int64>();
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.SByte?))
+            FormatterCache<System.UInt64>.TryFormatDelegate = &UInt64Func;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool SingleFunc(System.Single x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.SByte>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Single?))
+            FormatterCache<System.Single>.TryFormatDelegate = &SingleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DoubleFunc(System.Double x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Single>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.TimeSpan?))
+            FormatterCache<System.Double>.TryFormatDelegate = &DoubleFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool TimeSpanFunc(System.TimeSpan x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.TimeSpan>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt16?))
+            FormatterCache<System.TimeSpan>.TryFormatDelegate = &TimeSpanFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeFunc(System.DateTime x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt16>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt32?))
+            FormatterCache<System.DateTime>.TryFormatDelegate = &DateTimeFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DateTimeOffsetFunc(System.DateTimeOffset x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt32>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.UInt64?))
+            FormatterCache<System.DateTimeOffset>.TryFormatDelegate = &DateTimeOffsetFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool DecimalFunc(System.Decimal x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.UInt64>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.Guid?))
+            FormatterCache<System.Decimal>.TryFormatDelegate = &DecimalFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool GuidFunc(System.Guid x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return CreateNullableFormatter<System.Guid>();
+                return x.TryFormat(dest, out written, format);
             }
-            if (type == typeof(System.IntPtr))
+            FormatterCache<System.Guid>.TryFormatDelegate = &GuidFunc;
+
+            FormatterCache<System.Byte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTime?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.DateTimeOffset?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Decimal?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Double?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Int64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.SByte?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Single?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.TimeSpan?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt16?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt32?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.UInt64?>.TryFormatDelegate = &NullableFormat;
+
+            FormatterCache<System.Guid?>.TryFormatDelegate = &NullableFormat;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool IntPtrFunc(System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.IntPtr>((System.IntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.IntPtr.Size == 4
+                return  System.IntPtr.Size == 4
                     ? x.ToInt32().TryFormat(dest, out written, default)
-                    : x.ToInt64().TryFormat(dest, out written, default));
+                    : x.ToInt64().TryFormat(dest, out written, default);
             }
-            if (type == typeof(System.UIntPtr))
+            FormatterCache<System.IntPtr>.TryFormatDelegate = &IntPtrFunc;
+
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool UIntPtrFunc(System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<System.UIntPtr>((System.UIntPtr x, Span<char> dest, out int written, ReadOnlySpan<char> _) => System.UIntPtr.Size == 4
+                return  System.UIntPtr.Size == 4
                     ? x.ToUInt32().TryFormat(dest, out written, default)
-                    : x.ToUInt64().TryFormat(dest, out written, default));
+                    : x.ToUInt64().TryFormat(dest, out written, default);
             }
-            return null;
+            FormatterCache<System.UIntPtr>.TryFormatDelegate = &UIntPtrFunc;
+
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
index 0ccd705..3684c0f 100644
--- a/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
+++ b/src/ZString/Utf16/Utf16ValueStringBuilder.CreateFormatter.tt
@@ -32,47 +32,57 @@
     };
 #>
 using System;
+using System.Runtime.CompilerServices;
 
 namespace Cysharp.Text
 {
     public partial struct Utf16ValueStringBuilder
     {
-        static object CreateFormatter(Type type)
+        static unsafe void RegisterPrimitives()
         {
 <# foreach(var t in spanFormattablesA) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattablesB) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format));
+                return format.Length == 0 ? FastNumberWriter.TryWriteUInt64(dest, out written, x) : x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattablesC) { #>
-            if (type == typeof(<#= t.FullName #>))
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format) => x.TryFormat(dest, out written, format));
+                return x.TryFormat(dest, out written, format);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
 <# foreach(var t in spanFormattables) { #>
-            if (type == typeof(<#= t.FullName #>?))
-            {
-                return CreateNullableFormatter<<#= t.FullName #>>();
-            }
+            FormatterCache<<#= t.FullName #>?>.TryFormatDelegate = &NullableFormat;
+
 <# } #>
-<# foreach(var t in new [] {typeof(IntPtr), typeof(UIntPtr)}) { var u = t == typeof(UIntPtr);  #>
-            if (type == typeof(<#= t.FullName #>))
+<# foreach(var t in new [] {typeof(IntPtr), typeof(UIntPtr)} ) { var u = t == typeof(UIntPtr);  #>
+            [MethodImpl(MethodImplOptions.AggressiveInlining)]
+            static bool <#= t.Name #>Func(<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> format)
             {
                 // ignore format
-                return new TryFormat<<#= t.FullName #>>((<#= t.FullName #> x, Span<char> dest, out int written, ReadOnlySpan<char> _) => <#= t #>.Size == 4
+                return  <#= t #>.Size == 4
                     ? x.To<#= u ? "U" : "" #>Int32().TryFormat(dest, out written, default)
-                    : x.To<#= u ? "U" : "" #>Int64().TryFormat(dest, out written, default));
+                    : x.To<#= u ? "U" : "" #>Int64().TryFormat(dest, out written, default);
             }
+            FormatterCache<<#= t.FullName #>>.TryFormatDelegate = &<#= t.Name #>Func;
+
 <# } #>
-            return null;
         }
     }
 }
\ No newline at end of file
diff --git a/src/ZString/Utf16ValueStringBuilder.cs b/src/ZString/Utf16ValueStringBuilder.cs
index 7a4368a..1b9ad83 100644
--- a/src/ZString/Utf16ValueStringBuilder.cs
+++ b/src/ZString/Utf16ValueStringBuilder.cs
@@ -5,7 +5,7 @@ using System.Runtime.CompilerServices;
 
 namespace Cysharp.Text
 {
-    public partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
+    public unsafe partial struct Utf16ValueStringBuilder : IDisposable, IBufferWriter<char>, IResettableBufferWriter<char>
     {
         public delegate bool TryFormat<T>(T value, Span<char> destination, out int charsWritten, ReadOnlySpan<char> format);
 
@@ -32,6 +32,7 @@ namespace Cysharp.Text
                 newLine2 = newLine[1];
                 crlf = true;
             }
+            RegisterPrimitives();
         }
 
         [ThreadStatic]
@@ -232,7 +233,7 @@ namespace Cysharp.Text
         }
 
         /// <summary>Appends the string representation of a specified value to this instance.</summary>
-        public void Append<T>(T value)
+        public unsafe void Append<T>(T value)
         {
             if (!FormatterCache<T>.TryFormatDelegate(value, buffer.AsSpan(index), out var written, default))
             {
@@ -361,7 +362,7 @@ namespace Cysharp.Text
         /// are removed from this builder.
         /// </remarks>
         public void Replace(string oldValue, string newValue) => Replace(oldValue, newValue, 0, Length);
-        
+
         public void Replace(ReadOnlySpan<char> oldValue, ReadOnlySpan<char> newValue) => Replace(oldValue, newValue, 0, Length);
 
         /// <summary>
@@ -578,58 +579,62 @@ namespace Cysharp.Text
             index += written;
         }
 
+        static class CustomTryFormat<T>
+        {
+            public static TryFormat<T> formatMethod;
+            public static bool Dispatch(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
+            {
+                return formatMethod(value, dest, out written, format);
+            }
+        }
+
         /// <summary>
         /// Register custom formatter
         /// </summary>
         public static void RegisterTryFormat<T>(TryFormat<T> formatMethod)
         {
-            FormatterCache<T>.TryFormatDelegate = formatMethod;
+            CustomTryFormat<T>.formatMethod = formatMethod;
+            FormatterCache<T>.TryFormatDelegate = &CustomTryFormat<T>.Dispatch;
         }
 
-        static TryFormat<T?> CreateNullableFormatter<T>() where T : struct
+        static unsafe bool NullableFormat<T>(T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) where T : struct
         {
-            return new TryFormat<T?>((T? x, Span<char> dest, out int written, ReadOnlySpan<char> format) =>
+            if (x == null)
             {
-                if (x == null)
-                {
-                    written = 0;
-                    return true;
-                }
-                return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
-            });
+                written = 0;
+                return true;
+            }
+            return FormatterCache<T>.TryFormatDelegate(x.Value, dest, out written, format);
         }
 
-        /// <summary>
-        /// Supports the Nullable type for a given struct type.
-        /// </summary>
-        public static void EnableNullableFormat<T>() where T : struct
+        public unsafe static class FormatterCache<T>
         {
-            RegisterTryFormat<T?>(CreateNullableFormatter<T>());
-        }
+            private static unsafe delegate*<T, Span<char>, out int, ReadOnlySpan<char>, bool> _TryFormatDelegate;
 
-        public static class FormatterCache<T>
-        {
-            public static TryFormat<T> TryFormatDelegate;
-            static FormatterCache()
+            public static delegate*<T , Span<char> , out int , ReadOnlySpan<char> , bool> TryFormatDelegate
             {
-                var formatter = (TryFormat<T>)CreateFormatter(typeof(T));
-                if (formatter == null)
+                get
                 {
-                    if (typeof(T).IsEnum)
+                    if (_TryFormatDelegate == null)
                     {
-                        formatter = new TryFormat<T>(EnumUtil<T>.TryFormatUtf16);
-                    }
-                    else if (typeof(T) == typeof(string))
-                    {
-                        formatter = new TryFormat<T>(TryFormatString);
-                    }
-                    else
-                    {
-                        formatter = new TryFormat<T>(TryFormatDefault);
+                        if (typeof(T).IsEnum)
+                        {
+                            _TryFormatDelegate = &EnumUtil<T>.TryFormatUtf16;
+                        }
+                        else if (typeof(T) == typeof(string))
+                        {
+                            _TryFormatDelegate = &TryFormatString;
+                        }
+                        else
+                        {
+                            _TryFormatDelegate = &TryFormatDefault;
+                        }
                     }
+
+                    return _TryFormatDelegate;
                 }
 
-                TryFormatDelegate = formatter;
+                set => _TryFormatDelegate = value;
             }
 
             static bool TryFormatString(T value, Span<char> dest, out int written, ReadOnlySpan<char> format)
diff --git a/src/ZString/ZString.csproj b/src/ZString/ZString.csproj
index eab6ce5..bb765cb 100644
--- a/src/ZString/ZString.csproj
+++ b/src/ZString/ZString.csproj
@@ -18,6 +18,7 @@
         <RepositoryUrl>$(PackageProjectUrl)</RepositoryUrl>
         <RepositoryType>git</RepositoryType>
         <PackageLicenseExpression>MIT</PackageLicenseExpression>
+      <LangVersion>9.0</LangVersion>
     </PropertyGroup>
 
     <ItemGroup>
@@ -187,6 +188,7 @@
     <!-- Copy files for Unity -->
     <PropertyGroup>
         <DestinationRoot>$(MSBuildProjectDirectory)\..\ZString.Unity\Assets\Scripts\ZString\</DestinationRoot>
+        <AssemblyName>NewZString</AssemblyName>
     </PropertyGroup>
     <ItemGroup>
         <TargetFiles1 Include="$(MSBuildProjectDirectory)\**\*.cs" Exclude="**\bin\**\*.*;**\obj\**\*.*;_InternalVisibleTo.cs" />
-- 
2.27.0.windows.1

```

</div>
</details>