C#でファイル操作を完全解説|読み込み・書き込み・存在確認・パス指定の基本と実践

はじめに

C#では、テキストファイルの読み込み、ログファイルへの書き込み、CSVやJSONの入出力、ファイルの存在確認、コピー、移動、削除など、さまざまなファイル操作を簡単に実装できます。

特に業務アプリケーションやWebアプリケーションでは、設定ファイルの読み込み、アップロードファイルの保存、ログ出力、データのエクスポートなどでファイル操作が頻繁に登場します。

C#のファイル操作では、主にSystem.IO名前空間に含まれるFileクラス、Directoryクラス、Pathクラス、StreamReaderStreamWriterなどを使用します。

この記事では、C#でファイルを扱うための基本から実践的な使い方まで、サンプルコードを交えながら解説します。「csharp file」「C# file」などのキーワードで調べている方が、読み込み・書き込み・存在確認・パス指定を一通り理解できる内容です。

1. C#のファイル操作でできることと基本知識

1-1. C#で扱う「ファイル操作」とは

C#におけるファイル操作とは、プログラムからファイルやフォルダを扱う処理のことです。

代表的な操作には、次のようなものがあります。

ファイルの読み込み、ファイルへの書き込み、ファイルの存在確認、ファイルの作成、削除、コピー、移動、リネーム、フォルダの作成、フォルダ内のファイル一覧取得などです。

たとえば、アプリケーションの設定をJSONファイルから読み込んだり、処理結果をCSVファイルとして出力したり、エラー情報をログファイルに追記したりする場面で使用します。

C#ではこれらの操作を標準ライブラリだけで実装できるため、基本的なファイル操作であれば外部ライブラリを追加する必要はほとんどありません。

1-2. System.IO名前空間の役割

C#でファイル操作を行う場合、基本的にSystem.IO名前空間を使用します。

C#
using System.IO;

System.IOには、ファイルやディレクトリ、ストリーム、パスを扱うためのクラスが用意されています。

主なクラスは次のとおりです。

C#
File
FileInfo
Directory
DirectoryInfo
Path
StreamReader
StreamWriter
FileStream

Fileクラスはファイルの読み書きやコピー、削除などを静的メソッドで簡単に実行できます。Directoryクラスはフォルダ操作に使います。Pathクラスはファイルパスの結合や拡張子の取得などに便利です。

1-3. Fileクラス・FileInfoクラス・Stream系クラスの違い

C#のファイル操作では、用途に応じて使うクラスを選ぶことが重要です。

Fileクラスは、簡単なファイル操作を行いたい場合に便利です。すべて静的メソッドなので、インスタンスを作成せずに使えます。

C#
string text = File.ReadAllText("sample.txt");
File.WriteAllText("output.txt", "Hello C#");

FileInfoクラスは、特定のファイルに関する情報をオブジェクトとして扱いたい場合に使います。ファイルサイズ、作成日時、更新日時などを取得しやすいのが特徴です。

C#
FileInfo fileInfo = new FileInfo("sample.txt");

Console.WriteLine(fileInfo.Name);
Console.WriteLine(fileInfo.Length);
Console.WriteLine(fileInfo.CreationTime);

StreamReaderStreamWriterなどのStream系クラスは、大きなファイルを少しずつ読み書きしたい場合や、細かく制御したい場合に向いています。

C#
using StreamReader reader = new StreamReader("large.txt");

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

簡単な処理にはFileクラス、大容量ファイルや細かい制御にはStream系クラスを使うとよいでしょう。

1-4. ファイル操作でよく使うメソッド一覧

C#のファイル操作でよく使うメソッドをまとめると、次のようになります。

C#
File.Exists(path)              // ファイルの存在確認
File.ReadAllText(path) // テキストを一括読み込み
File.ReadAllLines(path) // 行単位で読み込み
File.WriteAllText(path, text) // テキストを書き込み
File.WriteAllLines(path, lines)// 複数行を書き込み
File.AppendAllText(path, text) // テキストを追記
File.Create(path) // ファイル作成
File.Delete(path) // ファイル削除
File.Copy(source, dest) // ファイルコピー
File.Move(source, dest) // ファイル移動・リネーム

パス操作では、次のメソッドがよく使われます。

C#
Path.Combine(dir, fileName)
Path.GetFileName(path)
Path.GetExtension(path)
Path.GetDirectoryName(path)
Path.ChangeExtension(path, ".bak")

フォルダ操作では、次のメソッドをよく使用します。

C#
Directory.Exists(path)
Directory.CreateDirectory(path)
Directory.GetFiles(path)
Directory.GetDirectories(path)
Directory.Delete(path)

2. C#でファイルを読み込む方法

2-1. File.ReadAllTextでテキストファイルを一括読み込みする

小さなテキストファイルを一括で読み込む場合は、File.ReadAllTextが便利です。

C#
using System;
using System.IO;

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

string content = File.ReadAllText(path);

Console.WriteLine(content);
}
}

File.ReadAllTextは、指定したファイルの内容をすべて文字列として読み込みます。設定ファイルや短いテキストファイルを扱う場合に適しています。

ただし、大容量ファイルを一括で読み込むとメモリ使用量が大きくなるため注意が必要です。数百MB以上のファイルを扱う場合は、StreamReaderを使って少しずつ読み込む方法を検討しましょう。

