C#のGetFilesというメソッドを使ってWindowsのルートフォルダから配下のディレクトリをたどろうとするとごみ箱などのシステムフォルダで止まり、うまく動作しません。
今回は、サブディレクトリも含めた全てのファイル名を取得するというサンプルを通してアクセスが拒否された場合、そのファイルのみ無視してファイル探索を続けるというプログラムを作成してみます。
今回のサンプルの実行イメージ
実行時の状態。
ボタンとリストボックスが表示されています。

ボタンをクリックするとWindowsのルートフォルダ(c:\)以降をサブディレクトリも含めて再帰的に探索し全てのテキストファイル(拡張子~.txtのファイル)を下のリストボックスに追加します。テキストファイルとしたのは全てのファイルにしてしまうと時間が掛かりすぎるためです。(それでもルートフォルダから探索している為、少し時間が掛かります。わたしの環境で1分弱でした)
ファイル取得後の状態

では、早速作ってみます。
プロジェクトの準備
Visual Studioを起動してメニューからプロジェクトの新規作成を選び、
テンプレート >Visual C# >Windowsフォームアプリケーション
を選択します。
プロジェクト名は、サブディレクトリも含めた全てのファイル名取得するとしておきます。
Form1にツールボックスからButtonとListBoxをそれぞれ一つづつ貼り付けます。

プロジェクトの準備はこれで完了です。
ファイルとディレクトリ取得に必要な関数
ファイル名取得とディレクトリ名取得に必要な関数はSystem.IOクラスのメソッドGetFilesとGetDirectoriesの2つです。
使い方はどちらも引数にパス名を文字列で指定する形になります。
戻り値はString配列です。
事前にusing System.IO;としてインポートしておくと良いでしょう。
例)c:\hoge\のファイル名を取得
String[] files = Directory.GetFiles("c:\hoge\");
例)c:\hoge\のディレクトリ名を取得
String[] files = Directory.GetDirectories("c:\hoge\");
上記は引数にパス名のみを指定していますが、引数を2つにして例えば拡張子がtxtだけのファイル名を抜き出すことも可能です。
例)
String[] files = System.IO.Directory.GetFiles("c:\hoge\", "*.txt");
3つ目の引数をSearchOption.AllDirectoriesにするとGetFilesメソッドで配下のディレクトリの中も探索してくれます。
例)
String[] files = Directory.GetFiles("c:\hoge\", "*.txt", SearchOption.AllDirectories);
詳しくはマイクロソフト.NET Frameworkドキュメントをご覧ください。
参考
Directory.GetFiles – .NET Framework 4.8
Directory.GetDirectories – .NET Framework 4.8
GetFilesメソッドの問題点
ちなみにGetFilesメソッドは、ディレクトリ名が見つかったときその配下のファイル名も取得することも出来るので、いくつかのサイトでは以下のようなソースコードが紹介されているはずです。
例)c:\以下のサブディレクトリを含めた全てのファイル名を取得する
string[] files = System.IO.Directory.GetFiles("c:\", "*", SearchOption.AllDirectories);
しかし、このコードは問題点があります。
上記コードのようにWindowsのルートフォルダ(C:\)などを第1引数に指定した場合です。
いきなり
System.UnauthorizedAccessException: パス ‘C:\$Recycle.Bin\S-1-5-18’ へのアクセスが拒否されました。
のようなエラーメッセージを吐き出してプログラムが停止してしまうのです。
理由はWindowsのごみ箱などのようなシステムフォルダは外部アプリからアクセスが許可されていない為です。
では例外処理(try~catch)でくくればいいじゃないか、と思うかもしれませんが、このコードの場合、エラーが見つかったらそこで探索をやめてしまうので、アクセスできないディレクトリだけを無視してアクセス可能なファイル名だけ取り出すということが出来ないのです。
そこでGetFilesメソッドとGetDirectoriesメソッドの両方を併用した再帰処理のプログラムが必要になってきます。
とは言ってもわたしも以下のサイトを参考に(というかほぼそのまま)利用させて頂きました。大変感謝です。
【C#】ドライブ直下からのファイルリスト取得について – Qiita
したがって、Windowsのルートフォルダのような場所から配下のサブディレクトリを含めて全てたどってファイル名を抜き出すには、以下のイメージとなります。
見つかったファイル名はリスト配列に追加する
【処理2】処理1と同じディレクトリにある全てのディレクトリ名を抜き出す
【処理3】処理2で見つかったディレクトリ名一つずつ処理1の指定のディレクトリとして探索をおこなう(再帰処理)
うーん。って感じですか?日本語での説明力不足ですいません。
ソースコードを見た方が分かりやすいかもしれません。
以下にソースコード全体を示します。
ソースコード全体
GetAllFilesメソッドは直接入力して作成してください。
public static List<String> GetAllFiles(String DirPath)
{
// ファイル名探索のコード
}
button1_Clickメソッドの部分は、フォーム上でbutton1をダブルクリックして以下のコードを自動生成させてください。
private void button1_Click(object sender, EventArgs e)
{
// ボタンを押した時の処理
}
今回作成したコード全体(プロジェクトのForm1.csにすべて記述しています)
Form1.cs
using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
namespace サブディレクトリも含めた全てのファイル名取得する
{
public partial class Form1 : Form
{
// 探索開始フォルダ
String DirPath = @"C:\";
public Form1()
{
InitializeComponent();
this.Text = ProductName;
button1.Text = "ファイル名を取得する";
}
/*
* サブディレクトリも含め全てのファイル名を取得する関数
*
* 引用元:【C#】ドライブ直下からのファイルリスト取得について
* https://qiita.com/OneK/items/8b0d02817a9f2a2fbeb0
*/
public static List<String> GetAllFiles(String DirPath)
{
List<String> lstStr = new List<String>(); // 取得したファイル名を格納するためのリスト
String[] strBuff; // ファイル名とディレクトリ名取得用
try
{
// ファイル名取得
strBuff = Directory.GetFiles(DirPath, "*.txt"); // 探索範囲がルートフォルダで時間が掛かるため、テキスト形式のファイルのみ探索
foreach (String file in strBuff)
{
lstStr.Add(file);
}
// ディレクトリ名の取得
strBuff = Directory.GetDirectories(DirPath);
foreach (String directory in strBuff)
{
List<String> lstBuff = GetAllFiles(directory); // 取得したディレクトリ名を引数にして再帰
lstBuff.ForEach(delegate (String str)
{
lstStr.Add(str);
});
}
}
catch (Exception e)
{
// 主に発生する例外は、システムフォルダ等で発生するアクセス拒否
// 例外名:System.UnauthorizedAccessException
Console.WriteLine(e);
}
// 取得したファイル名リストを呼び出し元に返す
return lstStr;
}
private void button1_Click(object sender, EventArgs e)
{
// ウインドウタイトルを探索中に変更
this.Text = "ディレクトリ探索中...";
// ファイル名を取得
List<String> files = GetAllFiles(DirPath);
// ウインドウタイトルを元に戻す
this.Text = ProductName;
// 取得したファイル名をリストボックスに追加
listBox1.Items.Clear(); // 一旦リストボックスをクリア
foreach (String file in files)
{
listBox1.Items.Add(file); // リストボックスに1件ずつ追加
}
MessageBox.Show("ファイル名取得に成功しました!");
}
}
}
これを利用するとちょっとしたファイルビューアプリ(テキストファイルの中身をリストボックスのファイル名をクリックしたらすぐに見ることができるやつ)や画像ファイル検索アプリなど色々と作れそうです。
以上、サブディレクトリも含めた全てのファイル名取得するでした。


コメント