C# FileStreamでファイルを書き込む方法|Write/WriteAsyncの基本と追記・上書きの実装例

はじめに

C#でファイルにデータを書き込む方法はいくつかありますが、バイト単位で細かく制御したい場合に使われる代表的なクラスがFileStreamです。

FileStreamを使うと、テキストファイル、CSVファイル、ログファイル、画像や独自形式のバイナリファイルなど、さまざまなファイルに対して書き込み処理を行えます。

特に、次のような処理をしたい場合に便利です。

C#
// 新規作成して書き込む
// 既存ファイルを上書きする
// ファイルの末尾に追記する
// 非同期で書き込む
// 大容量データを分割して書き込む

この記事では、C#のFileStreamでファイルを書き込む基本から、WriteWriteAsync、追記、上書き、エラー対策、実践的な使い分けまでを解説します。

1. C#のFileStreamでファイルを書き込む基本

1-1. FileStreamとは

FileStreamは、C#でファイルをストリームとして扱うためのクラスです。

ストリームとは、データの流れを表す仕組みです。ファイルを一度にすべて読み書きするのではなく、必要な分だけ順番に読み込んだり、書き込んだりできます。

FileStreamSystem.IO名前空間に含まれており、次のように使用します。

C#
using System.IO;

基本的な書き込み処理は、次の流れになります。

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

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

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);
}
}

このコードでは、sample.txtというファイルを作成し、文字列をUTF-8のバイト配列に変換してから書き込んでいます。

1-2. FileStreamでできる書き込み処理

FileStreamでは、主に次のような書き込み処理ができます。

C#
// 新規ファイルを作成して書き込む
// 既存ファイルを上書きする
// 既存ファイルの末尾に追記する
// 指定した位置から書き込む
// バイナリデータを書き込む
// 非同期で書き込む
// 大容量ファイルを少しずつ書き込む

例えば、テキストを書き込む場合は文字列をbyte[]に変換して書き込みます。

C#
string text = "C# FileStream Write";
byte[] bytes = Encoding.UTF8.GetBytes(text);

画像や音声、独自形式のデータなど、すでにバイト配列として扱えるデータであれば、そのままWriteメソッドで書き込めます。

1-3. WriteメソッドとWriteAsyncメソッドの違い

FileStreamでファイルを書き込む代表的なメソッドには、WriteWriteAsyncがあります。

Writeは同期処理です。書き込みが完了するまで、次の処理に進みません。

C#
fs.Write(data, 0, data.Length);

一方、WriteAsyncは非同期処理です。書き込み処理を待っている間に、アプリケーションが他の処理を進めやすくなります。

C#
await fs.WriteAsync(data, 0, data.Length);

コンソールアプリや小さなファイルの書き込みであればWriteでも十分です。Webアプリ、GUIアプリ、大容量ファイルの書き込みなど、処理をブロックしたくない場合はWriteAsyncが向いています。

1-4. FileStreamを使うべきケース

C#でファイルを書き込むだけなら、File.WriteAllTextStreamWriterを使う方が簡単な場合もあります。

それでもFileStreamを使うべきケースは、次のような場面です。

C#
// バイナリデータを書き込みたい
// 書き込み位置を細かく制御したい
// 大容量ファイルを分割して書き込みたい
// ファイル共有モードを指定したい
// 非同期書き込みを細かく制御したい
// 独自形式のファイルを作成したい

単純なテキスト保存であればFile.WriteAllText、文字列中心の書き込みであればStreamWriter、バイト単位の制御が必要な場合はFileStreamと考えると分かりやすいです。

2. FileStreamでファイルを書き込む前に知っておきたい基礎知識

2-1. FileModeの種類と使い分け

FileStreamでファイルを開くときは、FileModeを指定します。

FileModeは、ファイルをどのような状態で開くかを決める指定です。

代表的な値は次のとおりです。

FileMode説明
Create新規作成する。既存ファイルがある場合は上書きする
CreateNew新規作成する。既存ファイルがある場合は例外になる
Open既存ファイルを開く。存在しない場合は例外になる
OpenOrCreate既存ファイルがあれば開き、なければ作成する
Append既存ファイルの末尾に追記する。なければ作成する
Truncate既存ファイルを開き、内容を空にする

新規作成や上書きではFileMode.Createをよく使います。

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

追記したい場合はFileMode.Appendを使います。

C#
using FileStream fs = new FileStream("log.txt", FileMode.Append, FileAccess.Write);

既存ファイルを誤って上書きしたくない場合は、FileMode.CreateNewを使うと安全です。

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

2-2. FileAccess.Writeとは

