C#でファイルを書き込む方法|csharp write fileの基本から追記・上書きまで解説

はじめに

C#でテキストやログ、CSV、JSONなどを保存するには、System.IO名前空間のFileクラスやStreamWriterクラスを使用します。

小さなファイルを一度に保存するならFile.WriteAllText、既存ファイルへ追記するならFile.AppendAllText、大量のデータを少しずつ書き込むならStreamWriterが便利です。

この記事では、「csharp write file」という検索キーワードで調べている方に向けて、C#でファイルを書き込む基本から、追記、上書き、文字コード、非同期処理、例外処理までコード例付きで解説します。

1. csharp write fileの検索意図とC#でのファイル書き込みの全体像

1-1. 検索ユーザーが知りたい「C#でファイルを書き込む」とは

「csharp write file」と検索するユーザーは、主に次のような処理を実装したいと考えています。

  • 文字列をテキストファイルとして保存したい

  • 複数行のデータを書き込みたい

  • 既存ファイルへ追記したい

  • ファイルを上書き保存したい

  • UTF-8やShift_JISなどの文字コードを指定したい

  • CSV、JSON、ログファイルを作成したい

  • 大量のデータを効率よく書き込みたい

  • ファイル書き込み時のエラーを処理したい

C#には用途に応じた複数の書き込み方法があります。適切な方法を選ぶには、ファイルサイズ、書き込み回数、上書きか追記か、同期処理か非同期処理かを整理することが重要です。

1-2. C#でファイルを書き込む主な方法の比較

主な書き込み方法は次のとおりです。

方法主な用途動作
File.WriteAllText文字列を一括保存新規作成または上書き
File.WriteAllLines複数行を一括保存新規作成または上書き
File.AppendAllText文字列を追加新規作成または追記
File.AppendAllLines複数行を追加新規作成または追記
StreamWriter逐次書き込み、大量データ上書きまたは追記を選択可能
File.WriteAllTextAsync非同期で一括保存新規作成または上書き
StreamWriter.WriteAsync非同期で逐次書き込み上書きまたは追記を選択可能

数行程度の小さなファイルなら、コードが簡潔なFileクラスが適しています。大量のデータや、ループ内で少しずつ書き込みたい場合はStreamWriterが適しています。

1-3. まず押さえるべきSystem.IO名前空間

C#でファイルやディレクトリを操作するには、基本的にSystem.IO名前空間を使用します。

C#
using System.IO;

System.IOには、次のようなクラスがあります。

  • File:ファイルの作成、読み込み、書き込み、コピー、削除

  • Directory:ディレクトリの作成、確認、削除

  • Path:パスの結合、拡張子やファイル名の取得

  • StreamWriter:文字データの逐次書き込み

  • FileStream:バイト単位のファイル操作

最近のC#では暗黙的なusingが有効になっているプロジェクトもありますが、サンプルコードや共通ライブラリでは必要な名前空間を明示すると分かりやすくなります。

1-4. 上書き・追記・新規作成の違い

ファイル書き込みでは、既存ファイルに対する動作を確認しておく必要があります。

上書きは、既存ファイルの内容を削除して、新しい内容に置き換える処理です。

C#
File.WriteAllText("sample.txt", "新しい内容");

追記は、既存ファイルの末尾へ内容を追加する処理です。

C#
File.AppendAllText("sample.txt", "追加する内容");

新規作成は、ファイルが存在しない場合に新しいファイルを作る処理です。WriteAllTextAppendAllTextは、対象ファイルがなければ自動的に作成します。

ただし、保存先のディレクトリまでは自動作成されません。ディレクトリが存在しない場合は、事前にDirectory.CreateDirectoryを実行する必要があります。

2. C#でファイルを書き込む最も基本的な方法

2-1. File.WriteAllTextで文字列をファイルに書き込む

文字列をファイルへ保存する最も簡単な方法は、File.WriteAllTextです。

C#
using System.IO;

string path = "sample.txt";
string content = "C#でファイルを書き込みます。";

File.WriteAllText(path, content);

このコードを実行すると、指定したパスにsample.txtが作成され、文字列が保存されます。

絶対パスを指定することも可能です。

C#
string path = @"C:\work\sample.txt";
File.WriteAllText(path, "ファイルの内容");

文字列の先頭に@を付けた逐語的文字列リテラルを使うと、Windowsパスのバックスラッシュを重ねて記述する必要がありません。

2-2. ファイルが存在しない場合の動作

File.WriteAllTextで指定したファイルが存在しない場合、そのファイルは自動的に新規作成されます。

C#
string path = "new-file.txt";

File.WriteAllText(path, "新しく作成されたファイルです。");

ただし、親ディレクトリが存在しない場合はDirectoryNotFoundExceptionが発生します。

次のコードでは、書き込み前にディレクトリを作成しています。

C#
using System.IO;

string directoryPath = @"C:\work\data";
string filePath = Path.Combine(directoryPath, "sample.txt");

Directory.CreateDirectory(directoryPath);
File.WriteAllText(filePath, "保存するデータ");