2-2. File.ReadAllLinesで行単位に読み込む

ファイルを行単位で処理したい場合は、File.ReadAllLinesを使います。

C#
using System;
using System.IO;

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

string[] lines = File.ReadAllLines(path);

foreach (string line in lines)
{
Console.WriteLine(line);
}
}
}

File.ReadAllLinesは、ファイル内の各行を文字列配列として取得します。CSVファイルやログファイルのように、1行ずつ処理したいデータに向いています。

ただし、このメソッドもすべての行を一度にメモリへ読み込むため、大きなファイルでは注意が必要です。

2-3. StreamReaderで大きなファイルを効率よく読み込む

大きなファイルを効率よく読み込む場合は、StreamReaderを使います。StreamReaderを使うと、ファイルを1行ずつ読み込めるため、メモリ消費を抑えられます。

C#
using System;
using System.IO;

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

using StreamReader reader = new StreamReader(path);

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

usingを使うことで、読み込み完了後にファイルが自動的に閉じられます。ファイルを開いたままにすると、他の処理からアクセスできなくなることがあるため、リソースの解放は非常に重要です。

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

ファイルを読み込むときに文字化けが発生する場合は、文字コードを明示します。

UTF-8で読み込む例は次のとおりです。

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

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

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

Console.WriteLine(content);
}
}

Shift_JISのファイルを読み込む場合は、環境によって追加設定が必要になることがあります。

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

class Program
{
static void Main()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

string path = "sample_sjis.txt";
Encoding sjis = Encoding.GetEncoding("shift_jis");

string content = File.ReadAllText(path, sjis);

Console.WriteLine(content);
}
}

日本語を含むファイル操作では、文字コードの違いによる文字化けがよく発生します。特に古いWindowsアプリケーションやCSVファイルでは、Shift_JISが使われていることがあるため注意しましょう。

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

WebアプリケーションやGUIアプリケーションでは、ファイル読み込み中に処理を止めたくない場合があります。そのような場合は、非同期メソッドを使います。

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

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

string content = await File.ReadAllTextAsync(path);

Console.WriteLine(content);
}
}

行単位で非同期に読み込む場合は、次のように書けます。

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

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

string[] lines = await File.ReadAllLinesAsync(path);

foreach (string line in lines)
{
Console.WriteLine(line);
}
}
}

非同期処理を使うことで、ファイルI/Oの待ち時間に他の処理を進めやすくなります。

3. C#でファイルに書き込む方法

3-1. File.WriteAllTextでファイルを新規作成・上書きする

テキストファイルに文字列を書き込む場合は、File.WriteAllTextを使います。

C#
using System.IO;

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

File.WriteAllText(path, text);
}
}

指定したファイルが存在しない場合は新規作成されます。すでに存在する場合は、内容が上書きされます。

上書きしたくない場合は、事前にFile.Existsで存在確認を行うか、追記用のFile.AppendAllTextを使用します。

3-2. File.WriteAllLinesで複数行を書き込む

複数行のテキストを書き込む場合は、File.WriteAllLinesが便利です。

C#
using System.IO;

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

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

File.WriteAllLines(path, lines);
}
}

配列やリストの内容を1行ずつファイルに出力できます。

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

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

List<string> names = new List<string>
{
"Tanaka",
"Suzuki",
"Sato"
};

File.WriteAllLines(path, names);
}
}

CSVやログの簡易出力にも利用できます。

3-3. File.AppendAllTextで追記する

既存ファイルの末尾にテキストを追加したい場合は、File.AppendAllTextを使います。

C#
using System;
using System.IO;

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

string log = $"{DateTime.Now}: 処理を開始しました。{Environment.NewLine}";

File.AppendAllText(path, log);
}
}

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

Environment.NewLineを使うと、実行環境に応じた改行コードを使用できます。

3-4. StreamWriterで細かく書き込み処理を制御する

書き込み処理を細かく制御したい場合は、StreamWriterを使います。

C#
using System.IO;

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

using StreamWriter writer = new StreamWriter(path);

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

追記モードで開く場合は、コンストラクタの第2引数にtrueを指定します。

C#
using System.IO;

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

using StreamWriter writer = new StreamWriter(path, append: true);

writer.WriteLine("ログを追記します。");
}
}

大量のデータを書き込む場合や、ループ内で少しずつ出力したい場合にはStreamWriterが適しています。

3-5. 文字コードを指定して書き込む方法

ファイルを書き込むときも、必要に応じて文字コードを指定できます。

UTF-8で書き込む例です。

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

class Program
{
static void Main()
{
string path = "utf8.txt";
string text = "UTF-8で書き込みます。";

File.WriteAllText(path, text, Encoding.UTF8);
}
}

Shift_JISで書き込む場合は、次のようにします。

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

class Program
{
static void Main()
{
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);

string path = "sjis.txt";
string text = "Shift_JISで書き込みます。";
Encoding sjis = Encoding.GetEncoding("shift_jis");

File.WriteAllText(path, text, sjis);
}
}

外部システムに渡すCSVファイルなどでは、文字コード指定が重要になることがあります。相手システムがUTF-8を想定しているのか、Shift_JISを想定しているのかを事前に確認しましょう。

3-6. 非同期でファイルに書き込む方法

非同期でファイルに書き込む場合は、File.WriteAllTextAsyncを使います。

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

