C# FileStreamでファイルを読み込む方法|Read・ReadAsync・StreamReaderの使い分けまで徹底解説

はじめに

C#でファイルを読み込む方法には、FileStreamStreamReaderFile.ReadAllTextFile.ReadAllBytesなど複数の選択肢があります。その中でもFileStreamは、ファイルをバイト単位で扱える基本的なクラスであり、画像、PDF、CSV、ログファイル、独自形式のデータなど、さまざまなファイル読み込みに対応できます。

特に「C# FileStream Read」というキーワードで調べている方は、次のような疑問を持っているのではないでしょうか。

FileStream.Readの使い方が分からない」
ReadReadAsyncの違いを知りたい」
「テキストファイルならStreamReaderのほうがよいのか知りたい」
「大きなファイルを安全に読み込みたい」
「ファイル読み込み時のエラー対処も知っておきたい」

この記事では、C#のFileStreamを使ってファイルを読み込む基本から、ReadReadAsyncStreamReaderの使い分けまで、サンプルコードを交えて詳しく解説します。

1. C#のFileStreamとは?ファイル読み込みの基本を押さえる

FileStreamは、C#でファイルをストリームとして扱うためのクラスです。ストリームとは、データを連続した流れとして読み書きする仕組みのことです。

ファイル全体を一度に読み込むのではなく、必要な分だけ少しずつ読み込めるため、大きなファイルを扱う場合や、バイナリデータを細かく制御したい場合に役立ちます。

1-1. FileStreamでできること

FileStreamを使うと、主に次のような処理ができます。

ファイルをバイト配列として読み込む、ファイルへバイトデータを書き込む、ファイルの一部だけを読み込む、現在の読み込み位置を移動する、画像やPDFなどのバイナリファイルを扱う、大容量ファイルを分割して処理する、といった操作が可能です。

たとえば、次のようにFileStreamを作成すると、指定したファイルを読み取り専用で開けます。

C#
using FileStream fs = new FileStream(
"sample.dat",
FileMode.Open,
FileAccess.Read
);

このfsに対してReadメソッドを使うことで、ファイルの内容をバイト配列に読み込めます。

1-2. FileStreamが必要になる代表的なケース

FileStreamが必要になる代表的なケースは、ファイルをバイト単位で扱いたい場合です。

たとえば、画像ファイル、音声ファイル、動画ファイル、PDFファイル、圧縮ファイル、独自フォーマットのファイルなどは、基本的にテキストではなくバイナリデータとして扱います。このようなファイルを読み込む場合、StreamReaderではなくFileStreamを使うのが自然です。

また、数GB単位の大きなログファイルやデータファイルを処理する場合も、FileStreamが有効です。File.ReadAllBytesFile.ReadAllTextのようにファイル全体を一括でメモリに読み込むと、ファイルサイズによってはメモリ不足になる可能性があります。

FileStreamなら、一定サイズのバッファに分割して読み込めるため、メモリ使用量を抑えながら処理できます。

1-3. FileStreamとStreamReaderの違い

FileStreamStreamReaderは、どちらもファイル読み込みに使えますが、役割が異なります。

FileStreamは、ファイルをバイト単位で読み書きするためのクラスです。読み込まれるデータはbyte[]です。そのため、画像、PDF、ZIP、独自バイナリ形式などを扱う場合に適しています。

一方、StreamReaderは、テキストを文字単位または行単位で読み込むためのクラスです。読み込まれるデータはstringです。テキストファイル、CSVファイル、ログファイル、JSONファイル、XMLファイルなどを文字列として読みたい場合に適しています。

簡単に言うと、バイナリファイルを読むならFileStream、テキストファイルを読むならStreamReaderを使うのが基本です。

1-4. FileStreamで読み込めるファイルの種類

FileStreamは、ファイルの中身をバイト列として扱うため、基本的にはどのような種類のファイルでも読み込めます。

たとえば、次のようなファイルを読み込めます。

.txt
.csv
.log
.json
.xml
.jpg
.png
.pdf
.zip
.dat
.bin

ただし、読み込めることと、内容を正しく解釈できることは別です。FileStreamでPDFファイルをバイト配列として読み込むことはできますが、そのPDFの本文を抽出するにはPDF解析用のライブラリが必要です。

同様に、画像ファイルを読み込めても、画像として表示したり加工したりするには、画像処理用のAPIやライブラリが必要になります。

2. FileStreamでファイルを読み込む基本手順

FileStreamでファイルを読み込む基本的な流れは、ファイルを開く、バッファを用意する、Readで読み込む、読み込んだデータを処理する、最後にストリームを閉じる、という手順です。

2-1. FileStreamを使った読み込みの流れ

FileStreamによる読み込みは、次のような流れで行います。

C#
using FileStream fs = new FileStream(
"sample.txt",
FileMode.Open,
FileAccess.Read
);

