🔖

Encoding.GetString()は救われない

2024/06/09に公開
1

Encoding.GetString()は救われない

.NETで文字コードを扱う際、Encoding.GetString()Stringに変換します。しかしこのメソッドは.NET 8.0.6現在、救われない実装になっていることが分かりました。

Encoding.GetString()のオーバーロード

Encoding.GetString()には4つのオーバーロードがありますので、それぞれ見ていきます。

Encoding.GetString(Byte[], Int32, Int32)

Encoding.GetString(Byte[], Int32, Int32)

  1. GetChars()を呼んでChar[]を作り
  2. new String(Char[])を呼ぶ

となっているため、途中で無駄なChar[]を作成してしまいます。

Encoding.GetString(Byte[])

Encoding.GetString(Byte[])

  1. Encoding.GetString(Byte[], Int32, Int32)を呼ぶ

となっているため、先述の通りです。

Encoding.GetString(Byte*, Int32)

Encoding.GetString(Byte*, Int32)

  1. String.CreateStringFromEncoding(Byte*, Int32, Encoding)を呼ぶ

となっているため、この後、探っていきます。

Encoding.GetString(ReadOnlySpan<Byte>)

Encoding.GetString(ReadOnlySpan<Byte>)

  1. 固定してポインターを得て、
  2. String.CreateStringFromEncoding(Byte*, Int32, Encoding)を呼ぶ

となっているため、嫌な予感しかしませんが、先述の通りで、探っていきます。

Encoding.GetString()が呼び出すメソッド

String.CreateStringFromEncoding(Byte*, Int32, Encoding)

String.CreateStringFromEncoding(Byte*, Int32, Encoding)

  1. Encoding.GetCharCount(Byte*, Int32)を呼んで長さを得て、
  2. String()領域を確保し、
  3. 固定してポインターを得て、
  4. Encoding.GetChars(Byte*, Int32, Char*, Int32)で書き込む

となっているため、Encoding.GetString(Byte[])の時より好感が持てます。呼び出し先を更に探ります。

Encoding.GetCharCount(Byte*, Int32)

Encoding.GetCharCount(Byte*, Int32)はまずvirtualなので派生クラスでオーバーライド可能です。デフォルト実装は

  1. ポインターからReadOnlySpanを作成し
  2. ReadOnlySpan.ToArray()でByte[]を作り
  3. Encoding.GetCharCount(Byte[], Int32, Int32)を呼ぶ

おいいいいいい…ここまできて用もなく配列を作りますか。

結論

Encoding.GetString()のすべてのオーバーロードは変換途中で一時配列を作成しています。

おまけ… String(SByte*, Int32, Int32, Encoding)コンストラクタがありますが、こちらもEncoding.GetString()を呼ぶので同じようです。


よく使う、Shift-JISエンコーディングの場合、実体はDBCSCodePageEncodingで、親クラスがBaseCodePageEncodingで、その親クラスがEncodingNLSになっています。
EncodingNLSEncoding.GetCharCount(Byte*, Int32)をオーバーライドすることで、余計な配列を作らずに済むようになっているようです。

またUTF-8エンコーディングの場合も、実体はUTF8Encodingで、こちらもEncoding.GetCharCount(Byte*, Int32)をオーバーライドすることで、余計な配列を作らずに済むようになっています。

これら個別のエンコーディングについては、Encoding.GetString(Byte*, Int32)Encoding.GetString(ReadOnlySpan<Byte>)を使えば一時配列を作らずにStringに変換できるようです。

Discussion

あえとすあえとす

DBCSCodePageEncoding もフォールバック処理か何かで new byte[] していて、そこは ArrayPool とか使えんのか、と思いました。