Directory.CreateDirectoryは、対象ディレクトリがすでに存在していても基本的にエラーにならないため、存在確認を分けずに実行できます。

2-3. 既存ファイルがある場合は上書きされる点に注意

File.WriteAllTextの対象ファイルがすでに存在する場合、元の内容は削除され、新しい内容に置き換わります。

たとえば、ファイルに次の内容が保存されているとします。

元のデータ

次のコードを実行します。

C#
File.WriteAllText("sample.txt", "新しいデータ");

実行後の内容は次のようになります。

新しいデータ

「元のデータ」は残りません。既存データを残したい場合は、File.AppendAllTextまたは追記モードのStreamWriterを使用してください。

2-4. using System.IOを使った基本コード例

コンソールアプリケーションでファイルを書き込む基本例は次のとおりです。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = "message.txt";
string message = "Hello, C# file writing!";

File.WriteAllText(path, message);

Console.WriteLine($"ファイルを保存しました: {Path.GetFullPath(path)}");
}
}

相対パスを使用した場合、実際の保存先はプログラムのカレントディレクトリによって決まります。Path.GetFullPathを使うと、実際に参照される絶対パスを確認できます。

3. 複数行のテキストをファイルに書き込む方法

3-1. File.WriteAllLinesで配列やリストを書き込む

文字列の配列やリストを1要素1行として保存するには、File.WriteAllLinesを使用します。

C#
using System.IO;

string[] lines =
{
"1行目",
"2行目",
"3行目"
};

File.WriteAllLines("lines.txt", lines);

List<string>も書き込めます。

C#
using System.Collections.Generic;
using System.IO;

var lines = new List<string>
{
"りんご",
"みかん",
"ぶどう"
};

File.WriteAllLines("fruits.txt", lines);

WriteAllLinesは、配列やコレクションの各要素の後ろに改行を付けて保存します。

3-2. 改行を含む文字列をWriteAllTextで書き込む

複数行の文字列を作成し、WriteAllTextでまとめて書き込むこともできます。

C#
using System;
using System.IO;

string content =
"1行目" + Environment.NewLine +
"2行目" + Environment.NewLine +
"3行目";

File.WriteAllText("multiline.txt", content);

C#の複数行文字列を使う方法もあります。

C#
string content = """
1行目
2行目
3行目
""";

File.WriteAllText("multiline.txt", content);

Raw文字列リテラルはC# 11以降で利用できます。古い言語バージョンを使用している場合は、Environment.NewLineや逐語的文字列を利用してください。

3-3. WriteLineを使って1行ずつ書き込む

1行ずつ順番に書き込みたい場合は、StreamWriter.WriteLineを使用します。

C#
using System.IO;

using (var writer = new StreamWriter("output.txt"))
{
writer.WriteLine("1行目");
writer.WriteLine("2行目");
writer.WriteLine("3行目");
}

ループ処理との組み合わせにも適しています。

C#
using System.IO;

using (var writer = new StreamWriter("numbers.txt"))
{
for (int i = 1; i <= 100; i++)
{
writer.WriteLine($"番号: {i}");
}
}

WriteLineは書き込んだ文字列の後ろに改行を追加します。改行を付けたくない場合はWriteを使います。

3-4. 改行コードを扱うときの注意点

改行コードはOSによって異なります。

  • Windows:\r\n

  • Linux:\n

  • macOS:通常は\n

実行環境に適した改行を使用する場合は、Environment.NewLineを使います。

C#
string text =
"1行目" + Environment.NewLine +
"2行目";

出力形式として改行コードが明確に決められている場合は、\n\r\nを明示します。

C#
string lfText = "1行目\n2行目";
string crlfText = "1行目\r\n2行目";

CSVや外部システムとの連携では、相手側が期待する改行コードを確認してください。

4. C#でファイルに追記する方法

4-1. File.AppendAllTextで既存ファイルに追記する

既存ファイルの末尾へ文字列を追加するには、File.AppendAllTextを使用します。

C#
using System;
using System.IO;

string path = "log.txt";
string message = $"処理を開始しました。{Environment.NewLine}";

File.AppendAllText(path, message);

対象ファイルが存在しない場合は、新しいファイルが作成されます。

AppendAllTextは自動で改行を追加しないため、1件ずつ別の行に記録したい場合はEnvironment.NewLineを付けます。

C#
File.AppendAllText(
"log.txt",
"追加するメッセージ" + Environment.NewLine
);

4-2. File.AppendAllLinesで複数行を追記する

複数行をまとめて追加したい場合は、File.AppendAllLinesを使用します。

C#
using System.IO;

string[] newLines =
{
"追加データ1",
"追加データ2",
"追加データ3"
};

File.AppendAllLines("data.txt", newLines);

各要素は1行ずつファイルの末尾へ追加されます。対象ファイルが存在しない場合は新規作成されます。

4-3. StreamWriterのappend指定で追記する

StreamWriterのコンストラクターでappendtrueを指定すると、追記モードになります。

C#
using System.IO;

string path = "log.txt";

using (var writer = new StreamWriter(path, append: true))
{
writer.WriteLine("追記する内容");
}

