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)
|
なし |
using System;
using System.IO;
using System.Text;
foreach (var createStreamWriter in new Func<Stream, StreamWriter>[] {
// 引数encodingに指定する値を変えてStreamWriterを作成する
s => new(s), // encodingの指定なし
s => new(s, encoding: Encoding.UTF8),
s => new(s, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
using var stream = new MemoryStream();
const string text = "日本語";
using (var writer = createStreamWriter(stream)) {
writer.Write(text);
}
// StreamWriterが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(stream.ToArray()));
}
Option Infer On
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
' 引数encodingに指定する値を変えてStreamWriterを作成する
For Each createStreamWriter In New Func(Of Stream, StreamWriter)() {
Function(s) New StreamWriter(s), ' encodingの指定なし
Function(s) New StreamWriter(s, encoding := Encoding.UTF8),
Function(s) New StreamWriter(s, encoding := Encoding.Default), ' ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
Function(s) New StreamWriter(s, encoding := New UTF8Encoding(encoderShouldEmitUTF8Identifier := True)),
Function(s) New StreamWriter(s, encoding := New UTF8Encoding(encoderShouldEmitUTF8Identifier := False))
}
Using stream As New MemoryStream()
Const text As String = "日本語"
Using writer = createStreamWriter(stream)
writer.Write(text)
End Using
' StreamWriterが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(stream.ToArray()))
End Using
Next
End Sub
End Class
E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
(強調した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を出力するかどうか事前に知ることができる。
using System;
using System.Text;
class Sample {
static void Main()
{
var encodings = new[] {
Encoding.UTF8, // UTF-8 BOM
Encoding.Default, // UTF-8 non-BOM
new UTF8Encoding(true), // UTF-8 BOM
new UTF8Encoding(false), // UTF-8 non-BOM
Encoding.Unicode, // UTF-16 Little Endian BOM
new UnicodeEncoding(false, false), // UTF-16 Little Endian non-BOM
shift_jis, // Shift_JIS
};
foreach (var encoding in encodings) {
var preamble = encoding.GetPreamble();
Console.WriteLine(
"{0,12} {1,3} <{2}>",
encoding.WebName, // encodingのweb nameを表示
preamble.Length == 0 ? string.Empty : "BOM", // preambleの長さからBOMのありなしを判定
BitConverter.ToString(preamble) // preambleの内容を表示
);
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
Option Infer On
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
Dim encodings = New Encoding() {
Encoding.UTF8, ' UTF-8 BOM
Encoding.Default, ' UTF-8 non-BOM
New UTF8Encoding(True), ' UTF-8 BOM
New UTF8Encoding(False), ' UTF-8 non-BOM
Encoding.Unicode, ' UTF-16 Little Endian BOM
New UnicodeEncoding(False, False), ' UTF-16 Little Endian non-BOM
shift_jis ' Shift_JIS
}
For Each encoding In encodings
Dim preamble = encoding.GetPreamble()
Console.WriteLine(
"{0,12} {1,3} <{2}>",
encoding.WebName, ' encodingのweb nameを表示
If(preamble.Length = 0, String.Empty, "BOM"), ' preambleの長さからBOMのありなしを判定
BitConverter.ToString(preamble) ' preambleの内容を表示
)
Next
End Sub
#If NETFRAMEWORK Then
Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
utf-8 BOM <EF-BB-BF> utf-8 <> utf-8 BOM <EF-BB-BF> utf-8 <> utf-16 BOM <FF-FE> utf-16 <> shift_jis <>
StreamReaderにおける文字コードの自動判別とBOMあり/なしの判別
StreamReaderでは、コンストラクタの引数detectEncodingFromByteOrderMarksにtrue
を指定すると、ストリーム先頭のBOMから文字コードの自動判別を行うようになる。
自動判別された文字コードはCurrentEncodingプロパティに反映されるが、BOMがない場合を含め自動判別できなかった場合はBOMありのUTF-8が設定される。 またPeek/Readなどのメソッドは、BOMを除いた上で読み込んだ内容を返す。 このため、StreamReaderからは読み込んだストリームがBOMあり/なしかどうかを判別することができない。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
var encodings = new Encoding[] {
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
new UnicodeEncoding(bigEndian: true, byteOrderMark: true),
new UnicodeEncoding(bigEndian: true, byteOrderMark: false),
new UnicodeEncoding(bigEndian: false, byteOrderMark: true),
new UnicodeEncoding(bigEndian: false, byteOrderMark: false),
new UTF32Encoding(bigEndian: true, byteOrderMark: true),
new UTF32Encoding(bigEndian: true, byteOrderMark: false),
new UTF32Encoding(bigEndian: false, byteOrderMark: true),
new UTF32Encoding(bigEndian: false, byteOrderMark: false),
shift_jis,
};
const string text = "日本語";
foreach (var encoding in encodings) {
// 各種Encoding+BOMあり/なしでMemoryStreamに書き込む
Console.Write($"{encoding.WebName,12} {(encoding.Preamble.Length == 0 ? string.Empty : "BOM"),3} => ");
using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, encoding, leaveOpen: true)) {
writer.Write(text);
}
stream.Position = 0L;
// BOMによるEncodingの自動判別を有効にしてMemoryStreamから読み込む
using (var reader = new StreamReader(stream, detectEncodingFromByteOrderMarks: true)) {
reader.Peek(); // 自動判別されたEncodingを参照するには、まずPeek/Readを行う必要がある
// 自動判別されたEncodingを参照する
var e = reader.CurrentEncoding;
Console.WriteLine($"{e.WebName,12} {(e.Preamble.Length == 0 ? string.Empty : "BOM"),3}");
}
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
Option Infer On
Imports System
Imports System.IO
Imports System.Text
Class Sample
Shared Sub Main()
Dim encodings = New Encoding() {
New UTF8Encoding(encoderShouldEmitUTF8Identifier := True),
New UTF8Encoding(encoderShouldEmitUTF8Identifier := False),
New UnicodeEncoding(bigEndian := True, byteOrderMark := True),
New UnicodeEncoding(bigEndian := True, byteOrderMark := False),
New UnicodeEncoding(bigEndian := False, byteOrderMark := True),
New UnicodeEncoding(bigEndian := False, byteOrderMark := False),
New UTF32Encoding(bigEndian := True, byteOrderMark := True),
New UTF32Encoding(bigEndian := True, byteOrderMark := False),
New UTF32Encoding(bigEndian := False, byteOrderMark := True),
New UTF32Encoding(bigEndian := False, byteOrderMark := False),
shift_jis
}
Const text As String = "日本語"
For Each encoding In encodings
' 各種Encoding+BOMあり/なしでMemoryStreamに書き込む
Console.Write($"{encoding.WebName,12} {If(encoding.Preamble.Length = 0, String.Empty, "BOM"),3} => ")
Using stream As New MemoryStream()
Using writer As New StreamWriter(stream, encoding, leaveOpen := True)
writer.Write(text)
End Using
stream.Position = 0L
' BOMによるEncodingの自動判別を有効にしてMemoryStreamから読み込む
Using reader As New StreamReader(stream, detectEncodingFromByteOrderMarks := True)
reader.Peek() ' 自動判別されたEncodingを参照するには、まずPeek/Readを行う必要がある
' 自動判別されたEncodingを参照する
Dim e = reader.CurrentEncoding
Console.WriteLine($"{e.WebName,12} {If(e.Preamble.Length = 0, String.Empty, "BOM"),3}")
End Using
End Using
Next
End Sub
#If NETFRAMEWORK Then
Shared ReadOnly shift_jis As Encoding = Encoding.GetEncoding("shift_jis")
#Else
' `dotnet add sample.vbproj package System.Text.Encoding.CodePages`
Shared ReadOnly shift_jis As Encoding = CodePagesEncodingProvider.Instance.GetEncoding("shift_jis")
#End If
End Class
utf-8 BOM => utf-8 BOM utf-8 => utf-8 BOM utf-16BE BOM => utf-16BE BOM utf-16BE => utf-8 BOM utf-16 BOM => utf-16 BOM utf-16 => utf-8 BOM utf-32BE BOM => utf-32BE BOM utf-32BE => utf-8 BOM utf-32 BOM => utf-32 BOM utf-32 => utf-8 BOM shift_jis => utf-8 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を出力する。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
const string text = "日本語";
foreach (var e in new Encoding[] {
// Console.OutputEncodingに指定する値を変えて標準出力に書き込む
null, // 変更しない
Encoding.UTF8,
Encoding.Default, // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
}) {
if (e != null)
Console.OutputEncoding = e;
Console.WriteLine(text);
Console.Out.Flush();
}
}
}
>chcp 現在のコード ページ: 932 >dotnet run > out.txt && certutil -encodehex out.txt out.hex.txt && type out.hex.txt Input Length = 52 Output Length = 284 CertUtil: -encodehex command completed successfully. 0000 93 fa 96 7b 8c ea 0d 0a e6 97 a5 e6 9c ac e8 aa ...{............ 0010 9e 0d 0a e6 97 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 ................ 0020 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ac e8 ................ 0030 aa 9e 0d 0a >chcp 65001 Active code page: 65001 >dotnet run > out.txt && certutil -encodehex out.txt out.hex.txt && type out.hex.txt Input Length = 55 Output Length = 287 CertUtil: -encodehex command completed successfully. 0000 e6 97 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ................ 0010 ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ac e8 aa 9e 0d ................ 0020 0a e6 97 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 a5 e6 ................ 0030 9c ac e8 aa 9e 0d 0a .......
⚠区切り(改行文字)を強調してある
>chcp 現在のコード ページ: 932 >csc test.cs && test.exe > out.txt && certutil -encodehex out.txt out.hex.txt && type out.hex.txt Input Length = 49 Output Length = 281 CertUtil: -encodehex command completed successfully. 0000 93 fa 96 7b 8c ea 0d 0a e6 97 a5 e6 9c ac e8 aa ...{............ 0010 9e 0d 0a 93 fa 96 7b 8c ea 0d 0a e6 97 a5 e6 9c ......{......... 0020 ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ac e8 aa 9e 0d ................ 0030 0a . >chcp 65001 Active code page: 65001 >csc test.cs && test.exe > out.txt && certutil -encodehex out.txt out.hex.txt && type out.hex.txt Input Length = 52 Output Length = 284 CertUtil: -encodehex command completed successfully. 0000 e6 97 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ................ 0010 ac e8 aa 9e 0d 0a 93 fa 96 7b 8c ea 0d 0a e6 97 .........{...... 0020 a5 e6 9c ac e8 aa 9e 0d 0a e6 97 a5 e6 9c ac e8 ................ 0030 aa 9e 0d 0a ....
⚠区切り(改行文字)を強調してある
$echo $LANG ja_JP.UTF-8 $dotnet run | od -tx1 0000000 e6 97 a5 e6 9c ac e8 aa 9e 0a e6 97 a5 e6 9c ac 0000020 e8 aa 9e 0a e6 97 a5 e6 9c ac e8 aa 9e 0a e6 97 0000040 a5 e6 9c ac e8 aa 9e 0a e6 97 a5 e6 9c ac e8 aa 0000060 9e 0a 0000062
⚠区切り(改行文字)を強調してある
$echo $LANG ja_JP.UTF-8 $mcs test.cs && mono test.exe | od -tx1 0000000 e6 97 a5 e6 9c ac e8 aa 9e 0a ef bb bf e6 97 a5 0000020 e6 9c ac e8 aa 9e 0a ef bb bf e6 97 a5 e6 9c ac 0000040 e8 aa 9e 0a ef bb bf e6 97 a5 e6 9c ac e8 aa 9e 0000060 0a e6 97 a5 e6 9c ac e8 aa 9e 0a 0000073
⚠区切り(改行文字)とBOMを強調してある
Console.SetOutメソッドで標準出力をStreamWriterにリダイレクトする場合は、StreamWriter単体の場合と同様にBOMが出力される。
using System;
using System.IO;
using System.Text;
const string text = "日本語";
var defaultStdOut = Console.Out;
foreach (var createStreamWriter in new Func<Stream, StreamWriter>[] {
// 引数encodingに指定する値を変えてStreamWriterを作成する
s => new(s), // encodingの指定なし
s => new(s, encoding: Encoding.UTF8),
s => new(s, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
using (var stdout = new MemoryStream()) {
// StreamWriterを作成し、標準出力のリダイレクト先として設定する
var writer = createStreamWriter(stdout);
writer.AutoFlush = true;
Console.SetOut(writer);
// リダイレクトした標準出力に書き込む
Console.Write(text);
// 標準出力に書き込まれたバイト列を16進形式で表示
defaultStdOut.WriteLine(BitConverter.ToString(stdout.ToArray()));
}
}
E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
標準エラーも標準出力と同様の動作になると思われる。 (実際の動作は未調査)
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が出力される。
using System;
using System.Diagnostics;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("CHILD")))
ParentMain();
else
ChildMain();
}
static void ParentMain()
{
foreach (var e in new Encoding[] {
null,
#if !NETFRAMEWORK
Encoding.UTF8,
Encoding.Default, // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
#endif
}) {
// StandardInputEncodingに指定するEncodingを変えてProcessStartInfoを作成する
// (現在のプロセスと同じ実行可能ファイルを子プロセスとして起動し、標準入力に書き込む)
var psi =
#if MONO
new ProcessStartInfo("mono", System.Reflection.Assembly.GetExecutingAssembly().Location)
#else
new ProcessStartInfo(Process.GetCurrentProcess().MainModule.FileName)
#endif
{
EnvironmentVariables = { {"CHILD", "1"} },
UseShellExecute = false,
RedirectStandardInput = true,
#if !NETFRAMEWORK
StandardInputEncoding = e,
#endif
};
const string text = "日本語";
using (var child = Process.Start(psi)) {
child.StandardInput.Write(text);
child.StandardInput.Close();
child.WaitForExit();
}
}
}
static void ChildMain()
{
// 標準入力に書き込まれたバイト列を16進形式で表示
var reader = new BinaryReader(Console.OpenStandardInput());
Console.WriteLine(BitConverter.ToString(reader.ReadBytes(32)));
}
}
>chcp 現在のコード ページ: 932 >dotnet run 93-FA-96-7B-8C-EA EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E >chcp 65001 Active code page: 65001 >dotnet run E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
>chcp 現在のコード ページ: 932 >csc /nologo /d:NETFRAMEWORK test.cs && test.exe 93-FA-96-7B-8C-EA >chcp 65001 Active code page: 65001 >csc /nologo /d:NETFRAMEWORK test.cs && test.exe EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E
$echo $LANG ja_JP.UTF-8 $dotnet run E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
$echo $LANG ja_JP.UTF-8 $mcs -d:MONO test.cs && mono test.exe E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
System.IO名前空間
StreamWriter (追記モード)
コンストラクタの引数appendにtrue
を指定して追記モードで書き込みを行う場合、ファイルが存在しない場合(新規作成となる場合)はEncodingのpreambleに応じてBOMが出力される。 一方、追記となる場合はpreambleによらずBOMの追記は行われない。
上書きモード(append = false
)でのStreamWriterの動作は前述のとおり。
using System;
using System.IO;
using System.Text;
const string path = "out.txt";
const string text = "日本語";
foreach (var createStreamWriter in new Func<StreamWriter>[] {
// 引数encodingに指定する値を変えて、追記モード(append: true)のStreamWriterを作成する
() => new(path, append: true), // encodingの指定なし
() => new(path, append: true, encoding: Encoding.UTF8),
() => new(path, append: true, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
() => new(path, append: true, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
() => new(path, append: true, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
if (File.Exists(path))
File.Delete(path); // ファイルを削除しておく
// 新規に作成したファイルへの書き込みとなる場合
using (var writer = createStreamWriter()) {
writer.Write(text);
}
// StreamWriterが出力したバイト列を16進形式で表示
Console.Write("{0,-40} => ", BitConverter.ToString(File.ReadAllBytes(path)));
// 既存のファイルへの追記となる場合
using (var writer = createStreamWriter()) {
writer.Write(text);
}
// StreamWriterが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
}
E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E => EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E => EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E
File.WriteAllText
File.WriteAllTextなどのFileクラスの書き込みメソッドでは、StreamWriterと同様の動作でBOMが出力される。
using System;
using System.IO;
using System.Text;
const string contents = "日本語";
const string path = "out.txt";
foreach (var writeAllText in new Action[] {
// 引数encodingに指定する値を変えてFile.WriteAllTextを呼び出す
() => File.WriteAllText(path, contents), // encodingの指定なし
() => File.WriteAllText(path, contents, encoding: Encoding.UTF8),
() => File.WriteAllText(path, contents, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
() => File.WriteAllText(path, contents, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
() => File.WriteAllText(path, contents, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
writeAllText();
// File.WriteAllTextが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
}
E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E
File.AppendAllText
File.AppendAllTextなど、Fileクラスの追記による書き込みを行うメソッドでは、追記モードのStreamWriterと同様の動作となる。
using System;
using System.IO;
using System.Text;
const string contents = "日本語";
const string path = "out.txt";
foreach (var appendAllText in new Action[] {
// 引数encodingに指定する値を変えてFile.AppendAllTextを呼び出す
() => File.AppendAllText(path, contents), // encodingの指定なし
() => File.AppendAllText(path, contents, encoding: Encoding.UTF8),
() => File.AppendAllText(path, contents, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
() => File.AppendAllText(path, contents, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
() => File.AppendAllText(path, contents, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
if (File.Exists(path))
File.Delete(path); // ファイルを削除しておく
appendAllText(); // 新規に作成したファイルへの書き込みとなる場合
// File.AppendAllTextが出力したバイト列を16進形式で表示
Console.Write("{0,-40} => ", BitConverter.ToString(File.ReadAllBytes(path)));
appendAllText(); // 既存のファイルへの追記となる場合
// File.AppendAllTextが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
}
E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E => EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E => EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E-E6-97-A5-E6-9C-AC-E8-AA-9E
BinaryWriter
BinaryWriterクラスでは、Writeメソッドで文字列を書き込むことができる。 また、このときに使用するEncodingをコンストラクタの引数encodingで指定することができる。
ただし、BinaryWriterはpreambleの内容に関わらずBOMを出力しない。 そのためBinaryWriterでBOMを出力させたい場合は、preambleを取得して明示的に書き込む必要がある。
なお、BinaryWriter.Writeメソッドでは文字列のバイト長を前置した上で書き込む動作となっている点に注意。
using System;
using System.IO;
using System.Text;
const string text = "日本語";
// 比較のため文字列のバイト表現を取得して表示
Console.WriteLine("{0} ('{1}')", BitConverter.ToString(Encoding.UTF8.GetBytes(text)), text);
Console.WriteLine();
foreach (var createBinaryWriter in new Func<Stream, BinaryWriter>[] {
// 引数encodingに指定する値を変えてBinaryWriterを作成する
s => new(s), // encodingの指定なし
s => new(s, encoding: Encoding.UTF8),
s => new(s, encoding: Encoding.Default), // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: true)),
s => new(s, encoding: new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)),
}) {
using var stream = new MemoryStream();
using (var writer = createBinaryWriter(stream)) {
writer.Write(text);
}
// BinaryWriterが出力したバイト列を16進形式で表示
Console.WriteLine(BitConverter.ToString(stream.ToArray()));
}
E6-97-A5-E6-9C-AC-E8-AA-9E ('日本語') 09-E6-97-A5-E6-9C-AC-E8-AA-9E 09-E6-97-A5-E6-9C-AC-E8-AA-9E 09-E6-97-A5-E6-9C-AC-E8-AA-9E 09-E6-97-A5-E6-9C-AC-E8-AA-9E 09-E6-97-A5-E6-9C-AC-E8-AA-9E
System.Xml名前空間
XmlWriter
XmlWriter.Createメソッドで作成したXmlWriterを使用する場合、BOMが出力されるかどうかはXmlWriterSettings.Encodingプロパティに設定するEncoding次第となる。 デフォルトではEncoding.UTF8が設定されているため、特に指定しなかった場合はBOMが出力される。
したがって、BOMを出力したくない場合は、BOMを出力しないEncodingを明示的にXmlWriterSettings.Encodingに指定する必要がある。
using System;
using System.IO;
using System.Text;
using System.Xml;
const string path = "out.xml";
var doc = new XmlDocument();
doc.AppendChild(doc.CreateElement("doc"));
foreach (var createWriterSettings in new Func<XmlWriterSettings>[] {
// Encodingプロパティに指定する値を変えてXmlWriterSettingsを作成する
() => new(), // Encodingの指定なし (デフォルトではEncoding.UTF8が設定されている)
() => new() { Encoding = Encoding.UTF8 },
() => new() { Encoding = Encoding.Default }, // ⚠.NET FrameworkのEncoding.DefaultはUTF-8ではなくShift_JIS等のANSIコードページとなる
() => new() { Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: true) },
() => new() { Encoding = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false) },
}) {
using (var writer = XmlWriter.Create(path, createWriterSettings())) {
doc.Save(writer);
}
// XmlWriterが出力した内容を文字列・16進形式で表示
Console.WriteLine(File.ReadAllText(path));
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
Console.WriteLine();
}
<?xml version="1.0" encoding="utf-8"?><doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?><doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?><doc /> 3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?><doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?><doc /> 3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-3C-64-6F-63-20-2F-3E
XmlDocument
XmlWriterを使わずXmlDocument.SaveメソッドでXML文書を出力する場合は次のようになる。
XML宣言(XmlDeclaration
)のencoding属性に"utf-8"
などを指定した場合は、BOM付きのUTF-8で出力される。 一方null
やstring.Empty
を指定した場合はBOMなしのUTF-8で出力されるが、encoding属性は省略される。
したがって、BOMを出力せず、かつencoding属性付きのXML宣言を出力したい場合にはXmlWriterを使う必要がある。
using System;
using System.IO;
using System.Xml;
const string path = "out.xml";
foreach (var createXmlDeclaration in new Func<XmlDocument, XmlDeclaration>[] {
// encoding属性に指定する値を変えてXML宣言(XmlDeclaration)を作成する
d => d.CreateXmlDeclaration(version: "1.0", encoding: "utf-8", standalone: null),
d => d.CreateXmlDeclaration(version: "1.0", encoding: null, standalone: null),
d => d.CreateXmlDeclaration(version: "1.0", encoding: string.Empty, standalone: null),
}) {
var doc = new XmlDocument();
doc.AppendChild(createXmlDeclaration(doc));
doc.AppendChild(doc.CreateElement("doc"));
doc.Save(path);
// XmlDocument.Saveが出力した内容を文字列・16進形式で表示
Console.WriteLine(File.ReadAllText(path));
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
Console.WriteLine();
}
<?xml version="1.0" encoding="utf-8"?> <doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-0A-3C-64-6F-63-20-2F-3E <?xml version="1.0"?> <doc /> 3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-3F-3E-0A-3C-64-6F-63-20-2F-3E <?xml version="1.0"?> <doc /> 3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-3F-3E-0A-3C-64-6F-63-20-2F-3E
XDocument (System.Xml.Linq)
XmlWriterを使わずXDocument.SaveメソッドでXML文書を出力する場合は次のようになる。
XML宣言(XDeclaration
)のencoding属性に"utf-8"
/null
/string.Empty
のいずれを指定した場合でもBOM付きのUTF-8で出力される。 したがって、BOMを出力したくない場合には、BOMを出力しないEncodingを使用するXmlWriterを使う必要がある。
using System;
using System.IO;
using System.Xml.Linq;
const string path = "out.xml";
foreach (var createXDeclaration in new Func<XDeclaration>[] {
// encoding属性に指定する値を変えてXML宣言(XDeclaration)を作成する
() => new(version: "1.0", encoding: "utf-8", standalone: null),
() => new(version: "1.0", encoding: null, standalone: null),
() => new(version: "1.0", encoding: string.Empty, standalone: null),
}) {
var doc = new XDocument(
createXDeclaration(),
new XElement("doc")
);
doc.Save(path);
// XDocument.Saveが出力した内容を文字列・16進形式で表示
Console.WriteLine(File.ReadAllText(path));
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
Console.WriteLine();
}
<?xml version="1.0" encoding="utf-8"?> <doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-0A-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?> <doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-0A-3C-64-6F-63-20-2F-3E <?xml version="1.0" encoding="utf-8"?> <doc /> EF-BB-BF-3C-3F-78-6D-6C-20-76-65-72-73-69-6F-6E-3D-22-31-2E-30-22-20-65-6E-63-6F-64-69-6E-67-3D-22-75-74-66-2D-38-22-3F-3E-0A-3C-64-6F-63-20-2F-3E
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を明示的に指定することはできない。
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
var stream = new MemoryStream();
await JsonSerializer.SerializeAsync<string[]>(stream, new[] {"json"});
// JsonSerializerが出力した内容を文字列・16進形式で表示
Console.WriteLine(Encoding.UTF8.GetString(stream.ToArray()));
Console.WriteLine(BitConverter.ToString(stream.ToArray()));
["json"] 5B-22-6A-73-6F-6E-22-5D
ファイルとして出力する際など、なんらかの理由でBOMを付加する必要がある場合は、次のように出力ストリームにBOMを書き込んでからJsonSerializerで書き込むことにより、BOMありのUTF-8として出力できる。
using System;
using System.IO;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
const string path = "out.json";
using (var stream = File.OpenWrite(path)) {
stream.SetLength(0L); // 既存の内容を上書きする(truncate)
stream.Write(Encoding.UTF8.Preamble); // UTF-8のBOMを書き込む
await JsonSerializer.SerializeAsync<string[]>(stream, new[] {"json"}); // JsonSerializerでStreamに書き込む
}
// JsonSerializerが出力した内容を文字列・16進形式で表示
Console.WriteLine(File.ReadAllText(path));
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
["json"] EF-BB-BF-5B-22-6A-73-6F-6E-22-5D
BOMによる自動判別とBOMの透過的読み込みを行うStream
以下は、BOMからEncodingを自動判別し、BOMの透過的な読み込みを行うStreamラッパーBomTransparentStream
を実装した例。
与えられたStreamからBOMを読み込み、Encodingの自動判別を行うと同時に、コンストラクタでの指定に応じてBOMを維持/破棄した上で読み込むことができる。 StreamReaderとは異なり、自動判別できなかった場合はBOMなしのUTF-8として判別される。
簡易な実装かつ限定的なユースケースのみでの動作確認しかしていないため、使用方法によっては実装を修正する必要が出てくると思われる。 その他の制限・未実装事項等についてはコード中のコメントを参照。
using System;
using System.IO;
using System.Linq;
using System.Text;
/// <summary>与えられたStreamの内容を読み込み、BOMを維持/破棄した上で透過的に返すStream。 同時にBOMからEncodingを自動判別も行う。</summary>
class BomTransparentStream : Stream {
private Stream baseStream;
public Stream BaseStream => baseStream ?? throw new ObjectDisposedException(GetType().FullName);
private readonly bool discardBom;
public BomTransparentStream(string path, bool discardBom = true)
: this(File.OpenRead(path), discardBom) {}
public BomTransparentStream(Stream baseStream, bool discardBom = true)
{
this.baseStream = baseStream ?? throw new ArgumentNullException(nameof(baseStream));
this.discardBom = discardBom;
}
public override void Close()
{
baseStream?.Close();
baseStream = null;
base.Close();
}
public override bool CanSeek => false; // シークはサポートしない
public override bool CanRead => BaseStream.CanRead;
public override bool CanWrite => false; // 書き込みはサポートしない
public override bool CanTimeout => BaseStream.CanTimeout;
public override long Position {
get => BaseStream.Position; // 本来ならBOM分を差し引いた値を返すべき
set => throw new NotSupportedException();
}
public override long Length => BaseStream.Length; // 本来ならBOM分を差し引いた値を返すべき
public override void SetLength(long @value) => throw new NotSupportedException(); // 長さの設定はサポートしない
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void Flush() => throw new NotSupportedException();
public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
/// <summary>BOMから自動判別されたEncoding</summary>
/// <remarks>未判別の状態、また判別できなかった場合はEncoding.Defaultを返す</remarks>
public Encoding DetectedEncoding { get; private set; } = Encoding.Default;
/// <summary>まだBOMを読み込んでいない場合は、BOMの読み込みとEncodingの自動判別を行う</summary>
public void ReadPreamble()
{
if (isPreamble)
Read(Array.Empty<byte>(), 0, 0);
}
private bool isPreamble = true;
private ReadOnlyMemory<byte> preamble = default;
/// <summary>BaseStreamから読み込み、BOMを維持/破棄した結果を返す。 最初に呼び出された際は、BOMからEncodingの自動判別も行う。</summary>
public override int Read(byte[] buffer, int offset, int count)
{
// 引数チェックは省略
if (isPreamble) {
isPreamble = false;
// まだBOMを読み込んでいない場合は、BaseStreamの先頭4バイト分を読み込む
Memory<byte> _preamble = new byte[4];
BaseStream.Read(_preamble.Span); // 戻り値のチェックは省略、この実装ではStreamの内容が4バイト未満の場合は想定していない
// Encodingの判別を行い、判別できた場合はBOMを除去した結果を取得する
var (detectedEncoding, remainder) = DetectEncodingFromPreamble(_preamble);
DetectedEncoding = detectedEncoding;
// BOMを維持/破棄した内容をpreambleとして保持する
preamble = discardBom ? remainder : _preamble;
}
var buf = buffer.AsMemory(offset, count);
var readCount = 0;
for (;;) {
// preambleとして保持されている内容が空なら、BaseStreamから読み込み、そのまま返す
if (preamble.IsEmpty)
return readCount + BaseStream.Read(buf.Span);
// preambleの内容をbufferにコピーする
if (preamble.Length <= buf.Length) {
var len = preamble.Length;
preamble.CopyTo(buf);
preamble = default; // コピー済みのため、空にする
buf = buf.Slice(len);
readCount += len;
}
else {
var len = buf.Length;
preamble.Slice(0, len).CopyTo(buf);
preamble = preamble.Slice(len);
return len;
}
}
}
private static (Encoding, ReadOnlyMemory<byte>) DetectEncodingFromPreamble(ReadOnlyMemory<byte> preamble)
{
// 与えられたpreambleからEncodingを判別する
var e = encodings.FirstOrDefault(e => preamble.Span.StartsWith(e.Preamble)) ?? Encoding.Default;
// 判別したEncodingと、BOMを差し引いた分を返す
return (e, preamble.Slice(e.Preamble.Length));
}
// BOMの長い順にソート済みのEncoding
private static readonly Encoding[] encodings = new[] {
Encoding.UTF32,
new UTF32Encoding(bigEndian: true, byteOrderMark: true),
Encoding.UTF8,
Encoding.Unicode,
Encoding.BigEndianUnicode,
};
}
以下は使用例。
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
var encodings = new Encoding[] {
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
new UnicodeEncoding(bigEndian: true, byteOrderMark: true),
new UnicodeEncoding(bigEndian: true, byteOrderMark: false),
new UnicodeEncoding(bigEndian: false, byteOrderMark: true),
new UnicodeEncoding(bigEndian: false, byteOrderMark: false),
new UTF32Encoding(bigEndian: true, byteOrderMark: true),
new UTF32Encoding(bigEndian: true, byteOrderMark: false),
new UTF32Encoding(bigEndian: false, byteOrderMark: true),
new UTF32Encoding(bigEndian: false, byteOrderMark: false),
shift_jis,
};
const string contents = "日本語";
const string path = "out.txt";
foreach (var encoding in encodings) {
// 各種Encoding+BOMあり/なしでファイルに書き込む
File.WriteAllText(path, contents, encoding);
// 出力したファイル内容を16進形式で表示
Console.Write("{0,-60} => ", BitConverter.ToString(File.ReadAllBytes(path)));
using (var buffer = new MemoryStream()) {
// 出力したファイルをBomTransparentStreamで読み込む
using (var bomTransparentStream = new BomTransparentStream(path, discardBom: true /*BOMは除去する*/)) {
bomTransparentStream.CopyTo(buffer); // 内容をbufferにコピー
}
using (var fileStream = File.OpenWrite(path)) {
fileStream.SetLength(0L); // truncate
buffer.Position = 0L;
buffer.CopyTo(fileStream); // bufferの内容をコピー(ファイルに上書き)
}
}
// 出力したファイル内容を16進形式で表示
Console.WriteLine(BitConverter.ToString(File.ReadAllBytes(path)));
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E E6-97-A5-E6-9C-AC-E8-AA-9E => E6-97-A5-E6-9C-AC-E8-AA-9E FE-FF-65-E5-67-2C-8A-9E => 65-E5-67-2C-8A-9E 65-E5-67-2C-8A-9E => 65-E5-67-2C-8A-9E FF-FE-E5-65-2C-67-9E-8A => E5-65-2C-67-9E-8A E5-65-2C-67-9E-8A => E5-65-2C-67-9E-8A 00-00-FE-FF-00-00-65-E5-00-00-67-2C-00-00-8A-9E => 00-00-65-E5-00-00-67-2C-00-00-8A-9E 00-00-65-E5-00-00-67-2C-00-00-8A-9E => 00-00-65-E5-00-00-67-2C-00-00-8A-9E FF-FE-00-00-E5-65-00-00-2C-67-00-00-9E-8A-00-00 => E5-65-00-00-2C-67-00-00-9E-8A-00-00 E5-65-00-00-2C-67-00-00-9E-8A-00-00 => E5-65-00-00-2C-67-00-00-9E-8A-00-00 93-FA-96-7B-8C-EA => 93-FA-96-7B-8C-EA
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
var encodings = new Encoding[] {
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
new UnicodeEncoding(bigEndian: true, byteOrderMark: true),
new UnicodeEncoding(bigEndian: true, byteOrderMark: false),
new UnicodeEncoding(bigEndian: false, byteOrderMark: true),
new UnicodeEncoding(bigEndian: false, byteOrderMark: false),
new UTF32Encoding(bigEndian: true, byteOrderMark: true),
new UTF32Encoding(bigEndian: true, byteOrderMark: false),
new UTF32Encoding(bigEndian: false, byteOrderMark: true),
new UTF32Encoding(bigEndian: false, byteOrderMark: false),
shift_jis,
};
const string contents = "日本語";
const string path = "out.txt";
foreach (var encoding in encodings) {
// 各種Encoding+BOMあり/なしでファイルに書き込む
File.WriteAllText(path, contents, encoding);
using (var bomTransparentStream = new BomTransparentStream(path, discardBom: false /*BOMは維持する*/)) {
bomTransparentStream.ReadPreamble(); // Streamの先頭4バイトを読み込み、Encodingの自動判別を行う
var e = bomTransparentStream.DetectedEncoding; // 自動判別されたEncodingを取得する
Console.Write($"{e.WebName,12} {(e.Preamble.Length == 0 ? " " : "BOM")} : ");
// BinaryReaderでStreamの内容を読み込む
var reader = new BinaryReader(bomTransparentStream);
Console.WriteLine(BitConverter.ToString(reader.ReadBytes((int)bomTransparentStream.Length)));
}
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
utf-8 BOM : EF-BB-BF-E6-97-A5-E6-9C-AC-E8-AA-9E utf-8 : E6-97-A5-E6-9C-AC-E8-AA-9E utf-16BE BOM : FE-FF-65-E5-67-2C-8A-9E utf-8 : 65-E5-67-2C-8A-9E utf-16 BOM : FF-FE-E5-65-2C-67-9E-8A utf-8 : E5-65-2C-67-9E-8A utf-32BE BOM : 00-00-FE-FF-00-00-65-E5-00-00-67-2C-00-00-8A-9E utf-8 : 00-00-65-E5-00-00-67-2C-00-00-8A-9E utf-32 BOM : FF-FE-00-00-E5-65-00-00-2C-67-00-00-9E-8A-00-00 utf-8 : E5-65-00-00-2C-67-00-00-9E-8A-00-00 utf-8 : 93-FA-96-7B-8C-EA
using System;
using System.IO;
using System.Text;
class Sample {
static void Main()
{
var encodings = new Encoding[] {
new UTF8Encoding(encoderShouldEmitUTF8Identifier: true),
new UTF8Encoding(encoderShouldEmitUTF8Identifier: false),
new UnicodeEncoding(bigEndian: true, byteOrderMark: true),
new UnicodeEncoding(bigEndian: true, byteOrderMark: false),
new UnicodeEncoding(bigEndian: false, byteOrderMark: true),
new UnicodeEncoding(bigEndian: false, byteOrderMark: false),
new UTF32Encoding(bigEndian: true, byteOrderMark: true),
new UTF32Encoding(bigEndian: true, byteOrderMark: false),
new UTF32Encoding(bigEndian: false, byteOrderMark: true),
new UTF32Encoding(bigEndian: false, byteOrderMark: false),
shift_jis,
};
const string text = "日本語";
foreach (var encoding in encodings) {
// 各種Encoding+BOMあり/なしでMemoryStreamに書き込む
Console.Write($"{encoding.WebName,12} {(encoding.Preamble.Length == 0 ? " " : "BOM")} => ");
using var stream = new MemoryStream();
using (var writer = new StreamWriter(stream, encoding, leaveOpen: true)) {
writer.Write(text);
}
stream.Position = 0L;
// BOMによるEncodingの自動判別を有効にしてMemoryStreamから読み込む
using (var bomTransparentStream = new BomTransparentStream(stream, discardBom: true)) {
// Streamの先頭4バイトを読み込み、Encodingの自動判別を行う
bomTransparentStream.ReadPreamble();
// 自動判別されたEncodingを参照する
var e = bomTransparentStream.DetectedEncoding;
Console.Write($"{e.WebName,12} {(e.Preamble.Length == 0 ? " " : "BOM")} : ");
// 自動判別されたEncodingに基づいてStreamReaderを作成、内容を読み込む
using (var reader = new StreamReader(bomTransparentStream, encoding: bomTransparentStream.DetectedEncoding)) {
Console.WriteLine(reader.ReadToEnd());
}
}
}
}
static readonly Encoding shift_jis =
#if NETFRAMEWORK
Encoding.GetEncoding("shift_jis");
#else
// `dotnet add sample.csproj package System.Text.Encoding.CodePages`
CodePagesEncodingProvider.Instance.GetEncoding("shift_jis");
#endif
}
utf-8 BOM => utf-8 BOM : 日本語 utf-8 => utf-8 : 日本語 utf-16BE BOM => utf-16BE BOM : 日本語 utf-16BE => utf-8 : e�g,�� utf-16 BOM => utf-16 BOM : 日本語 utf-16 => utf-8 : �e,g�� utf-32BE BOM => utf-32BE BOM : 日本語 utf-32BE => utf-8 : e�g,�� utf-32 BOM => utf-32 BOM : 日本語 utf-32 => utf-8 : �e,g�� shift_jis => utf-8 : ���{