FileAccessは、ファイルに対してどの操作を行うかを指定する列挙型です。

主な値は次のとおりです。

FileAccess説明
Read読み取りのみ
Write書き込みのみ
ReadWrite読み取りと書き込み

ファイルに書き込むだけなら、FileAccess.Writeを指定します。

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

読み取りも書き込みも行いたい場合は、FileAccess.ReadWriteを指定します。

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

不要なアクセス権を指定しないことで、意図しない読み取りや書き込みを避けやすくなります。

2-3. FileShareの指定方法

FileShareは、ファイルを開いている間に、他のプロセスや処理がそのファイルへアクセスできるかを指定するものです。

代表的な値は次のとおりです。

FileShare説明
None他のアクセスを許可しない
Read他の読み取りを許可する
Write他の書き込みを許可する
ReadWrite他の読み取りと書き込みを許可する

例えば、自分が書き込み中は他の処理にファイルを開かせたくない場合は、FileShare.Noneを指定します。

C#
using FileStream fs = new FileStream(
"sample.txt",
FileMode.Create,
FileAccess.Write,
FileShare.None
);

ログファイルなどで、書き込み中でも別の処理が読み取りたい場合は、FileShare.Readを使うことがあります。

C#
using FileStream fs = new FileStream(
"log.txt",
FileMode.Append,
FileAccess.Write,
FileShare.Read
);

2-4. using文でFileStreamを安全に閉じる理由

FileStreamを使った後は、必ず閉じる必要があります。

ファイルを閉じ忘れると、次のような問題が起こることがあります。

C#
// ファイルが使用中のままになる
// 書き込み内容が完全に反映されない
// 他の処理がファイルを開けない
// リソースが解放されない

そのため、using文を使って自動的にDisposeされるようにします。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

ブロック形式で書くこともできます。

C#
using (FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write))
{
fs.Write(data, 0, data.Length);
}

usingを使うと、正常終了時だけでなく例外が発生した場合でも、ファイルが適切に閉じられます。

2-5. バイト配列に変換して書き込む仕組み

FileStream.Writeは、基本的にバイト配列を書き込むメソッドです。

そのため、文字列を直接書き込むのではなく、エンコーディングを指定してbyte[]に変換します。

C#
string text = "こんにちは";
byte[] data = Encoding.UTF8.GetBytes(text);

その後、Writeでファイルに書き込みます。

C#
fs.Write(data, 0, data.Length);

文字列をファイルに保存する場合、エンコーディングは重要です。

C#
Encoding.UTF8.GetBytes("こんにちは");
Encoding.Unicode.GetBytes("こんにちは");
Encoding.GetEncoding("shift_jis").GetBytes("こんにちは");

現在のC#開発では、特別な理由がなければUTF-8を使うことが多いです。

3. FileStream.Writeでファイルを書き込む基本例

3-1. 新規ファイルを作成して書き込む

まずは、FileStream.Writeを使って新規ファイルを作成し、文字列を書き込む基本例です。

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