byte[] buffer = new byte[1024];

int bytesRead = fs.Read(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");

このコードでは、sample.txtを読み取り専用で開き、最大1024バイトをbufferに読み込んでいます。

Readメソッドは、実際に読み込んだバイト数を戻り値として返します。指定したサイズ分が必ず読み込まれるとは限らないため、戻り値の確認が重要です。

2-2. FileMode・FileAccess・FileShareの指定方法

FileStreamを使うときによく指定するのが、FileModeFileAccessFileShareです。

FileModeは、ファイルをどのように開くかを指定します。読み込みでよく使うのはFileMode.Openです。

C#
FileMode.Open

これは、既存のファイルを開く指定です。ファイルが存在しない場合は例外が発生します。

FileAccessは、ファイルに対して何を行うかを指定します。読み込みだけならFileAccess.Readを使います。

C#
FileAccess.Read

FileShareは、他のプロセスが同じファイルへアクセスできるかを指定します。

C#
using FileStream fs = new FileStream(
"sample.txt",
FileMode.Open,
FileAccess.Read,
FileShare.Read
);

FileShare.Readを指定すると、他のプロセスも読み取り目的で同じファイルを開けます。ログファイルなど、他のアプリケーションが使用している可能性があるファイルを読む場合は、FileShareの指定が重要になります。

2-3. using文でFileStreamを安全に閉じる

FileStreamは、ファイルハンドルというOSのリソースを使用します。そのため、使い終わったら必ず閉じる必要があります。

C#では、using文またはusing宣言を使うことで、処理が終わったときに自動的にDisposeが呼ばれ、ファイルが閉じられます。

C#
using (FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read))
{
byte[] buffer = new byte[1024];
int bytesRead = fs.Read(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");
}

C# 8.0以降では、次のようにusing宣言も使えます。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[1024];
int bytesRead = fs.Read(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");

usingを使わずにFileStreamを閉じ忘れると、ファイルがロックされたままになったり、他の処理からファイルを開けなくなったりすることがあります。

2-4. 読み込み前に確認すべきファイルパスの扱い

ファイル読み込みでよくあるトラブルのひとつが、ファイルパスの指定ミスです。

C#では、相対パスと絶対パスのどちらも指定できます。

C#
string relativePath = "data/sample.txt";
string absolutePath = @"C:\work\data\sample.txt";

Windowsのパスでは\を使いますが、C#の文字列では\がエスケープ文字として扱われるため、次のように書く必要があります。

C#
string path = "C:\\work\\data\\sample.txt";

または、逐語的文字列リテラルを使って次のように書けます。

C#
string path = @"C:\work\data\sample.txt";

ファイルが存在するかを事前に確認する場合は、File.Existsを使います。

C#
string path = "sample.txt";

if (!File.Exists(path))
{
Console.WriteLine("ファイルが存在しません。");
return;
}

using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

ただし、File.Existsで存在確認をしても、その直後に別のプロセスがファイルを削除する可能性はあります。そのため、実際のファイル読み込み処理では例外処理も必要です。

3. FileStream.Readで同期的にファイルを読み込む方法

FileStream.Readは、ファイルからデータを同期的に読み込むメソッドです。同期処理とは、読み込みが完了するまで次の処理に進まない方式です。

コンソールアプリや小さなファイルの読み込みでは、Readを使った同期処理でも十分な場合が多いです。

3-1. Readメソッドの基本構文

代表的なReadメソッドの構文は次のとおりです。

C#
int bytesRead = fileStream.Read(buffer, offset, count);

各引数の意味は次のとおりです。

buffer : 読み込んだデータを格納するbyte配列
offset : bufferのどの位置から格納するか
count : 最大何バイト読み込むか

戻り値は、実際に読み込まれたバイト数です。ファイルの終端に達した場合は0が返ります。

たとえば、次のコードでは最大1024バイトを読み込みます。

C#
byte[] buffer = new byte[1024];
int bytesRead = fs.Read(buffer, 0, buffer.Length);

3-2. byte配列にファイル内容を読み込むサンプルコード

次のサンプルは、FileStream.Readを使ってファイルの先頭から最大1024バイトを読み込む例です。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = "sample.dat";

using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[1024];

int bytesRead = fs.Read(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");

for (int i = 0; i < bytesRead; i++)
{
Console.Write($"{buffer[i]:X2} ");
}
}
}

このコードでは、読み込んだバイトを16進数で表示しています。画像やPDFなどのバイナリファイルを確認するときに使える基本的な形です。

3-3. Readの戻り値の意味

Readメソッドの戻り値は、実際に読み込んだバイト数です。

たとえば、1024バイトのバッファを指定しても、必ず1024バイト読み込まれるとは限りません。ファイルの残りサイズが500バイトであれば、戻り値は500になります。ファイルの終端に達している場合は、戻り値は0になります。

C#
int bytesRead = fs.Read(buffer, 0, buffer.Length);

if (bytesRead == 0)
{
Console.WriteLine("ファイルの終端に達しました。");
}
else
{
Console.WriteLine($"{bytesRead} バイト読み込みました。");
}

Readを使うときは、戻り値を無視してはいけません。戻り値を確認せずにバッファ全体を処理すると、前回読み込んだデータや未使用領域まで処理してしまう可能性があります。

3-4. ループ処理で最後まで読み込む方法

ファイル全体を読み込む場合は、Readをループで呼び出します。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = "sample.dat";

using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
Console.WriteLine($"{bytesRead} バイト読み込みました。");

// ここでbufferの先頭bytesReadバイト分だけを処理する
// buffer.LengthではなくbytesReadを使うことが重要
}