append: falseを指定すると上書きモードです。

C#
using (var writer = new StreamWriter("output.txt", append: false))
{
writer.WriteLine("ファイルを上書きします。");
}

名前付き引数を使うと、truefalseが何を意味しているか分かりやすくなります。

4-4. ログファイルへの追記コード例

日時とログレベルを付けて記録する例です。

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

public static class Logger
{
private static readonly string LogPath = "application.log";

public static void WriteLog(string level, string message)
{
string logLine =
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss} [{level}] {message}";

using var writer = new StreamWriter(
LogPath,
append: true,
Encoding.UTF8
);

writer.WriteLine(logLine);
}
}

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

C#
Logger.WriteLog("INFO", "アプリケーションを起動しました。");
Logger.WriteLog("ERROR", "データの保存に失敗しました。");

複数のスレッドや複数のプロセスから同じログファイルへ同時に書き込む場合は、排他制御や専用のロギングライブラリも検討してください。

5. C#でファイルを上書き保存する方法

5-1. File.WriteAllTextによる上書き保存

File.WriteAllTextは、既存ファイルがある場合に内容を上書きします。

C#
using System.IO;

string path = "settings.txt";
string content = "Theme=Dark";

File.WriteAllText(path, content);

設定情報や、常に最新の状態だけを保存したいファイルに向いています。

5-2. StreamWriterで上書きモードを指定する

StreamWriterを上書きモードで開く場合は、appendfalseを指定します。

C#
using System.IO;

using var writer = new StreamWriter(
"report.txt",
append: false
);

writer.WriteLine("最新のレポート");
writer.WriteLine("古い内容は削除されます。");

StreamWriter(string path)のようにappendを省略した場合も、基本的には上書きモードです。

5-3. 上書き前にバックアップを作成する方法

重要なファイルを上書きする場合は、事前にバックアップを作成すると安全です。

C#
using System;
using System.IO;

string path = "settings.json";
string backupPath =
$"settings_{DateTime.Now:yyyyMMddHHmmss}.bak";

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

File.WriteAllText(path, "{ \"theme\": \"dark\" }");

同じバックアップ名が存在する可能性がある場合は、日時にミリ秒やGUIDを含める方法があります。

C#
string backupPath =
$"settings_{DateTime.Now:yyyyMMddHHmmssfff}.bak";

5-4. 誤ってデータを消さないための確認ポイント

上書き処理を実行する前に、次の点を確認してください。

  • 保存先のパスが正しいか

  • 上書き対象のファイル名が正しいか

  • 追記すべきデータではないか

  • 必要に応じてバックアップを作成しているか

  • 書き込み途中で失敗した場合の復旧方法があるか

特に、変数からパスを組み立てる場合は、書き込み直前にPath.GetFullPathで保存先を確認すると安全です。

C#
string path = Path.GetFullPath("settings.json");
Console.WriteLine($"保存先: {path}");

File.WriteAllText(path, "新しい設定");

重要なデータでは、一時ファイルへ書き込んでから置き換える方法も有効です。

C#
using System.IO;

string path = "settings.txt";
string tempPath = path + ".tmp";

File.WriteAllText(tempPath, "新しい設定");

File.Move(tempPath, path, overwrite: true);

ただし、厳密な原子性やバックアップが必要な場合は、対象OSやファイルシステムの仕様も考慮してください。

6. StreamWriterを使ってファイルを書き込む方法

6-1. StreamWriterが向いているケース

StreamWriterは、次のような処理に向いています。

  • 大量のデータを少しずつ書き込む

  • ループ内で1行ずつ書き込む

  • 書き込み内容をすべてメモリ上に保持したくない

  • WriteWriteLineを使い分けたい

  • 文字コードを明示したい

  • 書き込み途中でFlushしたい

File.WriteAllTextは文字列全体を用意してから書き込みます。一方、StreamWriterは順番にデータを書き込めるため、大きなファイルを生成するときに扱いやすい方法です。

6-2. using文で安全にファイルを閉じる

StreamWriterを使用した後は、ファイルを閉じてリソースを解放する必要があります。通常はusing文を使います。

C#
using System.IO;

using (var writer = new StreamWriter("sample.txt"))
{
writer.WriteLine("ファイルへ書き込みます。");
}

処理がusingブロックを抜けると、例外が発生した場合でもDisposeが呼び出され、ファイルが閉じられます。

C# 8以降では、using宣言も使用できます。

C#
using var writer = new StreamWriter("sample.txt");

writer.WriteLine("1行目");
writer.WriteLine("2行目");

変数のスコープが終了した時点でリソースが解放されます。

6-3. WriteとWriteLineの違い

Writeは、文字列の後ろに改行を追加しません。

C#
using var writer = new StreamWriter("sample.txt");

writer.Write("Hello, ");
writer.Write("C#");

出力結果は次のとおりです。

Hello, C#

WriteLineは、文字列の後ろに改行を追加します。

C#
using var writer = new StreamWriter("sample.txt");

writer.WriteLine("Hello");
writer.WriteLine("C#");

出力結果は次のとおりです。

Hello
C#