class Program
{
static async Task Main()
{
string path = "async.txt";
string text = "非同期でファイルに書き込みます。";

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

複数行を書き込む場合は、File.WriteAllLinesAsyncを使います。

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

class Program
{
static async Task Main()
{
string path = "async_lines.txt";

string[] lines =
{
"A",
"B",
"C"
};

await File.WriteAllLinesAsync(path, lines);
}
}

Webアプリケーションでアップロードファイルやログを扱う場合、非同期処理を適切に使うことで応答性を高められます。

4. C#でファイルの存在確認・作成・削除を行う方法

4-1. File.Existsでファイルの存在を確認する

ファイルが存在するかどうかを確認するには、File.Existsを使います。

C#
using System;
using System.IO;

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

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

File.Existsは、指定したパスにファイルが存在する場合にtrueを返します。存在しない場合や、パスが無効な場合はfalseを返します。

ただし、存在確認をした直後に別の処理がファイルを削除する可能性もあります。そのため、重要な処理では例外処理も組み合わせることが大切です。

4-2. ファイルが存在しない場合に作成する

ファイルが存在しない場合だけ作成する例です。

C#
using System.IO;

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

if (!File.Exists(path))
{
using FileStream fs = File.Create(path);
}
}
}

File.CreateFileStreamを返します。作成後にすぐ閉じる必要があるため、usingを使って確実に解放します。

テキストを同時に書き込む場合は、File.WriteAllTextでも構いません。

C#
using System.IO;

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

if (!File.Exists(path))
{
File.WriteAllText(path, "初期内容");
}
}
}

4-3. File.Deleteでファイルを削除する

ファイルを削除するには、File.Deleteを使います。

C#
using System.IO;

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

if (File.Exists(path))
{
File.Delete(path);
}
}
}

File.Deleteは、指定したファイルを削除します。ファイルが存在しない場合でも例外は発生しませんが、アクセス権がない場合やファイルが使用中の場合は例外が発生することがあります。

安全に削除するには、try-catchを使います。

C#
using System;
using System.IO;

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

try
{
File.Delete(path);
Console.WriteLine("削除しました。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("削除権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"削除できませんでした: {ex.Message}");
}
}
}

4-4. File.Copyでファイルをコピーする

ファイルをコピーするには、File.Copyを使います。

C#
using System.IO;

class Program
{
static void Main()
{
string sourcePath = "source.txt";
string destinationPath = "copy.txt";

File.Copy(sourcePath, destinationPath);
}
}

コピー先に同名ファイルがすでに存在する場合、例外が発生します。上書きコピーしたい場合は、第3引数にtrueを指定します。

C#
File.Copy(sourcePath, destinationPath, overwrite: true);

コピー元ファイルの存在確認や、コピー先ディレクトリの存在確認も忘れずに行うと安全です。

4-5. File.Moveでファイルを移動・リネームする

ファイルを移動するには、File.Moveを使います。

C#
using System.IO;

class Program
{
static void Main()
{
string sourcePath = "old.txt";
string destinationPath = "folder/new.txt";

File.Move(sourcePath, destinationPath);
}
}

同じフォルダ内でファイル名だけ変える場合は、リネームとして使えます。

C#
File.Move("oldname.txt", "newname.txt");

移動先に同名ファイルがある場合は、環境や指定方法によって例外が発生することがあります。上書きしたい場合は、事前に削除するか、上書き対応のメソッドを検討しましょう。

4-6. 存在確認時に注意すべきパスと権限の問題

File.Existsfalseが返る場合でも、必ずしもファイルが存在しないとは限りません。

たとえば、次のようなケースがあります。

パスが間違っている、相対パスの基準ディレクトリが想定と違う、アクセス権限がない、ファイルではなくディレクトリを指定している、ネットワークドライブに接続できない、などです。

特に相対パスを使っている場合、実行時のカレントディレクトリが想定と異なることでファイルが見つからないことがあります。

確認用に、次のように現在の作業ディレクトリを出力してみると原因を特定しやすくなります。

C#
Console.WriteLine(Environment.CurrentDirectory);

ファイル操作では、存在確認だけに頼らず、例外処理も組み合わせることが重要です。

5. C#でファイルパスを正しく指定する方法

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

ファイルパスには、絶対パスと相対パスがあります。

絶対パスは、ドライブ名やルートディレクトリから始まる完全なパスです。

C#
string path = @"C:\Users\user\Documents\sample.txt";

LinuxやmacOSでは次のような形式になります。

C#
string path = "/home/user/sample.txt";

相対パスは、現在の作業ディレクトリを基準にしたパスです。

C#
string path = "data/sample.txt";

相対パスは短く書けますが、実行環境によって基準位置が変わることがあります。そのため、本番環境では絶対パスや設定ファイルで管理したパスを使うことが多いです。

5-2. 実行ファイルから見た相対パスの考え方

C#で相対パスを指定した場合、基準になるのは多くの場合、実行時のカレントディレクトリです。

現在のカレントディレクトリは、次のコードで確認できます。

C#
Console.WriteLine(Environment.CurrentDirectory);

また、アプリケーションの実行ファイルが配置されているディレクトリを取得したい場合は、次のようにします。

C#
string baseDir = AppContext.BaseDirectory;
Console.WriteLine(baseDir);

