Encoding.GetString()は救われない
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)は
- GetChars()を呼んでChar[]を作り、
- new String(Char[])を呼ぶ
となっているため、途中で無駄なChar[]を作成してしまいます。
Encoding.GetString(Byte[])
- Encoding.GetString(Byte[], Int32, Int32)を呼ぶ
となっているため、先述の通りです。
Encoding.GetString(Byte*, Int32)
Encoding.GetString(Byte*, Int32)は
- String.CreateStringFromEncoding(Byte*, Int32, Encoding)を呼ぶ
となっているため、この後、探っていきます。
Encoding.GetString(ReadOnlySpan<Byte>)
Encoding.GetString(ReadOnlySpan<Byte>)は
- 固定してポインターを得て、
- String.CreateStringFromEncoding(Byte*, Int32, Encoding)を呼ぶ
となっているため、嫌な予感しかしませんが、先述の通りで、探っていきます。
Encoding.GetString()が呼び出すメソッド
String.CreateStringFromEncoding(Byte*, Int32, Encoding)
String.CreateStringFromEncoding(Byte*, Int32, Encoding)は
- Encoding.GetCharCount(Byte*, Int32)を呼んで長さを得て、
- String()領域を確保し、
- 固定してポインターを得て、
- Encoding.GetChars(Byte*, Int32, Char*, Int32)で書き込む
となっているため、Encoding.GetString(Byte[])の時より好感が持てます。呼び出し先を更に探ります。
Encoding.GetCharCount(Byte*, Int32)
Encoding.GetCharCount(Byte*, Int32)はまずvirtualなので派生クラスでオーバーライド可能です。デフォルト実装は
- ポインターからReadOnlySpanを作成し
- ReadOnlySpan.ToArray()でByte[]を作り、
- Encoding.GetCharCount(Byte[], Int32, Int32)を呼ぶ
おいいいいいい…ここまできて用もなく配列を作りますか。
結論
Encoding.GetString()のすべてのオーバーロードは変換途中で一時配列を作成しています。
おまけ… String(SByte*, Int32, Int32, Encoding)コンストラクタがありますが、こちらもEncoding.GetString()を呼ぶので同じようです。
よく使う、Shift-JISエンコーディングの場合、実体はDBCSCodePageEncodingで、親クラスがBaseCodePageEncodingで、その親クラスがEncodingNLSになっています。
EncodingNLSがEncoding.GetCharCount(Byte*, Int32)をオーバーライドすることで、余計な配列を作らずに済むようになっているようです。
またUTF-8エンコーディングの場合も、実体はUTF8Encodingで、こちらもEncoding.GetCharCount(Byte*, Int32)をオーバーライドすることで、余計な配列を作らずに済むようになっています。
これら個別のエンコーディングについては、Encoding.GetString(Byte*, Int32)かEncoding.GetString(ReadOnlySpan<Byte>)を使えば一時配列を作らずにStringに変換できるようです。
Discussion
DBCSCodePageEncodingもフォールバック処理か何かでnew byte[]していて、そこはArrayPoolとか使えんのか、と思いました。