class Program
{
static void Main()
{
string path = "output.txt";
string text = "C# FileStreamでファイルを書き込みます。";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

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

FileMode.Createを指定しているため、output.txtが存在しない場合は新規作成されます。すでに存在する場合は、内容が上書きされます。

3-2. 文字列をbyte配列に変換して書き込む

FileStreamはバイト単位でデータを扱うため、文字列を書き込むにはEncodingを使ってbyte[]へ変換します。

C#
string message = "Hello, FileStream!";
byte[] bytes = Encoding.UTF8.GetBytes(message);

変換したバイト配列をWriteに渡します。

C#
using FileStream fs = new FileStream("message.txt", FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);

全体のコードは次のようになります。

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

string path = "message.txt";
string message = "Hello, FileStream!";

byte[] bytes = Encoding.UTF8.GetBytes(message);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(bytes, 0, bytes.Length);

Encoding.UTF8.GetBytesを使うことで、文字列がUTF-8形式のバイト列に変換されます。

3-3. 書き込み位置とoffset/countの指定方法

FileStream.Writeには、次のような形式があります。

C#
fs.Write(byte[] buffer, int offset, int count);

それぞれの意味は次のとおりです。

引数説明
buffer書き込むバイト配列
offsetバイト配列のどの位置から書き込むか
count何バイト書き込むか

例えば、次のコードでは、バイト配列の一部だけを書き込みます。

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

string text = "ABCDEFG";
byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream("partial.txt", FileMode.Create, FileAccess.Write);

// data[2]から3バイト分を書き込む
fs.Write(data, 2, 3);

この場合、書き込まれる内容は次のようになります。

CDE

offsetはファイル内の書き込み位置ではなく、バイト配列の開始位置を指定する値です。

ファイル内の書き込み位置を変更したい場合は、Seekを使います。

C#
fs.Seek(10, SeekOrigin.Begin);

3-4. 書き込み後にファイル内容を確認する方法

書き込み後に内容を確認する簡単な方法は、File.ReadAllTextを使うことです。

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

string path = "check.txt";
string text = "書き込み確認用のテキストです。";

byte[] data = Encoding.UTF8.GetBytes(text);

using (FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write))
{
fs.Write(data, 0, data.Length);
}

string result = File.ReadAllText(path, Encoding.UTF8);
Console.WriteLine(result);

FileStreamusingで閉じた後に読み取るのがポイントです。

ファイルが閉じられる前に読み取ろうとすると、書き込み内容がまだ反映されていなかったり、ファイルがロックされていたりする場合があります。

4. FileStreamで上書き保存する方法

4-1. FileMode.Createで既存ファイルを上書きする

既存ファイルを上書き保存したい場合は、FileMode.Createを使うのが一般的です。

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

FileMode.Createは、ファイルが存在しない場合は新規作成し、存在する場合は内容を破棄して新しく書き込みます。

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

string path = "sample.txt";
string text = "新しい内容で上書きします。";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

既存の内容は残らないため、上書きしてよいファイルかどうかを事前に確認することが大切です。

4-2. FileMode.Truncateで内容を空にしてから書き込む

FileMode.Truncateを使うと、既存ファイルを開いたうえで、内容を空にできます。

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

FileMode.Truncateは既存ファイルがあることを前提にしています。ファイルが存在しない場合は例外が発生します。

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

string path = "sample.txt";
string text = "Truncateで空にしてから書き込みます。";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Truncate, FileAccess.Write);
fs.Write(data, 0, data.Length);

Createとの違いは、Createがファイルを新規作成または上書きするのに対し、Truncateは既存ファイルを開いて内容を空にする点です。

4-3. 上書き時に注意すべき既存データの扱い

上書き保存では、既存データが消える点に注意が必要です。

例えば、次のようなケースでは慎重に扱う必要があります。

C#
// ユーザーが作成したファイルを上書きする
// 設定ファイルを保存し直す
// ログファイルを誤って初期化する
// CSVファイルを再生成する

安全に上書きしたい場合は、次のような対策が有効です。

C#
// 上書き前にバックアップを作成する
// File.Existsで存在確認する
// CreateNewを使って誤上書きを防ぐ
// 一時ファイルへ書き込んでから置き換える

例として、上書き前にバックアップを作成するコードは次のとおりです。

C#
string path = "data.txt";
string backupPath = "data_backup.txt";

if (File.Exists(path))
{
File.Copy(path, backupPath, overwrite: true);
}

重要なファイルを扱う場合は、いきなりFileMode.Createで上書きするのではなく、バックアップや確認処理を入れると安全です。

4-4. 上書き保存の実装例

以下は、既存ファイルを上書き保存する実装例です。

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

class Program
{
static void Main()
{
string path = "settings.txt";
string content = "Theme=Dark\nFontSize=14\nLanguage=ja-JP";

OverwriteFile(path, content);

Console.WriteLine("上書き保存が完了しました。");
}

static void OverwriteFile(string path, string content)
{
byte[] data = Encoding.UTF8.GetBytes(content);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);
}
}

この例では、OverwriteFileメソッドにファイルパスと内容を渡すだけで、既存ファイルを上書き保存できます。

改行を含むテキストも、文字列として用意してからバイト配列に変換すれば問題なく書き込めます。

5. FileStreamで追記する方法

5-1. FileMode.Appendで末尾に書き込む

既存ファイルの末尾にデータを追加したい場合は、FileMode.Appendを使います。

C#
using FileStream fs = new FileStream("log.txt", FileMode.Append, FileAccess.Write);

FileMode.Appendを指定すると、書き込み位置はファイルの末尾になります。

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

string path = "log.txt";
string text = "ログを追記します。\n";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write);
fs.Write(data, 0, data.Length);

ファイルが存在しない場合は自動的に作成されます。

ログファイルや履歴ファイルのように、既存内容を残したまま追加したい場合に便利です。

5-2. Seekを使って書き込み位置を指定する

FileStreamでは、Seekを使って書き込み位置を指定できます。

C#
fs.Seek(0, SeekOrigin.End);

SeekOriginには、主に次の指定があります。

SeekOrigin説明
Beginファイルの先頭を基準にする
Current現在位置を基準にする
Endファイルの末尾を基準にする

例えば、FileMode.OpenOrCreateでファイルを開き、末尾へ移動してから書き込む場合は次のようになります。

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

