共用方式為


HOW TO:逐一查看目錄樹狀結構 (C# 程式設計手冊)

「逐一查看目錄樹狀結構」一詞,代表存取指定根資料夾下每個巢狀子目錄中任意深度的每個檔案。 您不需要開啟每個檔案。 只需要以 string 方式擷取檔案或子目錄的名稱,或者以 System.IO.FileInfoSystem.IO.DirectoryInfo 物件的型式擷取其他的資訊。

注意事項注意事項

在 Windows 中可以交替使用「目錄」和「資料夾」這兩個詞彙。 大部分的文件和使用者介面文字是使用「資料夾」,但 .NET Framework 類別庫是使用「目錄」一詞。

在最簡單的情況下,當您確定具有指定根資料夾下所有目錄的存取權限時,則可以使用 System.IO.SearchOption.AllDirectories 旗標。 這個旗標會傳回符合指定模式的所有巢狀子目錄。 下列範例顯示如何使用這個旗標。

root.GetDirectories("*.*", System.IO.SearchOption.AllDirectories);

這個方式有個缺點,就是指定根資料夾下如果有任何一個子目錄造成 DirectoryNotFoundExceptionUnauthorizedAccessException,整個方法就會失敗並且不會傳回任何目錄。 同樣的情形也適用於使用 GetFiles 方法時。 如果必須對特定子資料夾處理這些例外狀況 (Exception),您必須手動走遍目錄樹狀結構,如下列範例所示。

手動走遍目錄樹狀結構時,您可以先處理子目錄 (「前序走訪」(Pre-order Traversal)),或是先處理檔案 (「後序走訪」(Post-order Traversal))。 如果執行的是前序走訪,您要先走遍目前資料夾下的整個樹狀結構,才逐一查看直接位於資料夾本身中的檔案。 本文件稍後的範例會執行後序走訪,但您只要簡單修改一下即可執行前序走訪。

另一個選擇為是否要使用遞迴或堆疊式走訪。 本文件稍後的範例會顯示這兩種方式。

如果必須對檔案和資料夾執行各式各樣的作業,您可以藉由將作業重構到個別函式中以模組化這些範例,這樣就可以使用單一委派 (Delegate) 來叫用這些函式。

注意事項注意事項

NTFS 檔案系統可以包含的「重新分析點」(Reparse Point) 型式有「連接點交叉點」(Junction Point)、「符號連結」(Symbolic Link) 和「永久連結」(Hard Link)。 而 GetFilesGetDirectories 這類的 .NET Framework 方法,不會傳回重新分析點下的任何子目錄。 這個行為可以在兩個重新分析點彼此參考時,預防進入無限迴圈的風險。 一般而言,處理重新分析點要特別謹慎,避免不小心修改或刪除檔案。 如果需要精確控制重新分析點,請使用平台叫用或機器碼,直接呼叫適當的 Win32 檔案系統方法。

範例

下列範例會示範如何使用遞迴走遍目錄樹狀結構。 遞迴是很簡潔的方式,但在目錄樹狀結構很大且巢狀結構很深時,很可能造成堆疊溢位 (Stack Overflow) 例外狀況。

這裡所處理的特定例外狀況,以及對每個檔案和資料夾所執行的特定動作,都僅是提供做為範例用途。 您應該針對您的特定需求修改這個程式碼。 如需詳細資訊,請參閱程式碼中的註解。

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);
            }
        }            
    }
}

下列範例會示範如何在不使用遞迴的情況下,逐一查看目錄樹狀結構中的檔案和資料夾。 這項技術使用泛型 Stack<T> 集合型別,這是屬於後進先出 (LIFO) 堆疊。

這裡所處理的特定例外狀況,以及對每個檔案和資料夾所執行的特定動作,都僅是提供做為範例用途。 您應該針對您的特定需求修改這個程式碼。 如需詳細資訊,請參閱程式碼中的註解。

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 區塊,好讓您在存取資料夾遭到拒絕時,可以嘗試升級權限,然後再次存取資料夾。 通常,您只需要攔截那些您可以處理的例外狀況,不要讓應用程式處於未知狀態。

如果必須儲存目錄樹狀結構的內容,不管是存於記憶體或磁碟中,最佳的選項就是只要儲存每個檔案的 FullName 屬性 (型別為 string)。 然後,您可以視需要使用這個字串建立新 FileInfoDirectoryInfo 物件,或者是開啟任何需要其他處理的檔案。

穩固程式設計

建立穩固的檔案逐一查看程式碼,必須考慮許多檔案系統的複雜度。 如需詳細資訊,請參閱 NTFS 技術參考 (英文)。

請參閱

參考

System.IO

概念

LINQ 和檔案目錄

其他資源

檔案系統和登錄 (C# 程式設計手冊)