System.Text.EncodingクラスとBOMのあり/なしの指定方法について。 またSystem.IO.StreamWriter内部の動作や、Encodingを指定できるクラスでのBOM出力の動作と制御、その他BOM関連の処理について。
概略
StreamWriterではデフォルトでUTF-8が用いられるほか、StreamWriterコンストラクタの引数encodingにEncoding.UTF8プロパティ、Encoding.Defaultプロパティ、UTF8Encodingクラスのインスタンスを指定することによりUTF-8で書き込むことができる。
このとき、StreamWriterがBOM(Byte Order Mark)を出力するかどうかは、encodingに指定する値によって次のように変わる。
引数encodingに指定する値 | BOM出力 |
---|---|
(指定なし) | なし |
Encoding.UTF8
|
あり |
Encoding.Default (.NET Core/.NET 5以降) |
なし |
UTF8Encoding(encoderShouldEmitUTF8Identifier: true)
|
あり |
UTF8Encoding(encoderShouldEmitUTF8Identifier: false)
|
なし |
(強調したEF-BB-BF
のシーケンスがUTF-8のBOM)
UTF-8以外の場合も同様の動作となる。 BOMあり/なしで分類すると次のようになる。 Encodingクラスのプロパティとして用意されているものは、Encoding.Defaultを除くとすべてBOMありとなる。
エンコーディング | 引数encodingに指定する値 | |
---|---|---|
BOMあり | BOMなし | |
UTF-8 |
Encoding.UTF8
UTF8Encoding(encoderShouldEmitUTF8Identifier: true)
|
Encoding.Default (.NET Core/.NET 5以降)UTF8Encoding(encoderShouldEmitUTF8Identifier: false) |
UTF-16 Little Endian |
Encoding.Unicode
UnicodeEncoding(bigEndian: false, byteOrderMark: true)
|
UnicodeEncoding(bigEndian: false, byteOrderMark: false)
|
UTF-16 Big Endian |
Encoding.BigEndianUnicode
UnicodeEncoding(bigEndian: true, byteOrderMark: true)
|
UnicodeEncoding(bigEndian: true, byteOrderMark: false)
|
UTF-32 Little Endian |
Encoding.UTF32
UTF32Encoding(bigEndian: false, byteOrderMark: true)
|
UTF32Encoding(bigEndian: false, byteOrderMark: false)
|
UTF-32 Big Endian |
UTF32Encoding(bigEndian: true, byteOrderMark: true)
|
UTF32Encoding(bigEndian: true, byteOrderMark: false)
|
EncodingとBOMのあり/なし、StreamWriter等におけるBOM書き込みの動作
Encodingクラスおよび派生クラスでは、そのエンコーディングでのBOMを表すバイト列(以降preambleと表記)を取得することができる。 preambleは、Preambleプロパティ(.NET Standard 2.1/.NET Core 2.1以降)あるいはGetPreambleメソッド(.NET Framework 1.1以降)から取得できる。 これらはどちらも同じ内容のバイト列を返す。
UTF-8を扱うクラスUTF8Encodingにおいては、コンストラクタの引数encoderShouldEmitUTF8Identifierにtrue
を指定すると、preambleとしてUTF-8のBOMが設定される。 逆にfalse
を指定するとpreambleとして空のバイト列が設定される。
他方、StreamWriterなど書き込みを行うクラスでは、Encodingからpreambleを取得して出力する。 そのため、例えばStreamWriterがBOMを出力するかどうかは、StreamWriterに与えられたEncodingが何らかのpreambleを返すかどうかによって決まる。
この動作をより具体的に述べると次のようになる。 StreamWriterは、ストリームへの最初の書き込みを行う前にまずEncodingからpreambleを取得し、その内容をストリームへ書き込む。 このとき、preambleに何らかのバイト列が設定されていれば、それがBOMとして書き込まれることになる。 preambleとして空の配列が設定されている場合は、当然ストリームには何も書き込まれないため、結果としてBOMは書き込まれないことになる。
テキスト ファイルのエンコード方式を調べる方法はありますか。
(中略)
StreamWriter クラスも Encoding::GetPreamble() メソッドを呼び出し、テキスト ファイルの先頭にこのバイト列を書き込みます。これは優れた機能です。ユーザーがテキスト ファイルのエンコード方式をはっきり特定できるからです。ただし、弊社の多くの開発者は C 言語の知識を持っているため、テキスト ファイルの先頭に UTF-8 の Unicode BOM があると混乱してしまいました。また、Unicode に対応していないエディタ (vi、以前のバージョンの Emacs など) では扱いにくいこともあります。このため、StreamWriter クラスで既定で使用される UTF8Encoding では、GetPreamble メソッドから空のバイト列が返されます。UTF-8 ファイルに Unicode BOM を書き込むには、コード内で Encoding.UTF8 を明示的に指定します。
.NET Framework Developer Center:System.System.IO に関する FAQ | Microsoft Docs
UTF8Encodingと同様に、UTF-16を扱うUnicodeEncodingクラス・UTF-32を扱うUTF32Encodingクラスの場合も、コンストラクタの引数byteOrderMarkでpreambleとして設定するバイト列をBOMとするか空のバイト列とするかを指定することができる。 BOMが定義されないShift_JISなどのEncodingでは、preambleは空のバイト列となる。
StreamWriter以外のクラスやメソッドを使った書き込みの場合も、Encodingのpreambleを書き込む動作になっていれば、それによってBOM出力のありなしが決まる。 (§.File.WriteAllText、§.System.Xml名前空間)
一方、BinaryWriterはEncodingを指定できるものの、preambleは無視される(書き込まれない)ため、常にBOMなしで出力される。 (§.BinaryWriter)
なお、Encoding.GetBytesメソッドなどの文字列からバイト列等に変換するメソッドでは、preambleを含まない結果を返す。 (常にBOMなしとなる)
EncodingがBOMあり/なしかどうか知る方法
あるEncodingがBOMを出力するかどうかは、Preambleプロパティ・GetPreambleメソッドが返すpreambleの内容・長さによって判断できる。 そのため、これを調べることによって、EncodingやStreamWriterがBOMを出力するかどうか事前に知ることができる。
StreamReaderにおける文字コードの自動判別とBOMあり/なしの判別
StreamReaderでは、コンストラクタの引数detectEncodingFromByteOrderMarksにtrue
を指定すると、ストリーム先頭のBOMから文字コードの自動判別を行うようになる。
自動判別された文字コードはCurrentEncodingプロパティに反映されるが、BOMがない場合を含め自動判別できなかった場合はBOMありのUTF-8が設定される。 またPeek/Readなどのメソッドは、BOMを除いた上で読み込んだ内容を返す。 このため、StreamReaderからは読み込んだストリームがBOMあり/なしかどうかを判別することができない。
StreamクラスでEncodingの自動判別とBOMを維持した読み込みを行う例については§.BOMによる自動判別とBOMの透過的読み込みを行うStreamを参照。
各クラスにおけるBOM出力の動作と制御
以下は書き込みを行うクラスにおけるBOM出力の動作と、BOMありなしを指定する方法について。
標準ストリーム
Consoleクラス
Consoleクラスでは、Console.OutputEncodingによって標準出力で使用するエンコーディングを変更することができる。
.NET Framework/.NET 5のConsoleクラスでは、Console.OutputEncodingに設定したEncodingのpreambleによらずBOMは出力されない。 またWindows上では、chcpコマンドでコンソールの文字コードをUTF-8にしてもBOMは出力されない。
MonoのConsoleクラスでは、preambleに応じてBOMが出力される。 Monoでは環境変数LANG
(ja_JP.UTF-8
など)に従ってEncoding.Defaultに割り当てられるUTF8EncodingもBOMを出力する。
⚠区切り(改行文字)を強調してある
⚠区切り(改行文字)を強調してある
⚠区切り(改行文字)を強調してある
⚠区切り(改行文字)とBOMを強調してある
Console.SetOutメソッドで標準出力をStreamWriterにリダイレクトする場合は、StreamWriter単体の場合と同様にBOMが出力される。
標準エラーも標準出力と同様の動作になると思われる。 (実際の動作は未調査)
Processクラス
Processクラスでは、起動したプロセスの標準入力をリダイレクトして書き込む際に使用するエンコーディングをProcessStartInfo.StandardInputEncodingで変更することができる。
.NET 5およびMonoのProcessクラスでは、StreamWriterの場合と同様にEncodingに設定されているpreambleに応じてBOMが出力される。 Monoでは環境変数LANG
(ja_JP.UTF-8
など)に従ってEncoding.Defaultに割り当てられるUTF8EncodingもBOMを出力する。
.NET FrameworkではProcessStartInfo.StandardInputEncodingは定義されないため、標準入力への書き込みでは常にコンソールの文字コードが用いられる。 chcpコマンドでコンソールの文字コードをUTF-8にすると、BOMが出力される。
System.IO名前空間
StreamWriter (追記モード)
コンストラクタの引数appendにtrue
を指定して追記モードで書き込みを行う場合、ファイルが存在しない場合(新規作成となる場合)はEncodingのpreambleに応じてBOMが出力される。 一方、追記となる場合はpreambleによらずBOMの追記は行われない。
上書きモード(append = false
)でのStreamWriterの動作は前述のとおり。
File.WriteAllText
File.WriteAllTextなどのFileクラスの書き込みメソッドでは、StreamWriterと同様の動作でBOMが出力される。
File.AppendAllText
File.AppendAllTextなど、Fileクラスの追記による書き込みを行うメソッドでは、追記モードのStreamWriterと同様の動作となる。
BinaryWriter
BinaryWriterクラスでは、Writeメソッドで文字列を書き込むことができる。 また、このときに使用するEncodingをコンストラクタの引数encodingで指定することができる。
ただし、BinaryWriterはpreambleの内容に関わらずBOMを出力しない。 そのためBinaryWriterでBOMを出力させたい場合は、preambleを取得して明示的に書き込む必要がある。
なお、BinaryWriter.Writeメソッドでは文字列のバイト長を前置した上で書き込む動作となっている点に注意。
System.Xml名前空間
XmlWriter
XmlWriter.Createメソッドで作成したXmlWriterを使用する場合、BOMが出力されるかどうかはXmlWriterSettings.Encodingプロパティに設定するEncoding次第となる。 デフォルトではEncoding.UTF8が設定されているため、特に指定しなかった場合はBOMが出力される。
したがって、BOMを出力したくない場合は、BOMを出力しないEncodingを明示的にXmlWriterSettings.Encodingに指定する必要がある。
XmlDocument
XmlWriterを使わずXmlDocument.SaveメソッドでXML文書を出力する場合は次のようになる。
XML宣言(XmlDeclaration
)のencoding属性に"utf-8"
などを指定した場合は、BOM付きのUTF-8で出力される。 一方null
やstring.Empty
を指定した場合はBOMなしのUTF-8で出力されるが、encoding属性は省略される。
したがって、BOMを出力せず、かつencoding属性付きのXML宣言を出力したい場合にはXmlWriterを使う必要がある。
XDocument (System.Xml.Linq)
XmlWriterを使わずXDocument.SaveメソッドでXML文書を出力する場合は次のようになる。
XML宣言(XDeclaration
)のencoding属性に"utf-8"
/null
/string.Empty
のいずれを指定した場合でもBOM付きのUTF-8で出力される。 したがって、BOMを出力したくない場合には、BOMを出力しないEncodingを使用するXmlWriterを使う必要がある。
JsonSerializer (System.Text.Json)
RFC 8259 - The JavaScript Object Notation (JSON) Data Interchange Format (8.1. Character Encoding)にて、JSONフォーマットでは(閉じたシステム内で使用する場合を除き)UTF-8を使用しなければならない(MUST)、ネットワーク転送される場合はBOMを付加してはならない(MUST NOT)とされている。
JsonSerializerクラスはこれに沿った動作となっていて、常にBOMなしのUTF-8で出力される。 オプション等でEncodingを明示的に指定することはできない。
ファイルとして出力する際など、なんらかの理由でBOMを付加する必要がある場合は、次のように出力ストリームにBOMを書き込んでからJsonSerializerで書き込むことにより、BOMありのUTF-8として出力できる。
BOMによる自動判別とBOMの透過的読み込みを行うStream
以下は、BOMからEncodingを自動判別し、BOMの透過的な読み込みを行うStreamラッパーBomTransparentStream
を実装した例。
与えられたStreamからBOMを読み込み、Encodingの自動判別を行うと同時に、コンストラクタでの指定に応じてBOMを維持/破棄した上で読み込むことができる。 StreamReaderとは異なり、自動判別できなかった場合はBOMなしのUTF-8として判別される。
簡易な実装かつ限定的なユースケースのみでの動作確認しかしていないため、使用方法によっては実装を修正する必要が出てくると思われる。 その他の制限・未実装事項等についてはコード中のコメントを参照。
以下は使用例。