Console.WriteLine("読み込みが完了しました。");
}
}

このように、Readの戻り値が0になるまでループすることで、ファイルの最後まで読み込めます。

重要なのは、処理対象をbuffer.LengthではなくbytesReadにすることです。最後の読み込みでは、バッファサイズより少ないバイト数しか読み込まれないことがあるためです。

3-5. Readを使うときの注意点

FileStream.Readを使うときは、いくつか注意点があります。

まず、Readは同期処理です。ファイルの読み込みが完了するまで現在のスレッドが待機します。コンソールアプリでは問題になりにくいですが、UIアプリで大きなファイルを読み込むと画面が固まったように見えることがあります。

次に、Readは指定したバイト数を必ず読み込むとは限りません。戻り値を確認しながら処理する必要があります。

また、テキストファイルを文字列として扱いたい場合は、FileStreamだけで処理するよりもStreamReaderを使うほうが簡単です。FileStreamで読み込んだbyte[]を文字列に変換することもできますが、文字コードの扱いを自分で考える必要があります。

C#
string text = System.Text.Encoding.UTF8.GetString(buffer, 0, bytesRead);

テキストファイルであれば、後述するStreamReaderを使うほうが自然です。

4. FileStream.ReadAsyncで非同期にファイルを読み込む方法

FileStream.ReadAsyncは、ファイルからデータを非同期に読み込むためのメソッドです。非同期処理を使うと、ファイル読み込み中にスレッドを占有しにくくなります。

特に、UIアプリやWebアプリでファイルI/Oを扱う場合は、ReadAsyncの利用を検討する価値があります。

4-1. ReadAsyncメソッドを使うメリット

ReadAsyncを使う最大のメリットは、ファイル読み込み中にアプリケーションの応答性を保ちやすいことです。

たとえば、デスクトップアプリで大きなファイルを同期的に読み込むと、読み込みが終わるまでUIスレッドがブロックされ、画面が反応しなくなることがあります。ReadAsyncを使えば、読み込み完了を待っている間もUIスレッドを解放しやすくなります。

Webアプリでも、ファイル読み込み中にスレッドを占有し続けることを避けられるため、同時リクエストを処理しやすくなります。

ただし、非同期にすれば必ず処理速度が速くなるわけではありません。ReadAsyncは主に応答性やスケーラビリティを改善するための手段です。

4-2. async/awaitを使った基本サンプルコード

次のコードは、FileStream.ReadAsyncを使ってファイルを非同期で読み込む基本例です。

C#
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
string path = "sample.dat";

using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
useAsync: true
);

byte[] buffer = new byte[4096];

int bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");
}
}

ポイントは、Mainメソッドをasync Task Mainにしていること、awaitReadAsyncの完了を待っていること、FileStream作成時にuseAsync: trueを指定していることです。

最近の.NETでは、Memory<byte>を受け取るオーバーロードもよく使われます。

C#
int bytesRead = await fs.ReadAsync(buffer);

この書き方では、buffer全体に対して非同期読み込みを行います。

4-3. 大きなファイルを非同期で読み込む方法

大きなファイルを非同期で読み込む場合も、基本はループ処理です。

C#
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
string path = "largefile.dat";

using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 81920,
useAsync: true
);

byte[] buffer = new byte[81920];
int bytesRead;

while ((bytesRead = await fs.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
Console.WriteLine($"{bytesRead} バイト読み込みました。");

// bufferの先頭bytesReadバイト分だけを処理する
}

Console.WriteLine("非同期読み込みが完了しました。");
}
}

バッファサイズは用途によって調整できます。小さすぎると読み込み回数が増え、大きすぎるとメモリ使用量が増えます。一般的には、数KBから数十KB程度のバッファを使うことが多いです。

4-4. CancellationTokenを使ったキャンセル処理

非同期処理では、キャンセル処理も重要です。ReadAsyncにはCancellationTokenを渡せるオーバーロードがあります。

