C#を用いたプログラミングにおいて、テキストファイルの読み書きは非常に基本的な操作でありながら、システムのパフォーマンスや堅牢性に直結する重要な要素です。
近年の.NET環境では、非同期処理の標準化やメモリ効率の最適化が進み、より高度なファイル操作が求められるようになっています。
この記事では、初心者の方でもすぐに実践できる基本的な手法から、2026年時点での最新のベストプラクティスまでを詳しく解説します。
効率的なコードを書くためのポイントを整理し、実際の開発現場で役立つ知識を深めていきましょう。
C#におけるファイル操作の基本概念
C#でテキストファイルを扱う際、最も一般的に使用されるのはSystem.IO名前空間に含まれるクラス群です。
ファイルの読み書きには、大きく分けて「一括処理」と「ストリーム処理」の2つのアプローチが存在します。
一括処理は、ファイルの内容を一度にすべてメモリに読み込む方法で、小規模な設定ファイルなどの操作に適しています。
一方、ストリーム処理はファイルの内容を少しずつ読み書きする方法であり、ログファイルや巨大なデータセットを扱う際にメモリ消費を抑えるために必須となります。
開発者は、扱うデータのサイズやアプリケーションの要件に応じて、これらを適切に使い分ける必要があります。
一括読み書き:シンプルで直感的な方法
小規模なテキストファイルであれば、Fileクラスが提供する静的メソッドを利用するのが最も簡単です。
File.ReadAllText と File.WriteAllText
ファイル全体を一つの文字列として取得したり、文字列をそのままファイルに保存したりする場合に使用します。
// ファイルの書き込み
string content = "C#でのテキスト操作は非常に強力です。";
File.WriteAllText("example.txt", content);
// ファイルの読み込み
string readContent = File.ReadAllText("example.txt");
Console.WriteLine(readContent);
C#でのテキスト操作は非常に強力です。
この手法はコードが非常に簡潔になるため、可読性を重視する場面で有効です。
ただし、ファイルサイズが数GBに及ぶような場合には、メモリ不足を引き起こす可能性があるため注意が必要です。
File.ReadAllLines と File.AppendAllLines
テキストを一行ずつ処理したい場合は、ReadAllLinesメソッドが便利です。
このメソッドは、ファイルの内容を行ごとの文字列配列として返却します。
string[] lines = { "一行目", "二行目", "三行目" };
// 配列の内容をファイルに書き込む
File.WriteAllLines("lines.txt", lines);
// 既存のファイルに追記する
File.AppendAllLines("lines.txt", new[] { "追加の行" });
// 再度読み込み
string[] updatedLines = File.ReadAllLines("lines.txt");
foreach (var line in updatedLines)
{
Console.WriteLine(line);
}
一行目
二行目
三行目
追加の行
配列としてデータを扱えるため、LINQを活用して特定の条件に合致する行を抽出するなどの処理が容易になります。
非同期処理による効率的な読み書き
現代のアプリケーション開発において、UIスレッドをブロックしない非同期処理の導入は避けて通れません。
.NETでは、ファイル操作に対しても強力な非同期APIが提供されています。
async/awaitを活用した非同期I/O
File.WriteAllTextAsyncやFile.ReadAllTextAsyncを使用することで、スレッドのリソースを効率的に活用できます。
public async Task ProcessFileAsync()
{
string filePath = "async_example.txt";
string text = "これは非同期で書き込まれたテキストです。";
// 非同期書き込み
await File.WriteAllTextAsync(filePath, text);
// 非同期読み込み
string result = await File.ReadAllTextAsync(filePath);
Console.WriteLine(result);
}
ASP.NET Coreなどのサーバーサイド開発では、同時に多数のリクエストを処理するために、非同期メソッドの利用が強く推奨されます。
非同期処理を適切に使用することで、システム全体ののスループットを向上させることが可能です。
ストリーム処理:大規模ファイルへの対応
数百万行を超えるような巨大なテキストファイルを扱う場合、一括読み込みは現実的ではありません。
このようなケースでは、StreamReaderおよびStreamWriterクラスを使用します。
StreamReaderによる逐次読み込み
ReadLineAsyncメソッドをループ内で呼び出すことで、メモリ負荷を最小限に抑えつつデータを処理できます。
using (StreamReader reader = new StreamReader("large_data.txt"))
{
string line;
while ((line = await reader.ReadLineAsync()) != null)
{
// 一行ずつ処理を行う
if (line.Contains("特定キーワード"))
{
Console.WriteLine(line);
}
}
}
usingステートメントを使用することで、処理終了時にファイルハンドルを確実に解放し、リソースリークを防ぐことができます。
StreamWriterによる逐次書き込み
大量のデータを生成してファイルに出力する場合も、ストリームを利用します。
using (StreamWriter writer = new StreamWriter("output_log.txt", append: true))
{
for (int i = 0; i < 1000; i++)
{
await writer.WriteLineAsync($"ログエントリー {i}: 正常に処理されました。");
}
}
append: trueを指定することで、既存の内容を消去せずに末尾へ追記することが可能です。
文字エンコーディングのベストプラクティス
テキストファイルを扱う上で、文字化けの問題は常に考慮すべき課題です。
現代のC#開発におけるデフォルトのエンコーディングはUTF-8です。
明示的なエンコーディング指定
特定のシステム要件でShift-JISなどが必要な場合は、Encoding.GetEncodingを使用して明示的に指定します。
// Shift-JISを使用する場合の例
// 事前に Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); が必要な場合があります
var encoding = Encoding.GetEncoding("shift_jis");
await File.WriteAllTextAsync("sjis.txt", "日本語テキスト", encoding);
ただし、特別な理由がない限りはBOMなしのUTF-8を使用するのが、マルチプラットフォーム環境における最新の推奨事項です。
例外処理と安全なファイル操作
ファイル操作には、ファイルが存在しない、アクセス権限がない、別のプロセスが使用中であるなど、多くのエラー要因が伴います。
try-catchによるエラーハンドリング
実行時の例外を適切にキャッチし、ユーザーに分かりやすいフィードバックを提供することが重要です。
try
{
string content = await File.ReadAllTextAsync("config.json");
}
catch (FileNotFoundException ex)
{
Console.WriteLine("指定されたファイルが見つかりません。");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("ファイルへのアクセス権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"I/Oエラーが発生しました: {ex.Message}");
}
例外をキャッチする際は、最も具体的な例外クラスから順に記述するのがC#の基本ルールです。
パフォーマンスを最大化するためのTIPS
2026年の開発環境では、さらに高いパフォーマンスを実現するためのテクニックが活用されています。
FileStreamOptionsの活用
.NETの比較的新しいバージョンでは、FileStreamの挙動を細かく制御するためのFileStreamOptionsが導入されています。
var options = new FileStreamOptions
{
Mode = FileMode.Open,
Access = FileAccess.Read,
Share = FileShare.Read,
Options = FileOptions.Asynchronous | FileOptions.SequentialScan
};
using (var streamReader = new StreamReader("data.txt", options))
{
// 効率的な読み込み処理
}
FileOptions.SequentialScanを指定することで、OSに対して「このファイルは先頭から順に読み込まれる」というヒントを与え、キャッシュ効率を高めることができます。
Span<T>を用いた文字列解析
読み込んだテキストのパース(解析)において、Substringを多用すると大量のメモリ割り当てが発生します。
ReadOnlySpan<char>を使用することで、メモリのコピーを発生させずに文字列の一部を切り出し、高速に処理することが可能です。
よくある課題と解決策の比較
用途に応じた最適な手法を以下の表にまとめました。
| シナリオ | 推奨される手法 | メリット |
|---|---|---|
| 設定ファイルの読み込み | File.ReadAllTextAsync | 実装が非常に簡単 |
| 大量のログ出力 | StreamWriter | メモリ消費が極めて低い |
| データの追記 | File.AppendAllLinesAsync | 排他制御を考慮しやすい |
| バイナリ混在のテキスト | FileStream | バイト単位の細かな制御が可能 |
まとめ
C#でのテキストファイル読み書きは、単純なFileクラスの利用から、パフォーマンスを追求したストリーム処理や非同期APIまで多岐にわたります。
小規模なデータであれば簡潔なコードを優先し、大規模なデータや高負荷なシステムではメモリ効率と非同期性を重視するという使い分けが肝要です。
特にusingステートメントによるリソース管理と、最新のFileStreamOptionsを活用した最適化は、プロフェッショナルな開発において欠かせない技術です。
今回解説したベストプラクティスを参考に、堅牢で効率的なファイル処理を実装してみてください。
日々の開発の中でこれらの手法を使い分けることで、より高品質なC#プログラムを構築できるようになるはずです。
