方法 : ディレクトリ ツリーを反復処理する (C# プログラミング ガイド)
"ディレクトリ ツリーを反復処理する" とは、指定したルート フォルダー以下の入れ子になっている各サブディレクトリ内の各ファイルにアクセスすることです。 各ファイルを開く必要がない場合もあります。 単に、ファイルまたはサブディレクトリの名前を string として取得することもでき、その他の情報を System.IO.FileInfo オブジェクトまたは System.IO.DirectoryInfo オブジェクトの形式で取得することもできます。
注意
Windows では、"ディレクトリ" および "フォルダー" という用語は同義です。 多くのドキュメントおよびユーザー インターフェイスのテキストでは、"フォルダー" という用語が使用されていますが、.NET Framework クラス ライブラリでは、"ディレクトリ" という用語が使用されています。
最も容易なケース、つまり、指定したルート以下のすべてのディレクトリのアクセス許可があることが確実にわかっている場合、System.IO.SearchOption.AllDirectories フラグを使用できます。 このフラグは、指定したパターンと一致する、すべての入れ子にされたサブディレクトリを返します。 このフラグを使用する方法を次の例に示します。
root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);
この方法の弱点は、指定したルート以下のサブディレクトリの 1 つが DirectoryNotFoundException または UnauthorizedAccessException を発生させた場合、メソッド全体が失敗し、ディレクトリが返されないという点です。 GetFiles メソッドを使用する場合も同様です。 特定のサブフォルダーに関するこれらの例外を処理する必要がある場合、次の例に示されているように、ディレクトリ ツリーを手動で移動する必要があります。
ディレクトリ ツリーを手動で移動する場合、最初にサブディレクトリを処理すること (先行順走査)、または最初にファイルを処理すること (後順走査) ができます。 先行順走査を実行する場合、現在のフォルダー以下のツリー全体を移動しながら、そのフォルダー自体に直接格納されているファイルを反復処理していきます。 このドキュメント内の後の例では、後順走査を実行していますが、先行順走査を実行するように簡単に変更できます。
もう 1 つのオプションは、再帰とスタック ベースの走査のうちのどちらを使用するかです。 このドキュメント内の後の例では、両方の方法を示しています。
ファイルおよびフォルダーに対してさまざまな操作を実行する必要がある場合、単一のデリゲートを使用して呼び出すことができる個別の関数内に操作をリファクタリングすることで、これらの例をモジュール化できます。
注意
NTFS ファイル システムには、接合ポイント、シンボリック リンク、およびハード リンクの形式でリパース ポイントを含めることができます。 GetFiles、GetDirectories などの .NET Framework メソッドは、リパース ポイント以下のサブディレクトリを返しません。 この動作により、リパース ポイントが相互参照している場合に、無限ループに入るのが回避されます。 通常、ファイルを誤って変更または削除しないようにするためにリパース ポイントを処理する場合、十分な注意が必要です。 リパース ポイントを詳細に制御する必要がある場合、プラットフォーム呼び出しまたはネイティブ コードを使用して、適切な Win32 ファイル システム メソッドを直接呼び出します。
使用例
再帰を使用してディレクトリ ツリーを移動する方法を次の例に示します。 この再帰の方法は洗練されていますが、ディレクトリ ツリーが大規模で入れ子の階層が深い場合、スタック オーバーフロー例外が発生する可能性があります。
処理される特定の例外、および各ファイルまたは各フォルダーに対して実行される特定の操作は、例としてのみ用意したものです。 特定の要件を満たすには、このコードを修正する必要があります。 詳細については、コード内のコメントを参照してください。
public class RecursiveFileSearch
{
static System.Collections.Specialized.StringCollection log = new System.Collections.Specialized.StringCollection();
static void Main()
{
// Start with drives if you have to search the entire computer.
string[] drives = System.Environment.GetLogicalDrives();
foreach (string dr in drives)
{
System.IO.DriveInfo di = new System.IO.DriveInfo(dr);
// Here we skip the drive if it is not ready to be read. This
// is not necessarily the appropriate action in all scenarios.
if (!di.IsReady)
{
Console.WriteLine("The drive {0} could not be read", di.Name);
continue;
}
System.IO.DirectoryInfo rootDir = di.RootDirectory;
WalkDirectoryTree(rootDir);
}
// Write out all the files that could not be processed.
Console.WriteLine("Files with restricted access:");
foreach (string s in log)
{
Console.WriteLine(s);
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key");
Console.ReadKey();
}
static void WalkDirectoryTree(System.IO.DirectoryInfo root)
{
System.IO.FileInfo[] files = null;
System.IO.DirectoryInfo[] subDirs = null;
// First, process all the files directly under this folder
try
{
files = root.GetFiles("*.*");
}
// This is thrown if even one of the files requires permissions greater
// than the application provides.
catch (UnauthorizedAccessException e)
{
// This code just writes out the message and continues to recurse.
// You may decide to do something different here. For example, you
// can try to elevate your privileges and access the file again.
log.Add(e.Message);
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
}
if (files != null)
{
foreach (System.IO.FileInfo fi in files)
{
// In this example, we only access the existing FileInfo object. If we
// want to open, delete or modify the file, then
// a try-catch block is required here to handle the case
// where the file has been deleted since the call to TraverseTree().
Console.WriteLine(fi.FullName);
}
// Now find all the subdirectories under this directory.
subDirs = root.GetDirectories();
foreach (System.IO.DirectoryInfo dirInfo in subDirs)
{
// Resursive call for each subdirectory.
WalkDirectoryTree(dirInfo);
}
}
}
}
再帰を使用せずにディレクトリ ツリー内のファイルおよびフォルダーを反復処理する方法を、次の例に示します。 この方法では、後入れ先出し (LIFO: Last In First Out) スタックである Stack<T> コレクション型を使用します。
処理される特定の例外、および各ファイルまたは各フォルダーに対して実行される特定の操作は、例としてのみ用意したものです。 特定の要件を満たすには、このコードを修正する必要があります。 詳細については、コード内のコメントを参照してください。
public class StackBasedIteration
{
static void Main(string[] args)
{
// Specify the starting folder on the command line, or in
// Visual Studio in the Project > Properties > Debug pane.
TraverseTree(args[0]);
Console.WriteLine("Press any key");
Console.ReadKey();
}
public static void TraverseTree(string root)
{
// Data structure to hold names of subfolders to be
// examined for files.
Stack<string> dirs = new Stack<string>(20);
if (!System.IO.Directory.Exists(root))
{
throw new ArgumentException();
}
dirs.Push(root);
while (dirs.Count > 0)
{
string currentDir = dirs.Pop();
string[] subDirs;
try
{
subDirs = System.IO.Directory.GetDirectories(currentDir);
}
// An UnauthorizedAccessException exception will be thrown if we do not have
// discovery permission on a folder or file. It may or may not be acceptable
// to ignore the exception and continue enumerating the remaining files and
// folders. It is also possible (but unlikely) that a DirectoryNotFound exception
// will be raised. This will happen if currentDir has been deleted by
// another application or thread after our call to Directory.Exists. The
// choice of which exceptions to catch depends entirely on the specific task
// you are intending to perform and also on how much you know with certainty
// about the systems on which this code will run.
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
string[] files = null;
try
{
files = System.IO.Directory.GetFiles(currentDir);
}
catch (UnauthorizedAccessException e)
{
Console.WriteLine(e.Message);
continue;
}
catch (System.IO.DirectoryNotFoundException e)
{
Console.WriteLine(e.Message);
continue;
}
// Perform the required action on each file here.
// Modify this block to perform your required task.
foreach (string file in files)
{
try
{
// Perform whatever action is required in your scenario.
System.IO.FileInfo fi = new System.IO.FileInfo(file);
Console.WriteLine("{0}: {1}, {2}", fi.Name, fi.Length, fi.CreationTime);
}
catch (System.IO.FileNotFoundException e)
{
// If file was deleted by a separate application
// or thread since the call to TraverseTree()
// then just continue.
Console.WriteLine(e.Message);
continue;
}
}
// Push the subdirectories onto the stack for traversal.
// This could also be done before handing the files.
foreach (string str in subDirs)
dirs.Push(str);
}
}
}
アプリケーションにフォルダーを開くアクセス許可があるかどうか確認するためにすべてのフォルダーをテストするのは、通常、たいへん時間がかかります。 そのため、コード例には、try/catch ブロック内の操作のその部分のみが含まれています。 catch ブロックを修正すると、フォルダーへのアクセスを拒否されたとき、アクセス許可を昇格して再びアクセスを試行できます。 原則として、アプリケーションが不明の状態にならずに処理できる例外のみをキャッチします。
ディレクトリ ツリーの内容をメモリまたはディスクに保存する必要がある場合、各ファイルの (string 型の) FullName プロパティのみ保存するのが最適な選択肢です。 その後、必要に応じて、この文字列を使用して新しい FileInfo オブジェクトまたは DirectoryInfo オブジェクトを作成すること、または追加処理が必要なファイルを開くことができます。
信頼性の高いプログラミング
堅牢性の高いファイル反復処理コードを作成するには、ファイル システムの数多くの複雑な部分を考慮する必要があります。 詳細については、「NTFS Technical Reference」を参照してください。