実行ファイルの場所を基準にファイルパスを作る場合は、AppContext.BaseDirectoryPath.Combineを組み合わせると安全です。

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

Visual Studioで開発している場合、デバッグ実行時の出力先はbin/Debug/...配下になることがあります。相対パスでファイルが見つからない場合は、実際の実行ディレクトリを確認しましょう。

5-3. Path.Combineで安全にパスを結合する

ファイルパスを文字列連結で作るのは避けましょう。

悪い例です。

C#
string path = dir + "\\" + fileName;

この書き方は、OSによって区切り文字が異なる場合に問題になることがあります。また、余分なスラッシュや不足が発生しやすくなります。

安全にパスを結合するには、Path.Combineを使います。

C#
string dir = "data";
string fileName = "sample.txt";

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

複数階層のパスも簡単に作れます。

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

C# file操作では、パスの扱いがバグの原因になりやすいため、Path.Combineを使う習慣をつけることが大切です。

5-4. ファイル名・拡張子・ディレクトリ名を取得する

Pathクラスを使うと、ファイルパスからファイル名や拡張子などを簡単に取得できます。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string path = @"C:\data\sample.txt";

Console.WriteLine(Path.GetFileName(path));
Console.WriteLine(Path.GetFileNameWithoutExtension(path));
Console.WriteLine(Path.GetExtension(path));
Console.WriteLine(Path.GetDirectoryName(path));
}
}

出力例は次のようになります。

sample.txt
sample
.txt
C:\data

拡張子を変更したい場合は、Path.ChangeExtensionを使います。

C#
string newPath = Path.ChangeExtension("sample.txt", ".bak");
Console.WriteLine(newPath);

一時ファイル名を取得する場合は、Path.GetTempFileNamePath.GetRandomFileNameも利用できます。

5-5. Windows・Linux・macOSで異なるパス区切り文字への対応

Windowsではパス区切り文字に\が使われますが、LinuxやmacOSでは/が使われます。

そのため、クロスプラットフォーム対応のアプリケーションでは、区切り文字を直接書くのではなく、Path.Combineを使うのが基本です。

C#
string path = Path.Combine("data", "sample.txt");

区切り文字を取得したい場合は、次のプロパティを使えます。

C#
char separator = Path.DirectorySeparatorChar;
Console.WriteLine(separator);

.NETはWindows、Linux、macOSで動作するため、ファイルパスの書き方にも注意が必要です。特にDockerやLinuxサーバーでC#アプリケーションを動かす場合、Windows前提のパスを書いているとエラーになることがあります。

5-6. appsettings.jsonや設定ファイルでパスを管理する方法

実務では、ファイルパスをソースコードに直接書かず、設定ファイルで管理することが多いです。

たとえば、appsettings.jsonに次のように書きます。

JSON
{
"FileSettings": {
"InputPath": "data/input.csv",
"OutputPath": "data/output.csv",
"LogPath": "logs/app.log"
}
}

ASP.NET Coreなどでは、設定を読み込んで使用できます。

C#
string inputPath = configuration["FileSettings:InputPath"];

パスを設定ファイルで管理すると、開発環境、本番環境、テスト環境で異なるパスを使いやすくなります。

ただし、設定ファイルに書かれたパスが存在するとは限らないため、起動時や処理前に存在確認を行うと安全です。

6. C#でディレクトリを操作する方法

6-1. Directory.Existsでフォルダの存在を確認する

フォルダが存在するか確認するには、Directory.Existsを使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string dir = "data";

if (Directory.Exists(dir))
{
Console.WriteLine("フォルダは存在します。");
}
else
{
Console.WriteLine("フォルダは存在しません。");
}
}
}

ファイルの存在確認にはFile.Exists、フォルダの存在確認にはDirectory.Existsを使います。混同しないように注意しましょう。

6-2. Directory.CreateDirectoryでフォルダを作成する

フォルダを作成するには、Directory.CreateDirectoryを使います。

C#
using System.IO;

class Program
{
static void Main()
{
string dir = "data";

Directory.CreateDirectory(dir);
}
}

指定したフォルダがすでに存在していても、例外は発生しません。そのため、存在確認をせずに呼び出しても問題ありません。

複数階層のフォルダもまとめて作成できます。

C#
Directory.CreateDirectory(Path.Combine("data", "logs", "2026"));

ファイルに書き込む前に、保存先フォルダを作成しておくと安全です。

C#
string dir = "logs";
Directory.CreateDirectory(dir);

string path = Path.Combine(dir, "app.log");
File.AppendAllText(path, "ログを書き込みます。" + Environment.NewLine);

6-3. Directory.GetFilesでフォルダ内のファイル一覧を取得する

フォルダ内のファイル一覧を取得するには、Directory.GetFilesを使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string dir = "data";

string[] files = Directory.GetFiles(dir);

foreach (string file in files)
{
Console.WriteLine(file);
}
}
}

サブフォルダ内のファイルも含めて取得したい場合は、SearchOption.AllDirectoriesを指定します。

C#
string[] files = Directory.GetFiles(
"data",
"*.*",
SearchOption.AllDirectories
);

ファイル数が非常に多い場合は、Directory.EnumerateFilesを使うと、必要な分だけ順次処理しやすくなります。

C#
foreach (string file in Directory.EnumerateFiles("data"))
{
Console.WriteLine(file);
}

