はなちるのマイノート

Unityをメインとした技術ブログ。自分らしくまったりやっていきたいと思いますー!

【C#】Encoding.UTF8.GetStringでBOM付きのutf8を読み込むとutf16のBOMが入ることに気づいた

はじめに

以下のコードを書いていたときに、BOMに関して挙動が違うことに気づきました。

// utf-8のBOM付き.txtを読み込みたい

// パターンA
var x = File.ReadAllText(path);

// パターンB
var tmp = File.ReadAllBytes(path);
var y = Encoding.UTF8.GetString(tmp);

小ネタですが、地味に詰まったことがあるので書き残しておこうと思います。

実験

using System.Runtime.InteropServices;
using System.Text;

var path = "path/to/text1.txt";

// utf-8のBOM付き (EF BB BF)
// EF BB BF 48 65 6C 6C 6F
File.WriteAllText(path, "Hello", Encoding.UTF8);


// File.ReaddAllTextはBOMを無視して読み込む
var text1 = File.ReadAllText(path);

// 48 0 65 0 6c 0 6c 0 6f 0  (BOMなし)
foreach (var x in MemoryMarshal.Cast<char, byte>(text1.AsSpan()))
{
    Console.Write($"{x:x} ");
}
Console.WriteLine("");

// File.ReaddAllText(Encoding指定)はBOMを無視して読み込む
var text2 = File.ReadAllText(path, Encoding.UTF8);

// 48 0 65 0 6c 0 6c 0 6f 0  (BOMなし)
foreach (var x in MemoryMarshal.Cast<char, byte>(text2.AsSpan()))
{
    Console.Write($"{x:x} ");
}
Console.WriteLine("");


// Encoding.UTF8.GetStringで読み込む
var fileBytes = File.ReadAllBytes(path);

// ef bb bf 48 65 6c 6c 6f (utf-8のBOMあり)
Console.WriteLine(string.Join(" ", fileBytes.Select(x => x.ToString("x"))));

var text3 = Encoding.UTF8.GetString(fileBytes);

// ff fe 48 0 65 0 6c 0 6c 0 6f 0 (utf-16のBOMあり)
foreach (var x in MemoryMarshal.Cast<char, byte>(text3.AsSpan()))
{
    Console.Write($"{x:x} ");
}

// StreamReaderもBOMを無視して読み込む
var text4 = new StreamReader(path, Encoding.UTF8, true).ReadToEnd();

// 48 0 65 0 6c 0 6c 0 6f 0  (BOMなし)
foreach (var x in MemoryMarshal.Cast<char, byte>(text4.AsSpan()))
{
    Console.Write($"{x:x} ");
}
Console.WriteLine("");

補足するとC#のstringutf-16です。

  • Encoding.UTF8.GetString(bytes) : utf-16のBOM付きで格納
  • File.ReadAllText(path) : BOMを削除
  • File.ReadAllText(path, Encoding.Utf8) : BOMを削除
  • new StreamReader(path, Encoding.UTF8, true).ReadToEnd() ; BOMを削除

という結果になりました。