C#
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
string path = "largefile.dat";

using CancellationTokenSource cts = new CancellationTokenSource();

try
{
await ReadFileAsync(path, cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("読み込みがキャンセルされました。");
}
}

static async Task ReadFileAsync(string path, CancellationToken cancellationToken)
{
using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 81920,
useAsync: true
);

byte[] buffer = new byte[81920];
int bytesRead;

while ((bytesRead = await fs.ReadAsync(buffer, cancellationToken)) > 0)
{
cancellationToken.ThrowIfCancellationRequested();

Console.WriteLine($"{bytesRead} バイト読み込みました。");
}
}
}

CancellationTokenを使うことで、ユーザーがキャンセルボタンを押した場合や、一定時間で処理を中断したい場合に対応できます。

4-5. ReadAsyncを使うべきケースと使わなくてよいケース

ReadAsyncを使うべきケースは、UIアプリで画面の応答性を保ちたい場合、Webアプリでスレッドを効率よく使いたい場合、大容量ファイルを読み込む場合、ネットワークドライブや外部ストレージなど待ち時間が発生しやすい場所から読み込む場合です。

一方で、小さな設定ファイルをコンソールアプリで読み込むだけなら、同期処理のReadFile.ReadAllTextでも十分です。

非同期処理は便利ですが、コードが少し複雑になります。処理の規模やアプリケーションの種類に応じて選びましょう。

5. StreamReaderでテキストファイルを読み込む方法

テキストファイルを読み込む場合は、FileStreamよりもStreamReaderのほうが扱いやすいことが多いです。

StreamReaderは、バイト列を文字列に変換しながら読み込んでくれるクラスです。文字コードを指定したり、1行ずつ読み込んだりできるため、ログファイル、CSVファイル、設定ファイルなどの読み込みに向いています。

5-1. StreamReaderを使うべき場面

StreamReaderを使うべき場面は、ファイルの内容を文字列として扱いたい場合です。

たとえば、次のようなファイルを読む場合はStreamReaderが適しています。

テキストファイル
CSVファイル
ログファイル
JSONファイル
XMLファイル
設定ファイル

これらのファイルは最終的に文字列として扱うことが多いため、FileStream.Readでバイト配列として読み込んでから文字列に変換するよりも、StreamReaderを使ったほうがコードが簡潔になります。

5-2. ReadToEndでファイル全体を読み込む

ファイル全体を一度に文字列として読み込みたい場合は、ReadToEndを使います。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.txt";

using StreamReader reader = new StreamReader(path, Encoding.UTF8);

string text = reader.ReadToEnd();

Console.WriteLine(text);
}
}

ReadToEndはシンプルですが、ファイル全体をメモリに読み込みます。そのため、大容量ファイルには向いていません。

小さなテキストファイルや設定ファイルを読み込む場合に使うとよいでしょう。

5-3. ReadLineで1行ずつ読み込む

ログファイルやCSVファイルのように、1行ずつ処理したい場合はReadLineを使います。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.log";

using StreamReader reader = new StreamReader(path, Encoding.UTF8);

string? line;

while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
}

ReadLineは、ファイルを1行ずつ読み込むため、ファイル全体を一度にメモリに載せる必要がありません。大きなログファイルを処理する場合にも使いやすい方法です。

5-4. 文字コードを指定して読み込む方法

テキストファイルを読み込むときは、文字コードの指定が重要です。

UTF-8のファイルを読み込む場合は、次のように指定します。

C#
using StreamReader reader = new StreamReader("sample.txt", Encoding.UTF8);

Shift_JISのファイルを読み込む場合は、環境によってはエンコーディングプロバイダーの登録が必要です。

C#
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

Encoding sjis = Encoding.GetEncoding("shift_jis");

using StreamReader reader = new StreamReader("sample.txt", sjis);

string text = reader.ReadToEnd();

Console.WriteLine(text);

文字化けが発生する場合は、ファイルの実際の文字コードと、読み込み時に指定している文字コードが一致しているか確認しましょう。

5-5. FileStreamとStreamReaderを組み合わせる方法

StreamReaderは、FileStreamと組み合わせて使うこともできます。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.txt";

using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read
);

using StreamReader reader = new StreamReader(fs, Encoding.UTF8);

string text = reader.ReadToEnd();

Console.WriteLine(text);
}
}

この方法を使うと、FileStream側でFileModeFileAccessFileShareなどを細かく制御しつつ、テキストの読み込み自体はStreamReaderに任せられます。

ファイル共有設定や非同期設定を明示したい場合に便利です。

6. FileStream・Read・ReadAsync・StreamReaderの使い分け

C#でファイルを読み込むときは、目的に応じて適切な方法を選ぶことが大切です。

すべてのケースでFileStreamを使う必要はありません。逆に、すべてをFile.ReadAllTextで済ませるのも適切ではありません。