6-4. Directory.GetDirectoriesでサブフォルダ一覧を取得する

サブフォルダ一覧を取得するには、Directory.GetDirectoriesを使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string dir = "data";

string[] directories = Directory.GetDirectories(dir);

foreach (string directory in directories)
{
Console.WriteLine(directory);
}
}
}

サブフォルダを再帰的に取得する場合は、SearchOption.AllDirectoriesを指定します。

C#
string[] directories = Directory.GetDirectories(
"data",
"*",
SearchOption.AllDirectories
);

バックアップ処理やファイル整理ツールを作る場合に便利です。

6-5. Directory.Deleteでフォルダを削除する

フォルダを削除するには、Directory.Deleteを使います。

C#
using System.IO;

class Program
{
static void Main()
{
string dir = "old";

if (Directory.Exists(dir))
{
Directory.Delete(dir);
}
}
}

ただし、フォルダ内にファイルやサブフォルダがある場合、このままでは削除できません。中身ごと削除するには、第2引数にtrueを指定します。

C#
Directory.Delete(dir, recursive: true);

再帰削除は非常に強力な操作です。誤ったパスを指定すると重要なファイルを削除してしまう可能性があるため、削除対象のパスを必ず確認しましょう。

6-6. 特定の拡張子だけを検索する方法

特定の拡張子のファイルだけ取得したい場合は、検索パターンを指定します。

C#
string[] csvFiles = Directory.GetFiles("data", "*.csv");

テキストファイルだけを取得する例です。

C#
string[] textFiles = Directory.GetFiles("data", "*.txt");

サブフォルダも含めて検索する場合は、次のようにします。

C#
string[] logFiles = Directory.GetFiles(
"logs",
"*.log",
SearchOption.AllDirectories
);

複数の拡張子を検索したい場合は、LINQを使う方法があります。

C#
using System;
using System.IO;
using System.Linq;

class Program
{
static void Main()
{
string[] extensions = { ".txt", ".csv" };

var files = Directory.GetFiles("data")
.Where(file => extensions.Contains(Path.GetExtension(file)));

foreach (string file in files)
{
Console.WriteLine(file);
}
}
}

7. 実践でよく使うC#ファイル操作パターン

7-1. ログファイルに追記する

ログファイルへの追記は、C#のファイル操作でよく使われるパターンです。

C#
using System;
using System.IO;

class Logger
{
public static void WriteLog(string message)
{
string logDir = "logs";
Directory.CreateDirectory(logDir);

string logPath = Path.Combine(logDir, "app.log");

string log = $"{DateTime.Now:yyyy-MM-dd HH:mm:ss} {message}{Environment.NewLine}";

File.AppendAllText(logPath, log);
}
}

使用例です。

C#
Logger.WriteLog("アプリケーションを開始しました。");
Logger.WriteLog("処理が完了しました。");

日付ごとにログファイルを分ける場合は、次のようにします。

C#
string fileName = $"{DateTime.Now:yyyyMMdd}.log";
string logPath = Path.Combine("logs", fileName);

本格的なログ管理では専用のロギングライブラリを使うこともありますが、簡単な処理であればFile.AppendAllTextでも実装できます。

7-2. CSVファイルを読み込む

単純なCSVファイルを読み込む例です。

C#
using System;
using System.IO;

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

string[] lines = File.ReadAllLines(path);

foreach (string line in lines)
{
string[] columns = line.Split(',');

string id = columns[0];
string name = columns[1];
string email = columns[2];

Console.WriteLine($"{id}: {name} ({email})");
}
}
}

ただし、実際のCSVでは、カンマを含む値やダブルクォート、改行を含む値が登場することがあります。その場合、単純なSplit(',')では正しく処理できません。

複雑なCSVを扱う場合は、CSV専用ライブラリを使うか、仕様に合わせたパーサーを実装する必要があります。

7-3. CSVファイルを書き出す

CSVファイルを書き出す基本例です。

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

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

List<string> lines = new List<string>
{
"Id,Name,Email",
"1,Tanaka,tanaka@example.com",
"2,Suzuki,suzuki@example.com",
"3,Sato,sato@example.com"
};

File.WriteAllLines(path, lines);
}
}

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

C#
static string EscapeCsv(string value)
{
if (value.Contains(",") || value.Contains("\"") || value.Contains("\n"))
{
value = value.Replace("\"", "\"\"");
return $"\"{value}\"";
}

return value;
}

使用例です。

C#
string name = EscapeCsv("Tanaka, Taro");
string email = EscapeCsv("tanaka@example.com");

string line = $"1,{name},{email}";

7-4. JSONファイルを読み書きする

JSONファイルを読み書きする場合は、System.Text.Jsonを使うのが一般的です。

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

class User
{
public int Id { get; set; }
public string Name { get; set; } = "";
}

JSONファイルへ書き込む例です。

C#
User user = new User
{
Id = 1,
Name = "Tanaka"
};

string json = JsonSerializer.Serialize(user, new JsonSerializerOptions
{
WriteIndented = true
});

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

JSONファイルを読み込む例です。

C#
string json = File.ReadAllText("user.json");

User? user = JsonSerializer.Deserialize<User>(json);

if (user != null)
{
Console.WriteLine(user.Name);
}

設定ファイル、キャッシュデータ、簡易的なデータ保存などでよく使われます。

