StreamReader/Writerクラス

Streamクラスとテキスト

Streamクラス(正確にはその派生クラス)でのデータの読み書きはbyte型、byte型配列で行われます。
そのままではテキストデータが扱いづらいので、テキスト読み書き用のStreamReaderStreamWriterクラスが提供されています。

StreamReader/WriterクラスはStream派生クラスと組み合わせて使用します。
まずStreamのインスタンスを生成し、それをStreamReader/Writerのコンストラクターに渡します。
これでStreamReader/WriterはStreamとの間に入ってデータを適切に加工してくれます。


MemoryStream ms = new MemoryStream();
StreamWriter sw = new StreamWriter(ms);

//swを通してmsの読み書き
sw.WriteLine("あいうえお");

sw.Close();
ms.Close(); //必要ない

StreamReader/Writerクラスのストリームを閉じたとき、操作対象のストリームも同時に閉じるのでClose(Dispose)メソッドを二回呼ぶ必要はありません。
StreamReader/Writerを閉じても元のストリームを閉じないように動作を変更することも可能です。

このページではusingステートメントを利用したコードで記述します。


using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
    //swを通してmsの読み書き
    sw.WriteLine("あいうえお");
}
//この時点でmsとswはクローズ、破棄されている

共通の操作

BaseStreamプロパティ

StreamReader/WriterのインスタンスはBaseStreamプロパティで操作対象のストリームを取得することができます。


using (MemoryStream ms = new MemoryStream())
using (StreamReader sr = new StreamReader(ms))
{
    //BaseStreamプロパティはMemoryStreamを返すので
    //MemoryStreamのメソッドを使用可能
    sr.BaseStream.WriteByte((byte)1);
}

ただし、同じ操作がStreamReader/Writerクラスで提供されている場合、そちらを通して操作を行うことが推奨されます。
BaseStreamプロパティからベースとなるストリームの操作を行った場合、StreamReader/Writer側の操作は行われないので、必要な処理が行われない可能性があります。
ベースとなるストリームのインスタンス(上記コードの例では変数ms)を直接操作することも同様なので注意してください。

文字エンコーディング

文字エンコーディングはコンストラクターで指定します。
(後述)
指定しない場合のデフォルトの文字エンコーディングはUTF-8です。

StreamWriterではEncodingプロパティ、StreamReaderではCurrentEncodingプロパティで現在の文字エンコーディングを取得できます。
(読み取り専用プロパティ)


//デフォルトの文字コード(UTF-8)
using (MemoryStream ms = new MemoryStream())
using (StreamWriter sw = new StreamWriter(ms))
{
    Console.WriteLine(sw.Encoding.ToString());
}
Console.WriteLine();

string path = "test.txt";
//UTF-16で書き込み
using (StreamWriter sw = new StreamWriter(path, false, Encoding.Unicode))
{
    sw.WriteLine("ABC");
}

using (StreamReader sr = new StreamReader(path, true))
{
    Console.WriteLine(sr.CurrentEncoding.ToString());
    Console.WriteLine(sr.ReadLine());
    Console.WriteLine(sr.CurrentEncoding.ToString());
}
System.Text.UTF8Encoding

System.Text.UTF8Encoding+UTF8EncodingSealed
ABC
System.Text.UnicodeEncoding

StreamWriterはコンストラクターで指定した(あるいはデフォルトの)文字エンコーディングが使用されますが、StreamReaderはUnicode符号化(UTF-8やUTF-16など)が使用されている場合に、その種類を自動検出することができます。
(デフォルトでオン。オフにもできる)
自動検出は実際の読み取り(Read系メソッドの実行)の際に行われるので、途中でエンコードの形式が変わることがあります。

BOM無しのUnicodeや、Unicode以外の符号化形式の場合は自動検出はできません。

文字エンコーディングに関してはEncodingクラスを参照してください。

StreamWriterクラス

コンストラクター

オーバーロード 備考
StreamWriter(Stream stream)
StreamWriter(Stream stream, Encoding encoding)
StreamWriter(Stream stream, Encoding encoding, int bufferSize)
StreamWriter(Stream stream, Encoding? encoding = default, int bufferSize = -1, bool leaveOpen = false) .NET Framework4.5以降
StreamWriter(string path)
StreamWriter(string path, bool append)
StreamWriter(string path, bool append, Encoding encoding)
StreamWriter(string path, bool append, Encoding encoding, int bufferSize)
StreamWriter(string path, FileStreamOptions options) .NET6以降
StreamWriter(string path, Encoding encoding, FileStreamOptions options) .NET6以降