同じ行に複数の値を出力したい場合はWrite、レコード単位で行を分けたい場合はWriteLineが便利です。

6-4. 大量データを書き込む場合の考え方

大量データを扱う場合、すべての内容を巨大な文字列へ結合してからWriteAllTextを呼び出すと、多くのメモリを消費することがあります。

次のように、データを取得しながらStreamWriterへ書き込む方法が適しています。

C#
using System.IO;

using var writer = new StreamWriter("large-data.txt");

for (int i = 1; i <= 1_000_000; i++)
{
writer.WriteLine($"データ番号: {i}");
}

StreamWriterは内部バッファを利用するため、通常は1文字ずつ直接ディスクへ書き込むわけではありません。

書き込み内容を途中で確実に反映させたい場合はFlushを使用できます。

C#
writer.WriteLine("重要なデータ");
writer.Flush();

ただし、頻繁なFlushは書き込み性能を低下させることがあります。ログなどで即時反映が必要な場面に限定して使用するのが一般的です。

7. 文字コードを指定してファイルを書き込む方法

7-1. UTF-8でファイルを書き込む

UTF-8を明示する場合は、Encoding.UTF8を指定します。

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

string path = "utf8.txt";
string content = "日本語をUTF-8で保存します。";

File.WriteAllText(path, content, Encoding.UTF8);

StreamWriterでも指定できます。

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

using var writer = new StreamWriter(
"utf8.txt",
append: false,
Encoding.UTF8
);

writer.WriteLine("UTF-8で書き込みます。");

BOMの有無を明示したい場合は、UTF8Encodingを生成します。

C#
using System.Text;

var utf8WithoutBom = new UTF8Encoding(
encoderShouldEmitUTF8Identifier: false
);

File.WriteAllText(
"utf8-no-bom.txt",
"BOMなしUTF-8",
utf8WithoutBom
);

BOM付きにする場合はtrueを指定します。

C#
var utf8WithBom = new UTF8Encoding(
encoderShouldEmitUTF8Identifier: true
);

利用する.NETのバージョンやAPIによって既定のBOM動作が異なる可能性を考慮し、外部システムとの連携では明示的に指定すると確実です。

7-2. Shift_JISでファイルを書き込む

Shift_JISを利用する場合は、文字コード名またはコードページを指定してEncodingを取得します。

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

Encoding.RegisterProvider(
CodePagesEncodingProvider.Instance
);

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

File.WriteAllText(
"shift-jis.txt",
"Shift_JISで保存します。",
shiftJis
);

コードページ番号932を指定することもできます。

C#
Encoding shiftJis = Encoding.GetEncoding(932);

.NET Frameworkではそのまま利用できる環境がありますが、現行の.NETではCodePagesEncodingProviderの登録が必要です。また、プロジェクトによってはSystem.Text.Encoding.CodePagesパッケージの参照が必要になります。

7-3. Encodingを指定するコード例

文字コードを引数で切り替えるメソッドを作ることもできます。

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

public static class TextFileWriter
{
public static void Save(
string path,
string content,
Encoding encoding)
{
string? directory = Path.GetDirectoryName(path);

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

File.WriteAllText(path, content, encoding);
}
}

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

C#
TextFileWriter.Save(
"data-utf8.txt",
"UTF-8のデータ",
Encoding.UTF8
);

Shift_JISを指定する場合は、事前にコードページプロバイダーを登録します。

C#
Encoding.RegisterProvider(
CodePagesEncodingProvider.Instance
);

Encoding shiftJis = Encoding.GetEncoding(932);

TextFileWriter.Save(
"data-sjis.txt",
"Shift_JISのデータ",
shiftJis
);

7-4. 文字化けを防ぐための注意点

文字化けを防ぐには、書き込み側と読み込み側で同じ文字コードを使用する必要があります。

たとえば、UTF-8で保存したファイルをShift_JISとして開くと、文字化けする可能性があります。

次の点を確認してください。

  • 保存時の文字コード

  • 読み込み時の文字コード

  • BOMの有無

  • 連携先システムが要求する文字コード

  • 使用する文字を対象文字コードで表現できるか

Shift_JISでは表現できない文字があります。絵文字や一部の記号、多言語を扱う場合は、基本的にUTF-8が適しています。

変換できない文字をエラーとして検出したい場合は、例外フォールバックを指定できます。

C#
using System.Text;

Encoding.RegisterProvider(
CodePagesEncodingProvider.Instance
);

Encoding shiftJis = Encoding.GetEncoding(
932,
EncoderFallback.ExceptionFallback,
DecoderFallback.ExceptionFallback
);

この設定では、Shift_JISに変換できない文字が含まれている場合にEncoderFallbackExceptionが発生します。

8. パス指定と保存先の基本

8-1. 絶対パスと相対パスの違い

絶対パスは、ドライブやルートから保存先を指定する方法です。

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

相対パスは、現在のカレントディレクトリを基準に指定します。

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

相対パスの実際の保存先は、実行方法や開発環境によって変わることがあります。

確認するにはPath.GetFullPathを使用します。

C#
string path = @"data\sample.txt";
string fullPath = Path.GetFullPath(path);