7-5. 設定ファイルを読み込む

簡単な設定ファイルをJSONで読み込む例です。

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

class AppSettings
{
public string InputPath { get; set; } = "";
public string OutputPath { get; set; } = "";
}

設定ファイルの例です。

JSON
{
"InputPath": "data/input.csv",
"OutputPath": "data/output.csv"
}

読み込みコードです。

C#
string json = File.ReadAllText("settings.json");

AppSettings? settings = JsonSerializer.Deserialize<AppSettings>(json);

if (settings == null)
{
throw new InvalidOperationException("設定ファイルを読み込めませんでした。");
}

Console.WriteLine(settings.InputPath);
Console.WriteLine(settings.OutputPath);

ファイルパスや接続情報、出力先などを設定ファイル化しておくと、ソースコードを変更せずに動作を切り替えられます。

7-6. アップロードされたファイルを保存する

ASP.NET Coreでアップロードされたファイルを保存する場合の例です。

C#
using Microsoft.AspNetCore.Http;

public async Task SaveUploadedFileAsync(IFormFile file)
{
string uploadDir = Path.Combine(AppContext.BaseDirectory, "uploads");
Directory.CreateDirectory(uploadDir);

string fileName = Path.GetFileName(file.FileName);
string savePath = Path.Combine(uploadDir, fileName);

using FileStream stream = new FileStream(savePath, FileMode.Create);

await file.CopyToAsync(stream);
}

アップロードファイルを保存する場合は、ユーザーが指定したファイル名をそのまま信用しないことが重要です。

安全性を高めるには、保存時のファイル名をランダムに変更します。

C#
string extension = Path.GetExtension(file.FileName);
string fileName = $"{Guid.NewGuid()}{extension}";
string savePath = Path.Combine(uploadDir, fileName);

また、拡張子、ファイルサイズ、MIMEタイプなどを検証し、不正なファイルが保存されないようにしましょう。

7-7. 一時ファイルを作成して利用する

一時ファイルを作成するには、Path.GetTempFileNameを使います。

C#
using System;
using System.IO;

class Program
{
static void Main()
{
string tempFile = Path.GetTempFileName();

File.WriteAllText(tempFile, "一時データ");

string content = File.ReadAllText(tempFile);

Console.WriteLine(content);

File.Delete(tempFile);
}
}

一時フォルダのパスを取得するには、Path.GetTempPathを使います。

C#
string tempDir = Path.GetTempPath();
string tempFile = Path.Combine(tempDir, $"{Guid.NewGuid()}.tmp");

一時ファイルは、処理が終わったら削除するのが基本です。例外が発生しても削除されるように、finallyを使うと安全です。

C#
string tempFile = Path.GetTempFileName();

try
{
File.WriteAllText(tempFile, "一時データ");
}
finally
{
if (File.Exists(tempFile))
{
File.Delete(tempFile);
}
}

8. C#ファイル操作で発生しやすいエラーと対処法

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

FileNotFoundExceptionは、指定したファイルが見つからない場合に発生します。

主な原因は、ファイルパスが間違っている、相対パスの基準が想定と違う、ファイルが削除されている、ファイル名や拡張子が違う、などです。

対処法としては、読み込み前にFile.Existsで確認します。

C#
string path = "sample.txt";

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

string content = File.ReadAllText(path);

ただし、存在確認後にファイルが削除される可能性もあるため、最終的には例外処理も必要です。

C#
try
{
string content = File.ReadAllText(path);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}

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

DirectoryNotFoundExceptionは、指定したフォルダが存在しない場合に発生します。

たとえば、次のような書き込み処理で、dataフォルダが存在しない場合に発生します。

C#
File.WriteAllText("data/output.txt", "test");

対処法は、書き込み前にフォルダを作成することです。

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

string path = Path.Combine(dir, "output.txt");
File.WriteAllText(path, "test");

Directory.CreateDirectoryは、すでにフォルダが存在していても問題なく実行できるため、書き込み前に呼び出しておくと安全です。

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

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

主な原因は、管理者権限が必要な場所に書き込もうとしている、読み取り専用ファイルに書き込もうとしている、フォルダをファイルとして開こうとしている、アクセスが制限されたディレクトリを操作している、などです。

たとえば、Windowsのシステムフォルダやアプリケーションのインストール先に書き込もうとすると、権限エラーになることがあります。

対処法としては、ユーザーが書き込み可能なフォルダを使用します。

C#
string appData = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string dir = Path.Combine(appData, "MyApp");

Directory.CreateDirectory(dir);

string path = Path.Combine(dir, "settings.json");
File.WriteAllText(path, "{}");

ユーザー入力のパスを扱う場合は、アクセス可能な範囲に制限することも重要です。

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

IOExceptionは、ファイルI/O全般で発生する例外です。

主な原因は、ファイルが他のプロセスで使用中、ディスク容量不足、ネットワークドライブの切断、ファイル名が不正、同名ファイルが存在する、などです。

例外メッセージを確認すると原因を特定しやすくなります。

C#
try
{
File.Copy("source.txt", "dest.txt");
}
catch (IOException ex)
{
Console.WriteLine($"I/Oエラーが発生しました: {ex.Message}");
}

IOExceptionはさまざまな原因で発生するため、必要に応じてログに詳細情報を記録しておくと、トラブルシューティングしやすくなります。