string path = "data.txt";
string text = "末尾に追加します。\n";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write);
fs.Seek(0, SeekOrigin.End);
fs.Write(data, 0, data.Length);

FileMode.Appendでも追記できますが、Seekを使うと、より柔軟に書き込み位置を制御できます。

ただし、FileMode.Appendで開いた場合は基本的に末尾への追加が目的になるため、途中位置への書き込みにはOpenOpenOrCreateを使います。

5-3. 改行付きで追記する方法

テキストを追記する場合、改行を付けないと前の内容とつながってしまいます。

改行付きで追記するには、文字列にEnvironment.NewLineを付ける方法がおすすめです。

C#
string line = "新しい行を追記します。" + Environment.NewLine;

実装例は次のとおりです。

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

string path = "notes.txt";
string line = "FileStreamで改行付き追記" + Environment.NewLine;

byte[] data = Encoding.UTF8.GetBytes(line);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write);
fs.Write(data, 0, data.Length);

Environment.NewLineを使うと、実行環境に合わせた改行コードが使われます。

Windowsでは通常\r\n、LinuxやmacOSでは通常\nになります。

5-4. ログファイルへ追記する実装例

ログファイルへ追記する場合は、日時とメッセージを組み合わせると実用的です。

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

class Program
{
static void Main()
{
WriteLog("アプリケーションを開始しました。");
WriteLog("処理が完了しました。");
}

static void WriteLog(string message)
{
string path = "app.log";
string line = $"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}{Environment.NewLine}";

byte[] data = Encoding.UTF8.GetBytes(line);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
fs.Write(data, 0, data.Length);
}
}

FileShare.Readを指定しているため、ログを書き込んでいる間でも、別の処理が読み取りできる可能性があります。

ログファイルは追記型で扱うことが多いため、FileMode.Appendとの相性が良いです。

6. FileStream.WriteAsyncで非同期に書き込む方法

6-1. WriteAsyncを使うメリット

WriteAsyncは、ファイル書き込みを非同期で行うためのメソッドです。

非同期書き込みには、次のようなメリットがあります。

C#
// UIスレッドをブロックしにくい
// Webアプリでリクエスト処理を効率化しやすい
// 大容量ファイルの書き込みで待ち時間を扱いやすい
// キャンセル処理と組み合わせやすい

特に、Windows Forms、WPF、ASP.NET Coreなどでは、同期的なファイル書き込みが応答性に影響することがあります。

そのような場合は、asyncawaitを使ったWriteAsyncが有効です。

6-2. async/awaitを使った基本構文

WriteAsyncを使う基本構文は次のとおりです。

C#
await fs.WriteAsync(data, 0, data.Length);

メソッド側にはasyncを付けます。

C#
static async Task WriteFileAsync()
{
byte[] data = Encoding.UTF8.GetBytes("非同期で書き込みます。");

using FileStream fs = new FileStream("async.txt", FileMode.Create, FileAccess.Write);
await fs.WriteAsync(data, 0, data.Length);
}

Mainメソッドで呼び出す場合は、次のように書けます。

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

class Program
{
static async Task Main()
{
await WriteFileAsync();
}

static async Task WriteFileAsync()
{
byte[] data = Encoding.UTF8.GetBytes("WriteAsyncの基本例です。");

using FileStream fs = new FileStream("async.txt", FileMode.Create, FileAccess.Write);
await fs.WriteAsync(data, 0, data.Length);
}
}

6-3. 非同期で新規ファイルに書き込む例

非同期で新規ファイルを作成して書き込む例です。

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

class Program
{
static async Task Main()
{
string path = "new_async.txt";
string content = "FileStream.WriteAsyncで新規ファイルに書き込みます。";

await WriteNewFileAsync(path, content);

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

static async Task WriteNewFileAsync(string path, string content)
{
byte[] data = Encoding.UTF8.GetBytes(content);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
await fs.WriteAsync(data, 0, data.Length);
}
}

awaitを付けることで、書き込み完了を待ってから次の処理に進みます。

6-4. 非同期で追記・上書きする例

非同期で追記する場合は、FileMode.AppendWriteAsyncを組み合わせます。

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

static async Task AppendAsync(string path, string line)
{
string text = line + Environment.NewLine;
byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write);
await fs.WriteAsync(data, 0, data.Length);
}

非同期で上書きする場合は、FileMode.Createを使います。

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

static async Task OverwriteAsync(string path, string content)
{
byte[] data = Encoding.UTF8.GetBytes(content);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
await fs.WriteAsync(data, 0, data.Length);
}

呼び出し例は次のとおりです。