Console.WriteLine(fullPath);

「プロジェクトフォルダに保存される」とは限らないため、保存場所を確実に管理したい場合は基準となるディレクトリを明示してください。

8-2. Path.Combineで安全にパスを作成する

ディレクトリ名とファイル名を結合する場合は、文字列の連結ではなくPath.Combineを使用します。

C#
using System.IO;

string directory = @"C:\work\data";
string fileName = "sample.txt";

string path = Path.Combine(directory, fileName);

Path.Combineを使用すると、OSに適したディレクトリ区切り文字を考慮してパスを作成できます。

複数階層も指定できます。

C#
string path = Path.Combine(
"data",
"logs",
"application.log"
);

ファイル名を外部入力から取得する場合は、../などによる意図しないパス移動や、使用できない文字にも注意が必要です。

8-3. 実行ファイルと同じフォルダに保存する方法

実行ファイルの基準ディレクトリは、AppContext.BaseDirectoryで取得できます。

C#
using System;
using System.IO;

string path = Path.Combine(
AppContext.BaseDirectory,
"sample.txt"
);

File.WriteAllText(path, "実行ファイルの基準フォルダに保存");

ただし、アプリケーションの配置先によっては、そのフォルダへの書き込み権限がない場合があります。

ユーザーごとの設定やデータを保存する場合は、Environment.GetFolderPathを使って適切な保存先を取得する方法があります。

C#
using System;
using System.IO;

string appDataDirectory = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData
),
"MyApplication"
);

Directory.CreateDirectory(appDataDirectory);

string path = Path.Combine(
appDataDirectory,
"settings.json"
);

8-4. ディレクトリが存在しない場合に作成する方法

ファイルを書き込む前に、保存先ディレクトリを作成します。

C#
using System.IO;

string directory = Path.Combine(
"data",
"reports"
);

Directory.CreateDirectory(directory);

string path = Path.Combine(
directory,
"report.txt"
);

File.WriteAllText(path, "レポート内容");

フルパスから親ディレクトリを取得する方法もあります。

C#
string path = @"C:\work\data\report.txt";
string? directory = Path.GetDirectoryName(path);

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

File.WriteAllText(path, "保存する内容");

ファイル名だけの相対パスでは、Path.GetDirectoryNameが空文字列またはnull相当になる場合があるため、確認してから作成します。

9. 非同期でファイルを書き込む方法

9-1. File.WriteAllTextAsyncの基本

非同期で文字列をファイルへ保存するには、File.WriteAllTextAsyncを使用します。

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

class Program
{
static async Task Main()
{
string path = "async.txt";
string content = "非同期で書き込みます。";

await File.WriteAllTextAsync(path, content);
}
}

awaitを付けることで、書き込みの完了を非同期に待機できます。

呼び出し元のメソッドには、通常asyncを付け、戻り値をTaskまたはTask<T>にします。

9-2. StreamWriter.WriteAsyncの使い方

StreamWriterでは、WriteAsyncまたはWriteLineAsyncを利用できます。

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

public static async Task SaveAsync()
{
using var writer = new StreamWriter("output.txt");

await writer.WriteAsync("非同期で");
await writer.WriteLineAsync("書き込みます。");
}

複数行を書き込む例は次のとおりです。

C#
public static async Task SaveLinesAsync(
IEnumerable<string> lines)
{
using var writer = new StreamWriter("lines.txt");

foreach (string line in lines)
{
await writer.WriteLineAsync(line);
}
}

同じStreamWriterに対する複数の非同期書き込みを、完了を待たずに同時実行しないよう注意してください。

9-3. async/awaitを使ったコード例

ディレクトリを作成し、UTF-8で非同期保存する例です。

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

public static class FileService
{
public static async Task SaveTextAsync(
string path,
string content)
{
string? directory = Path.GetDirectoryName(path);

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

await File.WriteAllTextAsync(
path,
content,
Encoding.UTF8
);
}
}

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

C#
await FileService.SaveTextAsync(
Path.Combine("data", "message.txt"),
"非同期で保存する内容"
);

例外処理を加える場合は、呼び出し側または保存メソッド内でtry-catchを使用します。

9-4. 非同期処理が向いているケース

非同期のファイル書き込みは、次のようなケースに向いています。

  • GUIアプリケーションで画面の応答停止を避けたい

  • ASP.NET Coreなどでスレッドを長時間占有したくない

  • 大きなファイルを保存する

  • 複数のI/O処理を扱う

  • ネットワークストレージへ保存する

ただし、小さなファイルを1回書き込むだけなら、同期処理でも十分な場合があります。非同期化による複雑さと、アプリケーション全体の設計を考慮して選択してください。

また、非同期メソッドを.Result.Wait()で同期的に待つと、環境によってはデッドロックやスレッドの無駄な待機につながります。基本的には呼び出し元までasync/awaitを伝播させます。

10. ファイル書き込み時によくあるエラーと対処法

10-1. UnauthorizedAccessExceptionが出る原因

UnauthorizedAccessExceptionは、ファイルやディレクトリへのアクセス権限がない場合に発生します。