8-5. ファイル使用中でアクセスできない場合の対処法

ファイルが他のプロセスや自分のプログラム内で開かれていると、読み書きや削除ができないことがあります。

特に、FileStreamStreamReaderStreamWriterを閉じ忘れると、ファイルが使用中のままになります。

悪い例です。

C#
StreamWriter writer = new StreamWriter("log.txt");
writer.WriteLine("ログ");
// CloseやDisposeを呼んでいない

良い例です。

C#
using StreamWriter writer = new StreamWriter("log.txt");
writer.WriteLine("ログ");

usingを使うことで、処理が終わったときに自動でファイルが閉じられます。

一時的にファイルがロックされる可能性がある場合は、少し待って再試行する方法もあります。

C#
for (int i = 0; i < 3; i++)
{
try
{
File.Delete("target.txt");
break;
}
catch (IOException)
{
Thread.Sleep(1000);
}
}

ただし、むやみに再試行を繰り返すのではなく、根本原因を確認することが大切です。

8-6. try-catch-finallyで安全に例外処理を行う

ファイル操作では、例外処理を適切に実装することが重要です。

C#
using System;
using System.IO;

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

try
{
string content = File.ReadAllText(path);
Console.WriteLine(content);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("ファイルへのアクセス権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"ファイル操作中にエラーが発生しました: {ex.Message}");
}
finally
{
Console.WriteLine("ファイル処理を終了します。");
}
}
}

finallyブロックは、例外が発生してもしなくても実行されます。一時ファイルの削除や後処理に利用できます。

ただし、usingを使えばリソース解放は自動化できるため、通常はusingtry-catchを組み合わせるのが実践的です。

9. C#ファイル操作のベストプラクティス

9-1. using文でリソースを確実に解放する

StreamReaderStreamWriterFileStreamなどを使う場合は、必ずusingを使ってリソースを解放しましょう。

C#
using StreamReader reader = new StreamReader("sample.txt");
string content = reader.ReadToEnd();

従来の書き方では、次のようにも書けます。

C#
using (StreamReader reader = new StreamReader("sample.txt"))
{
string content = reader.ReadToEnd();
}

ファイルを閉じ忘れると、ファイルが使用中になり、削除や上書きができなくなることがあります。C# file操作では、リソース管理が非常に重要です。

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

File.ReadAllTextFile.ReadAllLinesは便利ですが、大容量ファイルを扱う場合には向いていません。

大きなファイルでは、StreamReaderFile.ReadLinesを使って1行ずつ処理しましょう。

C#
foreach (string line in File.ReadLines("large.txt"))
{
Console.WriteLine(line);
}

File.ReadLinesは遅延実行されるため、すべての行を一度にメモリへ読み込まずに処理できます。

ログファイル、CSVファイル、巨大なテキストデータを扱う場合には、メモリ使用量を意識した実装が必要です。

9-3. パスは文字列連結ではなくPath.Combineを使う

パスを作るときは、文字列連結ではなくPath.Combineを使いましょう。

C#
string path = Path.Combine("data", "input", "sample.csv");

悪い例です。

C#
string path = "data" + "\\" + "input" + "\\" + "sample.csv";

文字列連結では、OSごとの区切り文字の違いや、スラッシュの重複・不足が原因で不具合が起きやすくなります。

Path.Combineを使えば、Windows、Linux、macOSでも扱いやすいパスを作成できます。

9-4. 文字コードを明示して文字化けを防ぐ

日本語ファイルを扱う場合は、文字コードを意識しましょう。

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

書き込み時も同様です。

C#
File.WriteAllText("output.txt", text, Encoding.UTF8);

外部システムと連携する場合、UTF-8なのかShift_JISなのかを確認することが重要です。

特にCSVファイルは、Excelや古い業務システムとの連携で文字コード問題が発生しやすいです。

9-5. ファイルの存在確認と例外処理を組み合わせる

ファイル操作では、File.Existsによる存在確認だけでなく、例外処理も組み合わせましょう。

C#
string path = "sample.txt";

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

try
{
string content = File.ReadAllText(path);
Console.WriteLine(content);
}
catch (IOException ex)
{
Console.WriteLine($"読み込みに失敗しました: {ex.Message}");
}

存在確認後にファイルが削除されたり、他のプロセスにロックされたりする可能性があるためです。

安全なファイル操作では、事前チェックと例外処理の両方を使うことが大切です。

9-6. セキュリティを考慮してユーザー入力のパスを検証する

ユーザーが入力したファイル名やパスをそのまま使うのは危険です。

たとえば、次のような相対パスを使われると、想定外の場所にアクセスされる可能性があります。

../../secret.txt

アップロードファイルやダウンロードファイルを扱う場合は、保存先ディレクトリを固定し、ファイル名だけを安全に扱うようにします。

C#
string uploadDir = Path.Combine(AppContext.BaseDirectory, "uploads");
Directory.CreateDirectory(uploadDir);

string safeFileName = Path.GetFileName(userInputFileName);
string savePath = Path.Combine(uploadDir, safeFileName);

さらに安全性を高めるには、ファイル名をランダムに生成します。

C#
string extension = Path.GetExtension(userInputFileName);
string safeFileName = $"{Guid.NewGuid()}{extension}";

また、許可する拡張子を制限することも重要です。