C#
await AppendAsync("app.log", "ログを非同期で追記しました。");
await OverwriteAsync("config.txt", "Mode=Production");

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

WriteAsyncでは、CancellationTokenを使ってキャンセル処理を実装できます。

.NETの新しい書き方では、ReadOnlyMemory<byte>を渡す形のWriteAsyncもよく使われます。

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

class Program
{
static async Task Main()
{
using CancellationTokenSource cts = new CancellationTokenSource();

try
{
await WriteWithCancellationAsync("cancel.txt", "キャンセル対応の書き込みです。", cts.Token);
Console.WriteLine("書き込みが完了しました。");
}
catch (OperationCanceledException)
{
Console.WriteLine("書き込みがキャンセルされました。");
}
}

static async Task WriteWithCancellationAsync(string path, string content, CancellationToken cancellationToken)
{
byte[] data = Encoding.UTF8.GetBytes(content);

await using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);

await fs.WriteAsync(data.AsMemory(0, data.Length), cancellationToken);
}
}

キャンセル処理は、大容量ファイルや時間のかかる処理で特に役立ちます。

7. FileStreamで書き込むときのエラー対策

7-1. ファイルが使用中の場合の対処

ファイルが他のアプリケーションやプロセスで使用中の場合、FileStreamで開けずに例外が発生することがあります。

代表的にはIOExceptionが発生します。

C#
try
{
using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write, FileShare.None);
}
catch (IOException ex)
{
Console.WriteLine("ファイルが使用中の可能性があります。");
Console.WriteLine(ex.Message);
}

対策としては、次の方法があります。

C#
// FileShareの指定を見直す
// 少し待ってから再試行する
// 他のアプリでファイルを開いていないか確認する
// ロック対象のファイルを分ける

ログファイルなど、読み取りだけは許可したい場合はFileShare.Readを使うことがあります。

C#
using FileStream fs = new FileStream("log.txt", FileMode.Append, FileAccess.Write, FileShare.Read);

7-2. アクセス権限がない場合の対処

書き込み権限がない場所にファイルを作成しようとすると、UnauthorizedAccessExceptionが発生することがあります。

例えば、管理者権限が必要なフォルダや、読み取り専用ファイルに書き込もうとした場合です。

C#
try
{
using FileStream fs = new FileStream(@"C:\Windows\sample.txt", FileMode.Create, FileAccess.Write);
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("ファイルへの書き込み権限がありません。");
Console.WriteLine(ex.Message);
}

対策としては、アプリケーションが書き込み可能なフォルダを使うことが重要です。

C#
string path = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
"sample.txt"
);

アプリの設定ファイルや一時ファイルであれば、ユーザー領域や一時フォルダを使うと安全です。

C#
string tempPath = Path.Combine(Path.GetTempPath(), "temp.txt");

7-3. ディレクトリが存在しない場合の対処

指定したファイルパスのディレクトリが存在しない場合、DirectoryNotFoundExceptionが発生します。

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

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);

この例では、data\outputフォルダが存在しないと書き込みに失敗します。

事前にディレクトリを作成するには、Directory.CreateDirectoryを使います。

C#
string path = @"data\output\sample.txt";
string? directory = Path.GetDirectoryName(path);

if (!string.IsNullOrEmpty(directory))
{
Directory.CreateDirectory(directory);
}

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);

Directory.CreateDirectoryは、すでにディレクトリが存在していてもエラーになりにくいため、書き込み前に呼び出しておくと便利です。

7-4. try-catchで例外を処理する方法

FileStreamの書き込み処理では、ファイルの状態や権限によって例外が発生する可能性があります。

基本的な例外処理は次のように書きます。

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