StreamWriterのコンストラクターは大別して「Stream派生クラスを開くもの」と「指定のパスにあるファイルを開くもの」があります。
ファイルを開くコンストラクターはFileStreamクラスをベースのストリームとして開きます。

以下は引数の説明です。

Stream stream
操作対象となるStream派生クラス
string path
ファイルをFileStreamクラスで開く
Encoding encoding
文字エンコーディングの指定
デフォルトはUTF-8
(Encodingクラスを参照)
int bufferSize
バッファサイズの指定
bool leaveOpen
trueを指定すると、StreamWriterを閉じてもベースストリームを閉じない
デフォルトはfalse
bool append
true:ファイルを追記モードで開く
false:ファイルを上書きモードで開く(デフォルト)
ファイルが存在しない場合は無効
(常に新規作成)
FileStreamOptions options
FileStreamの設定を指定する
(これの説明は省略します)

Write、WriteLineメソッド

文字列の書き込みにはWriteWriteLineメソッドを使用します。


using (StreamWriter sw = new StreamWriter("test.txt"))
{
    sw.Write('a');                
    sw.Write("あいうえお");
    sw.Write(123);

    sw.WriteLine();

    sw.WriteLine("あいうえお");
    sw.WriteLine(456.789);
    sw.WriteLine("{0} {1}", "かき", "くけこ");
}

これらはConsole入出力WriteWriteLineメソッドとほぼ同じです。
WriteLineメソッドは終端に改行文字が挿入されます。
復号書式も指定できます。

Flushメソッド

FlushメソッドはStreamWriter内のバッファを書き出してストリームに反映させます。


using (StreamWriter sw = new StreamWriter("test.bin"))
{
    sw.Write(123);
    sw.Write("abc");

    //FileStreamにデータを反映
    sw.Flush();
}
//StreamWriterをクローズすることでも
//ベースストリームに反映される

このメソッドは必ずStreamWriterから呼び出してください。
BaseStreamから呼び出すと正常にデータが反映されない可能性があります。

AutoFlushプロパティ

AutoFlushプロパティは、StreamWriterで書き込みを行った際に自動的にFlushメソッドが呼び出されるか否かを指定します。
(bool型)
デフォルトはfalseで、trueを指定するとWriteWriteLineメソッドを実行するたびにFlushされます。

AutoFlushを有効にすると書き込みのたびにFlushが発生するためパフォーマンスが落ちる可能性があります。
コンソール画面への出力など、書き込みが即時に反映される方が都合が良い場合に有効にします。

BOMなしで書き込む

StreamWriteクラスは、文字エンコーディングがUnicode(UTF-8、UTF-32など)で、ストリームの先頭から書き込みを行う場合に、BOMを書き込みます。
BOMなしで書き込む場合はUTF○○Encodingクラスのインスタンスを作成し、StreamWriteクラスのコンストラクターに指定します。


//BOMありのUTF-8エンコーディング
Encoding encUTF8BOM = new UTF8Encoding(true, false);

//BOMなしのUTF-8エンコーディング
Encoding encUTF8noBOM = new UTF8Encoding(false, false);

using (StreamWriter sw = new StreamWriter("utf8bom.txt", false, encUTF8BOM))
{
    sw.Write("abc");
}

using (StreamWriter sw = new StreamWriter("utf8nobom.txt", false, encUTF8noBOM))
{
    sw.Write("abc");
}

BOMを書き込むか否かはStreamWriterクラスではなくEncodingクラス側で設定されています。
Encodingクラスの文字エンコーディングプロパティ(UTF8プロパティなど)はBOMを使用する設定になっています。
BOMを使用しない設定のEncodingオブジェクトはnew UTF8Encoding(false);という形式で作成できます。
引数にはBOMを使用するか否かをbool型の値で設定します。

ちなみに、無効な文字が検出された場合に例外を発生させるか否かを第二引数にbool型で設定するオーバーロードもあります。
EncodingクラスのUTF8プロパティはnew UTF8Encoding(true, false);という形式のものと同一です。

UTF-8以外の文字エンコーディングには以下のものがあります。
オーバーロードが多少異なるので注意してください。
引数に角括弧がつけられているものは省略可能であることを意味します。