6-1. バイナリファイルを読むならFileStream

画像、PDF、ZIP、音声、動画、独自形式のデータなど、バイナリファイルを読み込む場合はFileStreamが基本です。

C#
using FileStream fs = new FileStream("sample.pdf", FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[4096];
int bytesRead = fs.Read(buffer, 0, buffer.Length);

バイナリファイルでは、文字コードを意識する必要はありません。ファイルの内容をbyte[]として読み込み、必要に応じて解析やコピー、送信などを行います。

6-2. テキストファイルを読むならStreamReader

テキストファイルを文字列として読み込みたい場合は、StreamReaderが適しています。

C#
using StreamReader reader = new StreamReader("sample.txt", Encoding.UTF8);

string text = reader.ReadToEnd();

1行ずつ処理したい場合もStreamReaderが便利です。

C#
string? line;

while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}

CSV、ログ、JSON、XMLなど、テキストとして扱うファイルでは、まずStreamReaderを検討しましょう。

6-3. UIアプリやWebアプリではReadAsyncを検討する

Windows Forms、WPF、MAUIなどのUIアプリでは、同期的なファイル読み込みによって画面が固まることがあります。

このような場合は、ReadAsyncStreamReader.ReadToEndAsyncを検討します。

C#
using FileStream fs = new FileStream(
"sample.dat",
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize: 4096,
useAsync: true
);

byte[] buffer = new byte[4096];
int bytesRead = await fs.ReadAsync(buffer);

ASP.NET CoreなどのWebアプリでも、I/O待ちが発生する処理では非同期APIを使うことで、スレッドを効率よく使いやすくなります。

6-4. 小さなファイルならFile.ReadAllTextも選択肢

小さなテキストファイルを読み込むだけなら、File.ReadAllTextが最も簡単です。

C#
string text = File.ReadAllText("sample.txt", Encoding.UTF8);

小さなバイナリファイルなら、File.ReadAllBytesも使えます。

C#
byte[] data = File.ReadAllBytes("sample.dat");

ただし、これらのメソッドはファイル全体を一度にメモリに読み込みます。大容量ファイルでは使用を避け、FileStreamStreamReaderで分割して読み込むほうが安全です。

6-5. 目的別の使い分け早見表

目的おすすめの方法
テキストファイルをすべて読み込みたいFile.ReadAllTextまたはStreamReader.ReadToEnd
テキストファイルを1行ずつ読みたいStreamReader.ReadLine
バイナリファイルを読みたいFileStream.Read
大容量ファイルを分割して読みたいFileStream.ReadまたはStreamReader.ReadLine
非同期で読み込みたいFileStream.ReadAsyncまたはStreamReaderの非同期メソッド
小さなバイナリファイルを一括で読みたいFile.ReadAllBytes
ファイル共有設定を細かく指定したいFileStream

7. 実践サンプル:用途別のファイル読み込みコード

ここからは、実際によく使うファイル読み込みパターンを用途別に紹介します。

7-1. テキストファイルをすべて読み込む

小さなテキストファイルをすべて読み込む場合は、File.ReadAllTextが簡単です。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.txt";

string text = File.ReadAllText(path, Encoding.UTF8);

Console.WriteLine(text);
}
}

StreamReaderを使う場合は次のように書けます。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.txt";

using StreamReader reader = new StreamReader(path, Encoding.UTF8);

string text = reader.ReadToEnd();

Console.WriteLine(text);
}
}

ファイルサイズが小さく、文字列としてまとめて扱いたい場合に適しています。

7-2. テキストファイルを1行ずつ読み込む

ログファイルやCSVファイルのように、1行ずつ処理したい場合はReadLineを使います。

C#
using System;
using System.IO;
using System.Text;

class Program
{
static void Main()
{
string path = "sample.csv";

using StreamReader reader = new StreamReader(path, Encoding.UTF8);

string? line;

while ((line = reader.ReadLine()) != null)
{
string[] columns = line.Split(',');

Console.WriteLine($"1列目: {columns[0]}");
}
}
}

CSVを厳密に処理する場合は、カンマを含む値やダブルクォートの扱いが必要になるため、CSV専用ライブラリの利用も検討しましょう。

7-3. 画像やPDFなどのバイナリファイルを読み込む

画像やPDFなどのバイナリファイルを読み込む場合は、FileStreamを使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = "sample.pdf";

using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[4096];
int bytesRead;

while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// bufferの先頭bytesReadバイト分を処理する
Console.WriteLine($"{bytesRead} バイト読み込みました。");
}
}
}

ファイル全体をバイト配列に読み込みたいだけなら、サイズが小さい場合に限りFile.ReadAllBytesも使えます。

C#
byte[] data = File.ReadAllBytes("sample.pdf");