try
{
string path = "sample.txt";
string text = "例外処理付きの書き込みです。";

byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

Console.WriteLine("書き込みに成功しました。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("アクセス権限がありません。");
}
catch (DirectoryNotFoundException)
{
Console.WriteLine("指定されたディレクトリが存在しません。");
}
catch (IOException)
{
Console.WriteLine("入出力エラーが発生しました。ファイルが使用中の可能性があります。");
}
catch (Exception ex)
{
Console.WriteLine("予期しないエラーが発生しました。");
Console.WriteLine(ex.Message);
}

具体的な例外を先に捕捉し、最後にExceptionで予期しないエラーを扱うのが一般的です。

7-5. IOExceptionやUnauthorizedAccessExceptionの扱い

FileStreamの書き込みでよく見かける例外には、IOExceptionUnauthorizedAccessExceptionがあります。

IOExceptionは、ファイルが使用中、ディスクの問題、入出力処理の失敗などで発生することがあります。

C#
catch (IOException ex)
{
Console.WriteLine("IOエラーが発生しました。");
Console.WriteLine(ex.Message);
}

UnauthorizedAccessExceptionは、アクセス権限がない場合や、ディレクトリをファイルとして開こうとした場合などに発生することがあります。

C#
catch (UnauthorizedAccessException ex)
{
Console.WriteLine("アクセスが拒否されました。");
Console.WriteLine(ex.Message);
}

実務では、例外メッセージをそのままユーザーに表示するのではなく、ログに記録し、ユーザーには分かりやすいメッセージを表示することが多いです。

8. FileStream書き込み処理の実践パターン

8-1. テキストファイルを書き込む

テキストファイルを書き込む場合は、文字列をエンコーディングでバイト配列に変換します。

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

string path = "text.txt";
string content = "これはテキストファイルです。";

byte[] data = Encoding.UTF8.GetBytes(content);

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

単純なテキストだけならFile.WriteAllTextの方が簡単ですが、書き込み位置や共有モードを制御したい場合はFileStreamが役立ちます。

8-2. バイナリファイルを書き込む

FileStreamは、バイナリファイルの書き込みに向いています。

C#
using System.IO;

byte[] binaryData = new byte[]
{
0x01, 0x02, 0x03, 0x04, 0xFF
};

using FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write);
fs.Write(binaryData, 0, binaryData.Length);

画像、音声、圧縮データ、独自ファイル形式など、文字列ではなくバイト列として扱うデータにはFileStreamが適しています。

8-3. CSVファイルを書き込む

CSVファイルも文字列として作成し、バイト配列に変換して書き込めます。

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

string path = "users.csv";

StringBuilder csv = new StringBuilder();
csv.AppendLine("Id,Name,Age");
csv.AppendLine("1,Taro,30");
csv.AppendLine("2,Hanako,25");
csv.AppendLine("3,Jiro,28");

byte[] data = Encoding.UTF8.GetBytes(csv.ToString());

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

値にカンマやダブルクォート、改行が含まれる可能性がある場合は、CSVのエスケープ処理も必要です。

簡単なCSV出力であればこの方法で十分ですが、複雑なCSVを扱う場合はCSVライブラリの利用も検討するとよいでしょう。

8-4. ログファイルを書き込む

ログファイルは、既存の内容を残して追記するのが一般的です。

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

static void WriteLog(string message)
{
string path = "system.log";
string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}{Environment.NewLine}";

byte[] data = Encoding.UTF8.GetBytes(log);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
fs.Write(data, 0, data.Length);
}

WriteLog("ログインしました。");
WriteLog("データを保存しました。");

ログファイルでは、FileMode.AppendEnvironment.NewLineを組み合わせることで、行単位の追記がしやすくなります。

8-5. 大容量ファイルを書き込む

大容量ファイルを書き込む場合は、一度にすべてのデータをメモリに載せるのではなく、分割して書き込むのが基本です。

次の例では、一定サイズのバッファを繰り返し書き込んでいます。

C#
using System.IO;

string path = "large.bin";
byte[] buffer = new byte[1024 * 1024]; // 1MB

using FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write);

for (int i = 0; i < 100; i++)
{
fs.Write(buffer, 0, buffer.Length);
}

この例では、1MBのバッファを100回書き込み、約100MBのファイルを作成しています。

実際には、ネットワークから受け取ったデータや別ファイルから読み込んだデータを、一定サイズずつ書き込むケースが多いです。

9. FileStreamと他のファイル書き込み方法の違い

9-1. File.WriteAllTextとの違い

File.WriteAllTextは、文字列をファイルへ簡単に書き込むためのメソッドです。

C#
File.WriteAllText("sample.txt", "こんにちは", Encoding.UTF8);

非常に簡単ですが、基本的には文字列全体をまとめて書き込む用途に向いています。

一方、FileStreamはバイト単位で制御できます。

C#
byte[] data = Encoding.UTF8.GetBytes("こんにちは");

using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

単純なテキスト保存ならFile.WriteAllText、細かい制御が必要ならFileStreamという使い分けが分かりやすいです。

9-2. File.WriteAllBytesとの違い

File.WriteAllBytesは、バイト配列を一度にファイルへ書き込むメソッドです。

C#
byte[] data = { 0x01, 0x02, 0x03 };
File.WriteAllBytes("data.bin", data);

小さなバイナリデータを書き込む場合は便利です。

ただし、大容量データを分割して書き込みたい場合や、追記・位置指定・共有モードなどを細かく指定したい場合はFileStreamが向いています。