定数 説明
UTF7Encoding([bool allowOptionals]) allowOptionals: オプショナル文字はそのまま使用する
オプショナル文字は以下
!\"#$%&*;<=>@[]^_`{|}
(※UTF7EncodingクラスはBOMの有効/無効がなく、常に無効)
UTF8Encoding([bool encoderShouldEmitUTF8Identifier[, bool throwOnInvalidBytes]]); encoderShouldEmitUTF8Identifier: BOMを使用する
throwOnInvalidBytes: 無効な文字を検出した際に例外を送出する
UnicodeEncoding([bool bigEndian, bool byteOrderMark[, bool throwOnInvalidBytes]]);
(※UTF16のこと)
bigEndian: ビッグエンディアンを使用する
byteOrderMark: BOMを使用する
throwOnInvalidBytes: 無効な文字を検出した際に例外を送出する
UTF32Encoding([bool bigEndian, bool byteOrderMark[, bool throwOnInvalidBytes]]); bigEndian: ビッグエンディアンを使用する
byteOrderMark: BOMを使用する
throwOnInvalidBytes: 無効な文字を検出した際に例外を送出する

UTF○○EncodingクラスはEncodingクラスの派生クラスです。
UTF8EncodingクラスのインスタンスはUTF8Encoding型の変数に受けとっても良いですが、基底クラスであるEncoding型の変数でも受け取れます。

StreamReaderクラス

コンストラクター

オーバーロード 備考
StreamReader(Stream stream)
StreamReader(Stream stream, Encoding encoding)
StreamReader(Stream stream, bool detectEncodingFromByteOrderMarks)
StreamReader(Steam stream, Encoding encoding, bool detectEncodingFromByteOrderMarks)
StreamReader(Stream stream, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
StreamReader(Stream stream, Encoding? encoding = default, bool detectEncodingFromByteOrderMarks = true, int bufferSize = -1, bool leaveOpen = false) .NET Framework4.5以降
StreamReader(string path)
StreamReader(string path, Encoding encoding)
StreamReader(string path, bool detectEncodingFromByteOrderMarks)
StreamReader(string path, FileStreamOptions options) .NET6以降
StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks)
StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, int bufferSize)
StreamReader(string path, Encoding encoding, bool detectEncodingFromByteOrderMarks, FileStreamOptions options) .NET6以降

StreamReaderのコンストラクターも「Stream派生クラスを開くもの」と「指定のパスにあるファイルを開くもの」に大別されます。
ファイルを開くコンストラクターはFileStreamクラスをベースのストリームとして開きます。

以下は引数の説明です。

Stream stream
操作対象となるStream派生クラス
string path
ファイルをFileStreamクラスで開く
ファイルが存在しない場合は例外発生
Encoding encoding
文字エンコーディングの指定
デフォルトはUTF-8
(Encodingクラスを参照)
bool detectEncodingFromByteOrderMarks
trueを指定すると、UTF-8、UTF-16(ビッグエンディアン/リトルエンディアン)、UTF-32(リトルエンディアン/ビッグエンディアン)を自動検出する
BOM(バイトオーダーマーク)のないファイルおよびUnicode以外のファイルは自動検出できない
(encodingまたは標準のUTF-8が使用される)
デフォルトはtrue
int bufferSize
バッファサイズの指定
bool leaveOpen
trueを指定すると、StreamReaderを閉じてもベースストリームを閉じない
デフォルトはfalse
FileStreamOptions options
FileStreamの設定を指定する
(これの説明は省略します)

ファイルパスを指定する場合、ファイルが存在しないと例外(FileNotFoundException)が発生するので注意してください。

ReadLineメソッド

ReadLineメソッドはストリームから文字列を一行読み込みます。


using (StreamReader sr = new StreamReader("test.txt"))
{
    string line;
    while ((line = sr.ReadLine()) != null)
        Console.WriteLine(line);
}

このメソッドは現在位置から改行文字までを読み取り、string型の文字列として返します。
改行文字もストリームから読み取りますが、戻り値の文字列には改行文字は含まれません。

ストリームの終端に達した場合はnullを返します。
このコードはテキストファイルの内容全体を一行ずつ読み取り、コンソールに表示します。

ReadToEndメソッド

ReadToEndメソッドは、ストリームの終端までの文字列を全て読み取ります。


using (StreamReader sr = new StreamReader("test.txt"))
{
    string text = sr.ReadToEnd();
    Console.WriteLine(text);
}

このメソッドは現在位置がストリームの終端の場合は空文字("")を返します。

Readメソッド

Readメソッドはストリームから1文字を読み取ります。


int c = 0;
using (StreamReader sr = new StreamReader("test.txt"))
{
    while ((c = sr.Read()) >= 0)
        Console.Write((char)c);
}

読み取るのは「文字」ですが、戻り値はint型です。
ストリームの終端に達した場合は-1を返します。
(char型では-1を返せないため戻り値はint型)

配列に取得

Readメソッドは文字列をchar型の配列で受け取ることもできます。