C#
string[] allowedExtensions = { ".jpg", ".png", ".pdf" };
string extension = Path.GetExtension(fileName).ToLowerInvariant();

if (!allowedExtensions.Contains(extension))
{
throw new InvalidOperationException("許可されていない拡張子です。");
}

ファイル操作はセキュリティリスクにつながることがあるため、ユーザー入力を信用しない設計が必要です。

10. C#ファイル操作に関するよくある質問

10-1. FileクラスとFileInfoクラスはどちらを使うべきか

単発のファイル操作であれば、Fileクラスを使うのが簡単です。

C#
File.WriteAllText("sample.txt", "Hello");

一方、特定のファイルに対して複数の情報を取得したり、オブジェクトとして扱いたい場合はFileInfoが便利です。

C#
FileInfo info = new FileInfo("sample.txt");

Console.WriteLine(info.Name);
Console.WriteLine(info.Length);
Console.WriteLine(info.LastWriteTime);

基本的には、簡単な読み書きや存在確認にはFile、ファイル情報を繰り返し扱う場合にはFileInfoと考えるとよいでしょう。

10-2. ファイルを上書きせずに追記するにはどうすればよいか

ファイルを上書きせずに追記するには、File.AppendAllTextを使います。

C#
File.AppendAllText("log.txt", "ログを追記します。" + Environment.NewLine);

StreamWriterを使う場合は、追記モードを指定します。

C#
using StreamWriter writer = new StreamWriter("log.txt", append: true);
writer.WriteLine("ログを追記します。");

ログ出力や履歴保存では、上書きではなく追記を使うことが多いです。

10-3. ファイルが存在しない場合だけ作成するにはどうすればよいか

File.Existsで存在確認をしてから作成します。

C#
string path = "sample.txt";

if (!File.Exists(path))
{
File.WriteAllText(path, "初期内容");
}

空ファイルを作成するだけなら、File.Createを使います。

C#
if (!File.Exists(path))
{
using FileStream fs = File.Create(path);
}

ただし、存在確認後に別プロセスが同じファイルを作る可能性もあるため、厳密な制御が必要な場合は例外処理も組み合わせましょう。

10-4. 相対パスでファイルが見つからない原因は何か

相対パスでファイルが見つからない主な原因は、基準となるカレントディレクトリが想定と違うことです。

現在のカレントディレクトリは次のコードで確認できます。

C#
Console.WriteLine(Environment.CurrentDirectory);

実行ファイルの場所を基準にしたい場合は、AppContext.BaseDirectoryを使います。

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

Visual Studioのデバッグ実行では、プロジェクトフォルダではなくbin/Debug配下が基準になることがあります。ファイルをプロジェクトに追加している場合は、出力ディレクトリへコピーする設定も確認しましょう。

10-5. 大きなファイルを高速に読み込むにはどうすればよいか

大きなファイルを扱う場合は、File.ReadAllTextFile.ReadAllLinesのような一括読み込みを避けます。

1行ずつ処理するなら、File.ReadLinesが簡単です。

C#
foreach (string line in File.ReadLines("large.txt"))
{
// 1行ずつ処理
}

より細かく制御したい場合は、StreamReaderを使います。

C#
using StreamReader reader = new StreamReader("large.txt");

string? line;
while ((line = reader.ReadLine()) != null)
{
// 1行ずつ処理
}

大容量ファイルでは、メモリ使用量、文字コード、バッファサイズ、ディスクI/Oなどを考慮する必要があります。

10-6. C#でファイル操作を安全に実装するポイントは何か

C#でファイル操作を安全に実装するには、次のポイントを意識します。

ファイルやフォルダの存在確認を行うこと、usingでリソースを確実に解放すること、大容量ファイルでは一括読み込みを避けること、Path.Combineでパスを組み立てること、文字コードを明示すること、ユーザー入力のパスやファイル名を検証すること、例外処理を実装することです。

特に、アップロードファイルやユーザー指定のパスを扱う場合は、セキュリティ面の注意が必要です。ファイル名をそのまま使わず、保存先を限定し、拡張子やサイズを検証しましょう。

まとめ

C#のファイル操作は、System.IO名前空間を使うことで簡単に実装できます。

小さなテキストファイルを読み込むならFile.ReadAllText、行単位で読み込むならFile.ReadAllLinesFile.ReadLines、大容量ファイルを扱うならStreamReaderが適しています。

ファイルに書き込む場合は、上書きならFile.WriteAllText、複数行ならFile.WriteAllLines、追記ならFile.AppendAllText、細かく制御したい場合はStreamWriterを使います。

ファイルの存在確認にはFile.Exists、削除にはFile.Delete、コピーにはFile.Copy、移動やリネームにはFile.Moveを使用します。フォルダ操作では、Directory.ExistsDirectory.CreateDirectoryDirectory.GetFilesDirectory.Deleteなどが基本です。

また、ファイルパスは文字列連結ではなくPath.Combineで組み立てることが重要です。Windows、Linux、macOSなど複数の環境で動かす場合にも、安全で保守しやすいコードになります。

C# file操作では、文字コード、例外処理、権限、ファイルロック、セキュリティにも注意が必要です。基本的なメソッドの使い方だけでなく、実践で起こりやすいエラーへの対処法まで理解しておくことで、安定したファイル処理を実装できます。