主な原因は次のとおりです。

  • 書き込み権限のないフォルダを指定している

  • ファイルではなくディレクトリのパスを渡している

  • 読み取り専用ファイルを変更しようとしている

  • OSによって保護された場所へ保存しようとしている

たとえば、次のようにディレクトリ自体をファイルとして指定するとエラーになる可能性があります。

C#
File.WriteAllText(@"C:\work", "データ");

ファイル名まで含めて指定します。

C#
File.WriteAllText(
@"C:\work\sample.txt",
"データ"
);

アプリケーションデータは、ユーザーが書き込めるLocalApplicationDataなどへ保存する方法も検討してください。

10-2. DirectoryNotFoundExceptionが出る原因

DirectoryNotFoundExceptionは、指定したパスの親ディレクトリが存在しない場合に発生します。

C#
File.WriteAllText(
@"C:\not-exists\data\sample.txt",
"データ"
);

書き込み前にディレクトリを作成します。

C#
string directory = @"C:\work\data";
Directory.CreateDirectory(directory);

string path = Path.Combine(
directory,
"sample.txt"
);

File.WriteAllText(path, "データ");

ファイルは自動作成されますが、親ディレクトリは自動作成されない点を覚えておきましょう。

10-3. IOExceptionが出る原因

IOExceptionは、ファイル入出力に関するさまざまな問題で発生します。

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

  • ファイルが別の処理によって排他的に使用されている

  • ディスク容量が不足している

  • 同じ名前のファイルやディレクトリが競合している

  • ネットワークドライブへの接続に失敗した

  • ストレージデバイスでエラーが発生した

  • 無効なファイル操作を行った

同じアプリケーション内でStreamWriterを閉じていないことが原因になる場合もあります。

C#
using (var writer = new StreamWriter("sample.txt"))
{
writer.WriteLine("データ");
}

usingを使い、ファイルが確実に閉じられるようにします。

10-4. try-catchで例外処理を行う方法

ファイル書き込みでは、想定される例外を個別に処理できます。

C#
using System;
using System.IO;

try
{
File.WriteAllText(
@"C:\work\sample.txt",
"保存する内容"
);

Console.WriteLine("保存しました。");
}
catch (UnauthorizedAccessException ex)
{
Console.WriteLine(
$"書き込み権限がありません: {ex.Message}"
);
}
catch (DirectoryNotFoundException ex)
{
Console.WriteLine(
$"保存先が見つかりません: {ex.Message}"
);
}
catch (IOException ex)
{
Console.WriteLine(
$"ファイル入出力エラーです: {ex.Message}"
);
}

すべての例外を握りつぶすのではなく、ユーザーへの通知やログ記録、再試行など、アプリケーションに適した処理を行います。

エラー内容をそのまま一般ユーザーへ表示すると、内部パスなどの情報が漏れる可能性があります。利用者向けメッセージと開発者向けログを分けることも重要です。

10-5. ファイルが使用中の場合の対処法

別のアプリケーションや処理がファイルを排他的に開いていると、書き込み時にIOExceptionが発生することがあります。

短時間の競合が想定される場合は、待機して再試行する方法があります。

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

public static async Task WriteWithRetryAsync(
string path,
string content,
int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
await File.WriteAllTextAsync(path, content);
return;
}
catch (IOException) when (attempt < maxRetries)
{
await Task.Delay(500);
}
}
}

無制限に再試行すると処理が終わらなくなるため、回数や待機時間に上限を設けます。

同じアプリケーション内から同時に書き込む場合は、lockSemaphoreSlimを使用して書き込みを直列化する方法があります。

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

public static class SafeLogger
{
private static readonly SemaphoreSlim Semaphore =
new(1, 1);

public static async Task AppendAsync(
string path,
string message)
{
await Semaphore.WaitAsync();

try
{
await File.AppendAllTextAsync(
path,
message + System.Environment.NewLine
);
}
finally
{
Semaphore.Release();
}
}
}

この方法は同じプロセス内での排他制御です。複数プロセス間で同じファイルを共有する場合は、名前付きミューテックスや別の保存方式を検討する必要があります。

11. 実用例で学ぶC#のファイル書き込み

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

ユーザー名と日時をテキストファイルへ保存する例です。

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

string path = Path.Combine(
AppContext.BaseDirectory,
"user-info.txt"
);

string content = $"""
ユーザー名: Taro
保存日時: {DateTime.Now:yyyy-MM-dd HH:mm:ss}
""";

File.WriteAllText(
path,
content,
Encoding.UTF8
);

保存先の書き込み権限が保証されないアプリケーションでは、LocalApplicationDataなどを利用してください。

11-2. ログファイルに追記する例

ログ用ディレクトリを作成し、日付ごとのファイルへ追記する例です。

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

public static void AppendLog(
string level,
string message)
{
string logDirectory = Path.Combine(
AppContext.BaseDirectory,
"logs"
);

Directory.CreateDirectory(logDirectory);

string logPath = Path.Combine(
logDirectory,
$"app-{DateTime.Today:yyyyMMdd}.log"
);

string line =
$"{DateTime.Now:yyyy-MM-dd HH:mm:ss.fff} " +
$"[{level}] {message}";

using var writer = new StreamWriter(
logPath,
append: true,
Encoding.UTF8
);

writer.WriteLine(line);
}

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