char[] buf = new char[4];
int read = 0;
using (StreamReader sr = new StreamReader("test.txt"))
{
    while ((read = sr.Read(buf, 0, buf.Length)) > 0)
    {
        if(read != buf1.Length)
        {
            for (int i = 0; i < read; i++)
            {
                Console.Write(buf[i]);
            }
        }
        else
        {
            Console.Write(buf);
        }
    }
}

第一引数は文字を格納するchar型配列を指定します。
第二引数は配列のコピー開始開始番号を指定します。
第三引数は読み取る最大文字数を指定します。

戻り値は読み取られた文字数です。
途中でストリームの終端に達した場合などは第三引数で指定した文字数よりも少ない値が返されることがあります。
その場合、戻り値の値の分のみ配列の要素が上書きされます。
現在位置がストリームの終端である場合は0を返します。

サンプルコードでは、ストリームから4文字ずつを読み取ってコンソールに表示しています。
読み取りの途中でストリームの終端に達した場合は配列内に以前の内容が残っているので、戻り値の値の数だけ表示しています。

ReadBlockメソッド

ReadBlockメソッドはストリームから文字列を読み取りchar型配列に格納します。

このメソッドの引数や戻り値、および基本的な動作はReadメソッドのオーバーロード(配列に取得)と同じです。
Readメソッドは、例えばネットワークストリームなど遅延が発生する可能性のあるストリームに使用すると、ストリームの終端に達していなくても要求したよりも少ない文字数を返すことがあります。
ReadBlockメソッドは、その場合でも指定した文字数が取得できるまで待機します。
パソコン上のファイルやメモリへのストリームではこのような遅延はまず発生しません。

Peekメソッド

Peekメソッドは、ストリームの位置を変更せずに1文字を読み取ります。


using (StreamReader sr = new StreamReader("test.txt"))
{
    while (sr.Peek() >= 0)
        Console.Write((char)sr.Read());
}

このメソッドはReadメソッドの1文字読み取りに似ていますが、ストリームの位置を移動させません。
主にストリームの現在位置が終端であるかをチェックするために使用します。
戻り値はint型で、ストリームの終端である場合は-1を返します。

DiscardBufferedDataメソッド

DiscardBufferedDataメソッドは、内部バッファを破棄します。

StreamReaderはSeekメソッドやPositionプロパティによる位置変更をサポートしていません。
BaseStreamプロパティを経由してベースストリームの位置変更をすることは可能ですが、そのまま読み取りを行うとベースストリームの位置とズレが生じます。
これはStreamReaderは内部バッファを使用してデータを読み取るためです。

ベースストリームの位置を変更する場合はDiscardBufferedDataメソッドでバッファをクリアします。


using (MemoryStream ms = new MemoryStream())
{
    //UTF-8
    //バッファサイズはデフォルト
    //StreamWriterを閉じてもベースストリームを閉じない
    using (StreamWriter sw = new StreamWriter(ms, null, -1, true))
    {
        sw.Write("0123456789");
    }
    ms.Position = 0;

    //StreamWriterと同じ設定
    using (StreamReader sr = new StreamReader(ms, null, true, -1, true))
    {
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write(" ");

        //ベースストリームを先頭に戻す
        sr.BaseStream.Position = 0;

        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.WriteLine();
    }

    ms.Position = 0;

    //StreamWriterと同じ設定
    using (StreamReader sr = new StreamReader(ms, null, true, -1, true))
    {
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write(" ");

        //内部バッファをクリア
        sr.DiscardBufferedData();
        //ベースストリームを先頭に戻す
        sr.BaseStream.Position = 0;

        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.Write((char)sr.Read());
        Console.WriteLine();
    }
}
012 345
012 012

ふたつのStreamReaderは、同じデータに対して読み取りを行っています。
どちらも三文字を読み取った後にベースストリームの位置を先頭に戻してから再び三文字を読み取っています。
一番目の位置移動をしただけの方は移動が反映されていませんが、二番目のバッファをクリアした方は意図通り先頭から読み取りが行われます。

ただし日本語などのマルチバイト文字は一文字に数バイトを使用するため、ベースストリームで位置変更を行うと一文字の途中の位置にセットされる可能性があります。
この場合は不正な文字となるため読み取りに失敗します。

EndOfStreamプロパティ

EndOfStreamプロパティはストリームの現在位置が終端ならば真を返します。


using (StreamReader sr = new StreamReader("test.txt"))
{
    Console.WriteLine(sr.EndOfStream);
    sr.ReadToEnd();
    Console.WriteLine(sr.EndOfStream);
}
False
True