ただし、大きなPDFや画像を一括で読み込むとメモリを多く消費するため、必要に応じて分割読み込みを使いましょう。

7-4. 大容量ファイルを分割して読み込む

大容量ファイルは、一定サイズのバッファで分割して読み込むのが基本です。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = "largefile.bin";

const int bufferSize = 81920;

using FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[bufferSize];
int bytesRead;
long totalBytes = 0;

while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
totalBytes += bytesRead;

// bufferの先頭bytesReadバイト分だけを処理する
Console.WriteLine($"読み込み済み: {totalBytes} バイト");
}

Console.WriteLine("読み込み完了");
}
}

この方法なら、ファイルサイズが大きくても、メモリ使用量を一定に保ちやすくなります。

7-5. 非同期でファイルを読み込む

非同期でファイルを読み込む場合は、ReadAsyncを使います。

C#
using System;
using System.IO;
using System.Threading.Tasks;

class Program
{
static async Task Main()
{
string path = "largefile.bin";

const int bufferSize = 81920;

using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.Read,
bufferSize,
useAsync: true
);

byte[] buffer = new byte[bufferSize];
int bytesRead;
long totalBytes = 0;

while ((bytesRead = await fs.ReadAsync(buffer)) > 0)
{
totalBytes += bytesRead;

Console.WriteLine($"読み込み済み: {totalBytes} バイト");
}

Console.WriteLine("非同期読み込み完了");
}
}

UIアプリやWebアプリでは、このような非同期読み込みを使うことで、処理待ちによる応答性低下を抑えやすくなります。

8. FileStreamでファイル読み込み時によくあるエラーと対処法

ファイル読み込みでは、ファイルが存在しない、権限がない、他のプロセスが使用中、文字コードが違うなど、さまざまなエラーが発生します。

ここでは、FileStreamStreamReaderでファイルを読み込むときによくある例外と対処法を紹介します。

8-1. FileNotFoundExceptionの原因と対処法

FileNotFoundExceptionは、指定したファイルが存在しない場合に発生します。

C#
try
{
using FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}

主な原因は、ファイルパスの指定ミス、相対パスの基準ディレクトリの誤解、ファイル名のタイプミス、ファイルが削除または移動されていることです。

対処法としては、絶対パスで確認する、Directory.GetCurrentDirectory()で現在の作業ディレクトリを確認する、File.Existsで存在確認する、例外処理を書く、といった方法があります。

C#
Console.WriteLine(Directory.GetCurrentDirectory());

相対パスを使う場合は、実行時のカレントディレクトリがどこになっているかを確認しましょう。

8-2. UnauthorizedAccessExceptionの原因と対処法

UnauthorizedAccessExceptionは、ファイルまたはフォルダへのアクセス権限がない場合に発生します。

C#
try
{
using FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read);
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルへのアクセス権限がありません。");
}

主な原因は、読み取り権限がない、フォルダをファイルとして開こうとしている、管理者権限が必要な場所にアクセスしている、別のプロセスによってアクセスが制限されていることです。

対処法としては、ファイルの権限を確認する、読み取り可能なフォルダにファイルを置く、フォルダではなくファイルパスを指定しているか確認する、必要に応じて実行権限を見直すことが挙げられます。

特に、C:\WindowsProgram Files配下などは権限の問題が起こりやすいため注意しましょう。

8-3. IOExceptionの原因と対処法

IOExceptionは、入出力処理全般で発生する例外です。ファイルが他のプロセスにロックされている場合や、ディスクの問題がある場合などに発生します。

C#
try
{
using FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read);
}
catch (IOException ex)
{
Console.WriteLine($"I/Oエラーが発生しました: {ex.Message}");
}

主な原因は、ファイルが他のプロセスで使用中、ネットワークドライブへの接続問題、ディスクエラー、ファイル共有設定の不一致などです。

他のプロセスがファイルを使用している可能性がある場合は、FileShareを調整します。

C#
using FileStream fs = new FileStream(
"sample.log",
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite
);

ログファイルのように、別のアプリケーションが書き込み中のファイルを読みたい場合は、FileShare.ReadWriteを指定することで読み込める場合があります。

8-4. 文字化けが発生する原因と対処法

文字化けは、ファイルの文字コードと読み込み時に指定した文字コードが一致していない場合に発生します。

たとえば、Shift_JISで保存されたファイルをUTF-8として読み込むと、日本語が文字化けすることがあります。

C#
using StreamReader reader = new StreamReader("sample.txt", Encoding.UTF8);

Shift_JISのファイルを読む場合は、次のように文字コードを指定します。

C#
using System.Text;

Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

Encoding sjis = Encoding.GetEncoding("shift_jis");

using StreamReader reader = new StreamReader("sample.txt", sjis);

string text = reader.ReadToEnd();