C#
AppendLog("INFO", "処理を開始しました。");
AppendLog("WARN", "入力値が空です。");

長期間運用するアプリケーションでは、ログの保存期間、最大サイズ、ローテーションも考慮してください。

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

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

C#
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

static string EscapeCsv(string? value)
{
value ??= string.Empty;

bool needsQuotes =
value.Contains(',') ||
value.Contains('"') ||
value.Contains('\r') ||
value.Contains('\n');

if (value.Contains('"'))
{
value = value.Replace("\"", "\"\"");
}

return needsQuotes ? $"\"{value}\"" : value;
}

var rows = new List<string[]>
{
new[] { "ID", "名前", "備考" },
new[] { "1", "山田太郎", "東京都" },
new[] { "2", "佐藤花子", "カンマ,を含む値" },
new[] { "3", "鈴木一郎", "引用符\"を含む値" }
};

using var writer = new StreamWriter(
"users.csv",
append: false,
new UTF8Encoding(
encoderShouldEmitUTF8Identifier: true
)
);

foreach (string[] row in rows)
{
string line = string.Join(
",",
row.Select(EscapeCsv)
);

writer.WriteLine(line);
}

表計算ソフトや連携先システムによって、文字コード、BOM、改行コード、区切り文字の要件が異なります。出力先の仕様を確認してください。

11-4. JSONファイルを書き込む例

JSONを保存する場合は、文字列を手作業で組み立てるより、System.Text.Jsonを使用すると安全です。

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

var user = new
{
Id = 1,
Name = "山田太郎",
IsActive = true
};

var options = new JsonSerializerOptions
{
WriteIndented = true
};

string json = JsonSerializer.Serialize(
user,
options
);

File.WriteAllText("user.json", json);

非同期で直接ファイルへシリアライズすることもできます。

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

public static async Task SaveJsonAsync<T>(
string path,
T value)
{
var options = new JsonSerializerOptions
{
WriteIndented = true
};

await using FileStream stream = File.Create(path);

await JsonSerializer.SerializeAsync(
stream,
value,
options
);
}

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

C#
var data = new
{
Title = "サンプル",
Count = 10
};

await SaveJsonAsync("data.json", data);

11-5. 設定ファイルを保存する例

設定クラスをJSONとして保存する例です。

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

public class AppSettings
{
public string Theme { get; set; } = "Light";
public int FontSize { get; set; } = 14;
public bool AutoSave { get; set; } = true;
}

public static class SettingsService
{
public static async Task SaveAsync(
string path,
AppSettings settings)
{
string? directory = Path.GetDirectoryName(path);

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

var options = new JsonSerializerOptions
{
WriteIndented = true
};

string json = JsonSerializer.Serialize(
settings,
options
);

await File.WriteAllTextAsync(path, json);
}
}

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

C#
var settings = new AppSettings
{
Theme = "Dark",
FontSize = 16,
AutoSave = true
};

string settingsPath = Path.Combine(
Environment.GetFolderPath(
Environment.SpecialFolder.LocalApplicationData
),
"MyApplication",
"settings.json"
);

await SettingsService.SaveAsync(
settingsPath,
settings
);

パスワードやAPIキーなどの秘密情報を、暗号化せずに通常の設定ファイルへ保存するのは避けてください。

12. C#でファイルを書き込むときのベストプラクティス

12-1. 小さなファイルはFileクラスを使う

短いテキストや設定ファイルなど、内容全体をメモリ上に保持しても問題がない場合は、Fileクラスが簡潔です。

C#
File.WriteAllText(
"message.txt",
"保存する内容"
);

複数行ならWriteAllLinesを使用できます。

C#
File.WriteAllLines(
"items.txt",
new[] { "item1", "item2", "item3" }
);

コード量が少なく、リソースのクローズ処理も内部で行われるため、単純なファイル書き込みに適しています。

12-2. 大きなファイルや逐次書き込みはStreamWriterを使う

大量のデータを生成しながら保存する場合は、StreamWriterを使用します。

C#
using var writer = new StreamWriter("large.txt");

foreach (var item in GetLargeData())
{
writer.WriteLine(item);
}

この方法なら、全データを1つの巨大な文字列に変換する必要がありません。

StreamWriterは必ずusingで管理し、正常終了時だけでなく例外発生時にもファイルが閉じられるようにします。

12-3. パス・文字コード・例外処理を明示する

環境による違いや予期しないエラーを減らすには、次の項目を明示します。

  • 保存先ディレクトリ

  • ファイル名

  • 上書きか追記か

  • 文字コード

  • BOMの有無

  • 改行コード

  • 例外発生時の処理

たとえば、次のコードは保存先、追記モード、文字コードを明示しています。

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

string directory = Path.Combine(
AppContext.BaseDirectory,
"logs"
);

Directory.CreateDirectory(directory);

