今回から数回に分けて、C# のファイル入出力とオブジェクトの保存形式化について見ていきます。
アプリケーションは、画面やネットワークだけで完結するわけではありません。
設定ファイルを読む、ログを書く、CSV を取り込む、画像を保存する、バックアップを作るなど、ファイルシステムとのやり取りは多くの場面で必要になります。
まずこの記事では、System.IO 名前空間の基本型を使って、ディレクトリ、ファイル、ドライブ情報を扱う方法を整理します。
System.IO 名前空間の役割
System.IO には、ファイル、ディレクトリ、ストリーム、テキスト読み書き、バイナリ読み書きなどを扱う型が含まれています。
よく使う型は次の通りです。
DirectoryInfo
特定のディレクトリを表すインスタンス型です。
ディレクトリの列挙、作成、削除、親ディレクトリの取得などに使います。
FileInfo
特定のファイルを表すインスタンス型です。
ファイルサイズ、更新日時、作成、削除、コピー、移動、ストリームを開く操作などに使います。
Directory
ディレクトリ操作を行う静的メソッド群です。
一度きりの処理や、パス文字列から直接操作したい場合に便利です。
File
ファイル操作を行う静的メソッド群です。
短いコードで読み書きやコピーをしたい場合に便利です。
DriveInfo
ドライブ情報を表します。
空き容量、ドライブ種別、ボリューム名などを確認できます。
インスタンス型と静的型の使い分け
DirectoryInfo / FileInfo と Directory / File は、似たような操作を提供します。
ざっくりした使い分けは次の通りです。
DirectoryInfo/FileInfo: 同じ対象に対して複数の操作を行うときに便利Directory/File: パス文字列を使って単発の操作を行うときに便利
たとえば、1 つのファイルについてサイズ、更新日時、コピー処理をまとめて扱うなら FileInfo が読みやすくなります。
using System.IO;
var file = new FileInfo("sample.txt");
Console.WriteLine(file.FullName);
Console.WriteLine(file.Exists);
Console.WriteLine(file.Length);
Console.WriteLine(file.LastWriteTime);
一方、ファイルの存在確認だけなら File.Exists() で十分です。
if (File.Exists("sample.txt"))
{
Console.WriteLine("ファイルがあります。");
}
FileSystemInfo という共通の基底型
DirectoryInfo と FileInfo は、どちらも FileSystemInfo を継承しています。
FileSystemInfo には、ファイルシステム上の項目に共通する情報があります。
FullNameNameExistsCreationTimeLastAccessTimeLastWriteTimeAttributes
共通化されているため、ファイルとディレクトリをまとめて扱うコードを書けます。
using System.IO;
DirectoryInfo root = new DirectoryInfo(".");
foreach (FileSystemInfo item in root.GetFileSystemInfos())
{
Console.WriteLine($"{item.Name} - {item.LastWriteTime}");
}
DirectoryInfo でディレクトリを列挙する
DirectoryInfo を使うと、ディレクトリ内のファイルやサブディレクトリを取得できます。
using System.IO;
DirectoryInfo directory = new DirectoryInfo(".");
Console.WriteLine("ディレクトリ:");
foreach (DirectoryInfo child in directory.GetDirectories())
{
Console.WriteLine(child.Name);
}
Console.WriteLine("ファイル:");
foreach (FileInfo file in directory.GetFiles())
{
Console.WriteLine($"{file.Name} ({file.Length} bytes)");
}
検索パターンも指定できます。
foreach (FileInfo file in directory.GetFiles("*.txt"))
{
Console.WriteLine(file.FullName);
}
サブディレクトリも含めて列挙したい場合は、SearchOption.AllDirectories を指定します。
foreach (FileInfo file in directory.GetFiles(
"*.md",
SearchOption.AllDirectories))
{
Console.WriteLine(file.FullName);
}
大量のファイルを扱う場合は、配列を返す GetFiles() より、遅延列挙できる EnumerateFiles() が向いています。
foreach (FileInfo file in directory.EnumerateFiles("*.md"))
{
Console.WriteLine(file.Name);
}
DirectoryInfo でサブディレクトリを作成する
ディレクトリを作成するには CreateSubdirectory() を使えます。
using System.IO;
DirectoryInfo root = new DirectoryInfo("work");
if (!root.Exists)
{
root.Create();
}
DirectoryInfo logs = root.CreateSubdirectory("logs");
Console.WriteLine(logs.FullName);
Create() や CreateSubdirectory() は、すでに存在していても例外になりにくい設計です。
ただし、ファイルと同名のディレクトリを作ろうとした場合や権限がない場合は失敗します。
Directory 型で単発操作を行う
Directory は静的メソッドでディレクトリを操作します。
using System.IO;
string path = "work/logs";
Directory.CreateDirectory(path);
string[] files = Directory.GetFiles(path);
foreach (string file in files)
{
Console.WriteLine(file);
}
存在確認もできます。
if (Directory.Exists(path))
{
Console.WriteLine("ディレクトリがあります。");
}
移動や削除もできます。
Directory.Move("work/logs", "work/old-logs");
Directory.Delete("work/old-logs", recursive: true);
recursive: true は配下のファイルやディレクトリも削除するため、実アプリでは対象パスを慎重に確認しましょう。
DriveInfo でドライブ情報を調べる
DriveInfo を使うと、利用可能なドライブを列挙できます。
using System.IO;
foreach (DriveInfo drive in DriveInfo.GetDrives())
{
Console.WriteLine($"名前: {drive.Name}");
Console.WriteLine($"種類: {drive.DriveType}");
if (drive.IsReady)
{
Console.WriteLine($"形式: {drive.DriveFormat}");
Console.WriteLine($"空き容量: {drive.AvailableFreeSpace}");
Console.WriteLine($"総容量: {drive.TotalSize}");
}
Console.WriteLine();
}
リムーバブルメディアやネットワークドライブなどは、準備できていない場合があります。
IsReady を確認してから容量や形式を読むのが安全です。
FileInfo でファイル情報を扱う
FileInfo は、1 つのファイルを表します。
using System.IO;
FileInfo file = new FileInfo("sample.txt");
Console.WriteLine(file.FullName);
Console.WriteLine(file.Exists);
if (file.Exists)
{
Console.WriteLine(file.Length);
Console.WriteLine(file.CreationTime);
Console.WriteLine(file.LastWriteTime);
}
ファイルを作成する例です。
using FileStream stream = file.Create();
Create() はファイルを作成して FileStream を返します。
既存ファイルがある場合は上書きされるため、必要なら事前に Exists を確認します。
FileInfo でコピー・移動・削除する
using System.IO;
FileInfo source = new FileInfo("sample.txt");
if (!source.Exists)
{
File.WriteAllText(source.FullName, "hello");
}
FileInfo copied = source.CopyTo("sample-copy.txt", overwrite: true);
copied.MoveTo("sample-moved.txt", overwrite: true);
copied.Delete();
FileInfo は対象ファイルを表すオブジェクトなので、複数操作を続けて書くと読みやすくなります。
File 型で短く読み書きする
File には、よく使う処理を短く書けるメソッドがあります。
using System.IO;
File.WriteAllText("message.txt", "こんにちは");
string text = File.ReadAllText("message.txt");
Console.WriteLine(text);
複数行の読み書きもできます。
string[] lines =
{
"one",
"two",
"three"
};
File.WriteAllLines("lines.txt", lines);
foreach (string line in File.ReadLines("lines.txt"))
{
Console.WriteLine(line);
}
ReadAllLines() は全行を一度に読み込みます。
ReadLines() は遅延列挙されるため、大きなファイルではこちらが扱いやすい場合があります。
パス操作には Path 型を使う
文字列結合でパスを作ると、区切り文字の違いや末尾スラッシュで壊れやすくなります。
Path.Combine() を使いましょう。
using System.IO;
string directory = "work";
string fileName = "message.txt";
string path = Path.Combine(directory, fileName);
Console.WriteLine(path);
拡張子やファイル名も取り出せます。
Console.WriteLine(Path.GetFileName(path));
Console.WriteLine(Path.GetExtension(path));
Console.WriteLine(Path.GetDirectoryName(path));
一時ファイルや一時ディレクトリを扱う場合にも便利です。
string tempFile = Path.GetTempFileName();
string tempPath = Path.GetTempPath();
例外処理を忘れない
ファイルシステム操作は、失敗する前提で書く必要があります。
try
{
string text = File.ReadAllText("missing.txt");
Console.WriteLine(text);
}
catch (FileNotFoundException)
{
Console.WriteLine("ファイルが見つかりません。");
}
catch (UnauthorizedAccessException)
{
Console.WriteLine("アクセス権限がありません。");
}
catch (IOException ex)
{
Console.WriteLine($"入出力エラー: {ex.Message}");
}
存在確認をしてから開いても、その直後に別プロセスが削除する可能性があります。
そのため、存在確認だけに頼らず、実際の操作を try / catch で守る考え方が大切です。
まとめ
System.IO は、ファイルシステム操作の入口です。
同じ対象に対して複数操作を行うなら DirectoryInfo や FileInfo、パス文字列から単発操作を行うなら Directory や File が便利です。
また、パスの組み立てには Path、ドライブ情報には DriveInfo を使います。
次回は、より低レベルな読み書きの仕組みであるストリームと、テキスト・バイナリの読み書きを見ていきます。