対処法は、ファイルの文字コードを確認する、読み込み時に正しいEncodingを指定する、可能であればファイルをUTF-8に統一することです。

特に日本語環境では、古いCSVファイルや業務システムの出力ファイルがShift_JISになっていることがあります。

8-5. ファイルがロックされている場合の対処法

ファイルが他のプロセスにロックされていると、IOExceptionが発生することがあります。

たとえば、別のアプリケーションが排他的にファイルを開いている場合、こちらから読み込めないことがあります。

読み込み側で共有設定を指定する例は次のとおりです。

C#
using FileStream fs = new FileStream(
"sample.log",
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite
);

ただし、相手側のプロセスが共有を許可していない場合、読み込み側でFileShare.ReadWriteを指定しても開けないことがあります。

対処法としては、ファイルを使用しているアプリケーションを閉じる、書き込み側のFileShare設定を見直す、一時ファイルにコピーしてから読み込む、一定時間後にリトライする、といった方法があります。

リトライ処理の例は次のとおりです。

C#
using System;
using System.IO;
using System.Threading;

class Program
{
static void Main()
{
string path = "sample.log";

for (int i = 0; i < 3; i++)
{
try
{
using FileStream fs = new FileStream(
path,
FileMode.Open,
FileAccess.Read,
FileShare.ReadWrite
);

Console.WriteLine("ファイルを開けました。");
break;
}
catch (IOException)
{
Console.WriteLine("ファイルが使用中です。少し待って再試行します。");
Thread.Sleep(1000);
}
}
}
}

9. FileStreamでファイルを読み込むときのベストプラクティス

FileStreamを安全かつ効率的に使うには、いくつかのポイントがあります。

9-1. usingまたはusing宣言でリソースを解放する

FileStreamを使うときは、必ずusingまたはusing宣言を使いましょう。

C#
using FileStream fs = new FileStream("sample.dat", FileMode.Open, FileAccess.Read);

または次のように書きます。

C#
using (FileStream fs = new FileStream("sample.dat", FileMode.Open, FileAccess.Read))
{
// 読み込み処理
}

これにより、例外が発生した場合でもFileStreamが適切に破棄され、ファイルが閉じられます。

9-2. 大容量ファイルは一括読み込みを避ける

大容量ファイルをFile.ReadAllBytesFile.ReadAllTextで一括読み込みすると、大量のメモリを消費します。

大きなファイルは、FileStream.ReadまたはReadAsyncで分割して読み込みましょう。

C#
byte[] buffer = new byte[81920];
int bytesRead;

while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// bytesRead分だけ処理する
}

ファイルサイズが大きい場合ほど、ストリーム処理のメリットが大きくなります。

9-3. 例外処理を適切に書く

ファイル読み込みでは、ファイルが存在しない、権限がない、ロックされているなどの問題が起こり得ます。

必要に応じて、具体的な例外を捕捉しましょう。