C#
using FileStream fs = new FileStream("data.bin", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

9-3. StreamWriterとの違い

StreamWriterは、テキストを書き込むためのクラスです。

C#
using StreamWriter writer = new StreamWriter("sample.txt", false, Encoding.UTF8);
writer.WriteLine("こんにちは");

文字列や行単位の書き込みにはStreamWriterの方が扱いやすいです。

一方、FileStreamはバイト単位の書き込みに向いています。

C#
byte[] data = Encoding.UTF8.GetBytes("こんにちは");

using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

実務では、テキスト中心ならStreamWriter、バイナリ中心ならFileStreamを使うことが多いです。

また、StreamWriterの内部でFileStreamを使うこともできます。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
using StreamWriter writer = new StreamWriter(fs, Encoding.UTF8);

writer.WriteLine("FileStreamとStreamWriterを組み合わせます。");

9-4. 用途別の使い分け

用途別に整理すると、次のようになります。

用途おすすめ
短いテキストを一度に保存File.WriteAllText
バイト配列を一度に保存File.WriteAllBytes
行単位でテキストを書き込むStreamWriter
バイナリデータを書き込むFileStream
追記や上書きを細かく制御FileStream
大容量データを分割して書き込むFileStream
非同期で細かく制御するFileStream.WriteAsync

FileStreamは少しコード量が増えますが、その分だけ柔軟な制御ができます。

10. FileStreamで書き込むときの注意点

10-1. エンコーディングの指定に注意する

文字列をFileStreamで書き込む場合、エンコーディングの指定が重要です。

C#
byte[] data = Encoding.UTF8.GetBytes("こんにちは");

書き込み時と読み込み時のエンコーディングが異なると、文字化けすることがあります。

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

日本語を含むテキストでは、特にUTF-8、Shift_JIS、Unicodeの違いに注意が必要です。

基本的にはUTF-8を使い、他のシステムとの連携で必要な場合だけ別のエンコーディングを指定するとよいでしょう。

10-2. ファイルの閉じ忘れを防ぐ

FileStreamを閉じ忘れると、ファイルがロックされたままになったり、書き込み内容が確定しなかったりすることがあります。

避けるべき例は次のようなコードです。

C#
FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);
// CloseやDisposeを忘れている

必ずusingを使います。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

非同期の場合は、await usingを使うこともできます。

C#
await using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
await fs.WriteAsync(data);

10-3. 既存ファイルを誤って上書きしない

FileMode.Createは便利ですが、既存ファイルがある場合は内容を上書きします。

C#
using FileStream fs = new FileStream("important.txt", FileMode.Create, FileAccess.Write);

重要なファイルを扱う場合は、事前に存在確認を行うと安全です。

C#
string path = "important.txt";

if (File.Exists(path))
{
Console.WriteLine("ファイルがすでに存在します。");
}
else
{
using FileStream fs = new FileStream(path, FileMode.CreateNew, FileAccess.Write);
}

誤上書きを防ぎたい場合は、FileMode.CreateNewを使う方法もあります。

C#
using FileStream fs = new FileStream("newfile.txt", FileMode.CreateNew, FileAccess.Write);

10-4. バッファサイズを適切に設定する

FileStreamのコンストラクターでは、バッファサイズを指定できます。

C#
using FileStream fs = new FileStream(
"large.dat",
FileMode.Create,
FileAccess.Write,
FileShare.None,
bufferSize: 81920
);

小さなファイルでは、バッファサイズを強く意識する必要はあまりありません。

しかし、大容量ファイルを扱う場合は、バッファサイズがパフォーマンスに影響することがあります。

大きすぎるバッファはメモリを余分に使い、小さすぎるバッファは書き込み回数が増える可能性があります。

実務では、数KBから数十KB程度を基準にし、処理内容に応じて調整します。

10-5. 同時書き込み時の競合を避ける

複数の処理が同じファイルへ同時に書き込むと、データが壊れたり、例外が発生したりすることがあります。

対策としては、次の方法があります。

C#
// FileShare.Noneで排他制御する
// lockを使って同一プロセス内の同時書き込みを防ぐ
// 書き込み先ファイルを分ける
// キューに積んで1つずつ書き込む

同一アプリ内でログを書き込む場合は、lockを使う方法があります。

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

class Logger
{
private static readonly object _lock = new object();

public static void Write(string message)
{
lock (_lock)
{
string path = "app.log";
string line = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}{Environment.NewLine}";
byte[] data = Encoding.UTF8.GetBytes(line);

using FileStream fs = new FileStream(path, FileMode.Append, FileAccess.Write, FileShare.Read);
fs.Write(data, 0, data.Length);
}
}
}