string path = Path.Combine(
directory,
"application.log"
);

using var writer = new StreamWriter(
path,
append: true,
Encoding.UTF8
);

writer.WriteLine("ログメッセージ");

12-4. 上書きと追記を用途に応じて使い分ける

上書きと追記の選択は、保存するデータの性質によって決まります。

上書きが向いているもの

  • 設定ファイル

  • 最新状態だけが必要なデータ

  • 定期的に再生成するレポート

  • キャッシュファイル

追記が向いているもの

  • ログファイル

  • 操作履歴

  • 監査記録

  • 時系列データ

上書きでは既存内容が失われるため、重要なファイルはバックアップや一時ファイルを利用します。追記ではファイルが大きくなり続けるため、ローテーションや保存期間の管理が必要です。

13. csharp write fileに関するよくある質問

13-1. C#でファイルがなければ作成して書き込める?

はい。File.WriteAllTextFile.WriteAllLinesFile.AppendAllTextFile.AppendAllLinesは、対象ファイルが存在しない場合に新しいファイルを作成します。

C#
File.WriteAllText(
"new-file.txt",
"新しいファイルです。"
);

ただし、親ディレクトリは自動作成されません。

C#
string directory = "data";
Directory.CreateDirectory(directory);

string path = Path.Combine(
directory,
"new-file.txt"
);

File.WriteAllText(path, "保存する内容");

13-2. C#でファイルに追記するにはどのメソッドを使う?

短い文字列ならFile.AppendAllText、複数行ならFile.AppendAllLinesを使用します。

C#
File.AppendAllText(
"log.txt",
"追加内容" + Environment.NewLine
);

繰り返し書き込む場合や、大量データを扱う場合は、追記モードのStreamWriterが適しています。

C#
using var writer = new StreamWriter(
"log.txt",
append: true
);

writer.WriteLine("追加内容");

13-3. WriteAllTextとStreamWriterの違いは?

File.WriteAllTextは、文字列全体を一度にファイルへ保存するための簡潔なメソッドです。

C#
File.WriteAllText(
"sample.txt",
"ファイル全体の内容"
);

StreamWriterは、複数回に分けて逐次書き込めます。

C#
using var writer = new StreamWriter("sample.txt");

writer.WriteLine("1行目");
writer.WriteLine("2行目");

小さなファイルや単純な保存にはWriteAllText、大きなファイルやループ内での書き込みにはStreamWriterが向いています。

13-4. C#でファイルを上書きせずに書き込む方法は?

既存ファイルを残したまま末尾へ追加するなら、File.AppendAllTextを使用します。

C#
File.AppendAllText(
"data.txt",
"追加するデータ" + Environment.NewLine
);

既存ファイルがある場合に書き込み自体を行いたくないなら、File.Existsで確認できます。

C#
string path = "data.txt";

if (!File.Exists(path))
{
File.WriteAllText(
path,
"新規ファイルの内容"
);
}

ただし、確認後に別の処理がファイルを作成する競合が起こる可能性があります。ファイルが存在するときに必ず失敗させたい場合は、FileMode.CreateNewを使用します。

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

string path = "data.txt";

using var stream = new FileStream(
path,
FileMode.CreateNew,
FileAccess.Write
);

using var writer = new StreamWriter(
stream,
Encoding.UTF8
);

writer.WriteLine("新規ファイルの内容");

FileMode.CreateNewは、ファイルがすでに存在する場合にIOExceptionを発生させます。

13-5. C#で文字化けせずにファイルを書き込むには?

保存時と読み込み時に同じ文字コードを指定してください。

UTF-8で保存する例は次のとおりです。

C#
using System.Text;

File.WriteAllText(
"utf8.txt",
"日本語のデータ",
Encoding.UTF8
);

Shift_JISで保存する例は次のとおりです。

C#
using System.Text;

Encoding.RegisterProvider(
CodePagesEncodingProvider.Instance
);

Encoding shiftJis = Encoding.GetEncoding(932);

File.WriteAllText(
"shift-jis.txt",
"日本語のデータ",
shiftJis
);

外部システムや表計算ソフトへ渡す場合は、文字コードだけでなくBOMや改行コードの要件も確認しましょう。

まとめ

C#でファイルを書き込む基本的な方法は、File.WriteAllTextです。

C#
File.WriteAllText(
"sample.txt",
"保存する内容"
);

複数行ならFile.WriteAllLines、既存ファイルへの追記ならFile.AppendAllTextまたはFile.AppendAllLinesを使用します。

C#
File.AppendAllText(
"log.txt",
"ログメッセージ" + Environment.NewLine
);

大量データや逐次書き込みにはStreamWriterが適しています。

C#
using var writer = new StreamWriter(
"output.txt",
append: true
);

writer.WriteLine("書き込む内容");

実装時は、上書きと追記の違いだけでなく、保存先のパス、ディレクトリの存在、文字コード、ファイルの使用状況、アクセス権限にも注意が必要です。

小さなファイルはFileクラス、大きなファイルや繰り返し書き込みはStreamWriterという基準で使い分けると、シンプルで安全なファイル書き込み処理を実装できます。