C#
try
{
using FileStream fs = new FileStream("sample.dat", FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[4096];
int bytesRead = fs.Read(buffer, 0, buffer.Length);

Console.WriteLine($"{bytesRead} バイト読み込みました。");
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("アクセス権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"I/Oエラーが発生しました: {ex.Message}");
}

すべての例外をまとめてExceptionで捕捉することもできますが、原因ごとに適切な対処をしたい場合は、具体的な例外を分けて扱うほうが分かりやすくなります。

9-4. 文字コードを明示する

テキストファイルを読み込む場合は、文字コードを明示するのがおすすめです。

C#
using StreamReader reader = new StreamReader("sample.txt", Encoding.UTF8);

文字コードを明示しておくことで、環境差やファイル形式の違いによる文字化けを防ぎやすくなります。

日本語を含むファイルでは、UTF-8なのかShift_JISなのかを確認しましょう。特にCSVファイルは、Excelや古い業務システムとの関係でShift_JISになっていることがあります。

9-5. 同期処理と非同期処理を適切に選ぶ

ファイル読み込みでは、同期処理と非同期処理を適切に選ぶことも重要です。

コンソールアプリで小さなファイルを読むだけなら、同期処理でも十分です。

C#
int bytesRead = fs.Read(buffer, 0, buffer.Length);

一方、UIアプリやWebアプリ、大容量ファイルの読み込みでは、非同期処理を検討しましょう。

C#
int bytesRead = await fs.ReadAsync(buffer);

非同期処理は便利ですが、必ずしもすべての場面で必要ではありません。コードの分かりやすさ、アプリケーションの種類、ファイルサイズ、応答性の要件を考慮して選びましょう。

10. FileStreamのファイル読み込みに関するよくある質問

ここでは、C#のFileStreamによるファイル読み込みでよくある質問に答えます。

10-1. FileStreamとFile.ReadAllBytesの違いは?

FileStreamは、ファイルをストリームとして開き、必要な分だけ少しずつ読み込めます。

C#
using FileStream fs = new FileStream("sample.dat", FileMode.Open, FileAccess.Read);

一方、File.ReadAllBytesは、ファイル全体を一度にバイト配列として読み込みます。

C#
byte[] data = File.ReadAllBytes("sample.dat");

小さなファイルならFile.ReadAllBytesが簡単です。しかし、大容量ファイルではメモリ使用量が大きくなるため、FileStreamで分割して読み込むほうが安全です。

10-2. FileStreamでテキストを文字列として読み込める?

FileStreamでもテキストを読み込めますが、読み込まれるのはbyte[]です。そのため、文字列として扱うにはEncodingを使って変換する必要があります。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.Open, FileAccess.Read);

byte[] buffer = new byte[fs.Length];

int bytesRead = fs.Read(buffer, 0, buffer.Length);

string text = Encoding.UTF8.GetString(buffer, 0, bytesRead);

Console.WriteLine(text);

ただし、テキストファイルを読み込む目的であれば、通常はStreamReaderを使うほうが簡単です。

C#
using StreamReader reader = new StreamReader("sample.txt", Encoding.UTF8);

string text = reader.ReadToEnd();

10-3. Readメソッドで一度に全部読み込めないのはなぜ?

Readメソッドは、指定した最大バイト数まで読み込むメソッドです。必ず指定したサイズ分を読み込むとは限りません。

たとえば、バッファサイズが4096バイトでも、ファイルの残りが1000バイトしかなければ、戻り値は1000になります。また、ストリームの種類や状況によっては、指定サイズより少ないデータだけを返すこともあります。

そのため、ファイルの最後まで読みたい場合は、戻り値が0になるまでループする必要があります。

C#
int bytesRead;

while ((bytesRead = fs.Read(buffer, 0, buffer.Length)) > 0)
{
// bytesRead分だけ処理する
}

Readの戻り値を確認することは、FileStreamを使ううえで非常に重要です。

10-4. ReadAsyncは必ず使ったほうがよい?

ReadAsyncを必ず使う必要はありません。

小さなファイルを読み込むだけのコンソールアプリや、起動時に設定ファイルを一度読むだけの処理であれば、同期処理でも問題ないことが多いです。

一方、UIアプリで画面の応答性を保ちたい場合、Webアプリで同時処理性能を意識したい場合、大容量ファイルを扱う場合は、ReadAsyncを検討するとよいでしょう。

重要なのは、非同期処理を使う目的を理解することです。ReadAsyncは、単純に処理速度を上げるためではなく、I/O待ちの間にスレッドを有効活用しやすくするための仕組みです。

10-5. ファイル読み込み後にFileStreamを閉じないとどうなる?

FileStreamを閉じないままにすると、ファイルハンドルが解放されず、ファイルがロックされたままになることがあります。

その結果、他の処理でファイルを削除できない、上書きできない、別のアプリケーションから開けない、といった問題が発生する可能性があります。

そのため、FileStreamを使うときは必ずusingを使いましょう。

C#
using FileStream fs = new FileStream("sample.dat", FileMode.Open, FileAccess.Read);

usingを使えば、処理が終わったタイミングで自動的にDisposeが呼ばれ、ファイルが閉じられます。

まとめ

C#でファイルを読み込む方法は複数ありますが、バイナリデータを扱うならFileStream、テキストデータを扱うならStreamReaderを使うのが基本です。

FileStream.Readは、ファイルからバイト配列へ同期的に読み込むメソッドです。戻り値として実際に読み込んだバイト数が返るため、必ず戻り値を確認しながら処理しましょう。ファイル全体を読み込む場合は、Readをループで呼び出し、戻り値が0になるまで処理します。

FileStream.ReadAsyncは、非同期でファイルを読み込むメソッドです。UIアプリやWebアプリ、大容量ファイルの読み込みでは、アプリケーションの応答性やスケーラビリティを保つために有効です。

テキストファイルを文字列として読み込む場合は、StreamReaderを使うと簡潔に書けます。ReadToEndでファイル全体を読み込み、ReadLineで1行ずつ処理できます。文字化けを防ぐためには、Encoding.UTF8やShift_JISなど、適切な文字コードを指定することが大切です。

小さなファイルであれば、File.ReadAllTextFile.ReadAllBytesも便利です。ただし、大容量ファイルでは一括読み込みを避け、FileStreamStreamReaderで分割して読み込むようにしましょう。

最後に、FileStreamを使うときは必ずusingまたはusing宣言でリソースを解放し、FileNotFoundExceptionUnauthorizedAccessExceptionIOExceptionなどの例外にも適切に対応することが重要です。C#で安全かつ効率的にファイルを読み込むためには、FileStreamReadReadAsyncStreamReaderの特徴を理解し、目的に応じて使い分けましょう。