複数プロセスから同じファイルへ書き込む場合は、より慎重な排他制御が必要です。

11. C# FileStream Writeに関するよくある質問

11-1. FileStream.Writeで文字列を直接書き込めるか

FileStream.Writeでは、基本的に文字列を直接書き込むことはできません。

文字列を書き込む場合は、Encodingを使ってbyte[]に変換します。

C#
string text = "こんにちは";
byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

文字列を直接扱いたい場合は、StreamWriterを使う方が簡単です。

C#
using StreamWriter writer = new StreamWriter("sample.txt", false, Encoding.UTF8);
writer.WriteLine("こんにちは");

11-2. WriteとWriteAsyncはどちらを使うべきか

小さなファイルを単純に書き込むだけなら、Writeで問題ありません。

C#
fs.Write(data, 0, data.Length);

一方、次のような場合はWriteAsyncが向いています。

C#
// GUIアプリで画面を固めたくない
// Webアプリで同期処理を避けたい
// 大容量ファイルを書き込む
// キャンセル処理を入れたい
C#
await fs.WriteAsync(data, 0, data.Length);

迷った場合は、コンソールアプリや小規模処理ではWrite、応答性やスケーラビリティを重視する処理ではWriteAsyncを選ぶとよいです。

11-3. ファイルが存在しない場合は自動作成されるか

指定するFileModeによって異なります。

ファイルが存在しない場合に作成される代表例は次のとおりです。

C#
FileMode.Create
FileMode.CreateNew
FileMode.OpenOrCreate
FileMode.Append

一方、FileMode.OpenFileMode.Truncateは、ファイルが存在しない場合に例外が発生します。

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

ファイルがなければ作成し、あれば上書きしたい場合はFileMode.Createを使います。

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

ファイルがなければ作成し、あれば末尾に追記したい場合はFileMode.Appendを使います。

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

11-4. 追記と上書きはどう切り替えるか

追記と上書きは、主にFileModeで切り替えます。

上書きする場合はFileMode.Createを使います。

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

追記する場合はFileMode.Appendを使います。

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

既存ファイルの末尾へ移動して書き込む方法もあります。

C#
using FileStream fs = new FileStream("sample.txt", FileMode.OpenOrCreate, FileAccess.Write);
fs.Seek(0, SeekOrigin.End);
fs.Write(data, 0, data.Length);

単純な追記ならFileMode.Append、位置を細かく制御したい場合はSeekを使うとよいです。

11-5. StreamWriterではなくFileStreamを使うべき場面はどこか

StreamWriterはテキスト書き込みに便利なクラスです。

そのため、文字列や行単位の書き込みだけならStreamWriterが向いています。

C#
using StreamWriter writer = new StreamWriter("sample.txt", false, Encoding.UTF8);
writer.WriteLine("テキストを書き込みます。");

一方、FileStreamを使うべき場面は次のようなケースです。

C#
// バイナリデータを書き込みたい
// byte配列をそのまま扱いたい
// 書き込み位置を制御したい
// FileModeやFileShareを細かく指定したい
// 大容量データを分割して書き込みたい
// StreamWriter以外のストリーム処理と組み合わせたい

テキスト中心ならStreamWriter、バイト単位の制御が必要ならFileStreamを選ぶと分かりやすいです。

まとめ

C#でFileStreamを使うと、ファイルへの書き込み処理をバイト単位で細かく制御できます。

基本的な流れは、文字列をEncoding.UTF8.GetBytesなどでbyte[]に変換し、FileStream.Writeで書き込む形です。

C#
string text = "C# FileStream Write";
byte[] data = Encoding.UTF8.GetBytes(text);

using FileStream fs = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);
fs.Write(data, 0, data.Length);

上書きしたい場合はFileMode.Create、追記したい場合はFileMode.Appendを使います。

C#
// 上書き
using FileStream overwrite = new FileStream("sample.txt", FileMode.Create, FileAccess.Write);

// 追記
using FileStream append = new FileStream("sample.txt", FileMode.Append, FileAccess.Write);

非同期で書き込む場合は、WriteAsyncを使います。

C#
await fs.WriteAsync(data, 0, data.Length);

FileStreamは、File.WriteAllTextStreamWriterよりコード量は増えますが、バイナリデータ、追記、上書き、書き込み位置、共有モード、大容量ファイルなどを柔軟に扱えます。

ファイルの閉じ忘れを防ぐためにusingを使い、必要に応じてtry-catchIOExceptionUnauthorizedAccessExceptionに対応することも重要です。

FileStream.WriteFileStream.WriteAsyncを正しく使い分ければ、C#でのファイル書き込み処理を安全かつ柔軟に実装できます。