使用递归获取目录树

递归的一个用途是导航分层数据结构,如文件系统目录树,而不知道树有多少级别或每个级别上的对象数。在此示例中,你将看到如何在目录树上使用递归来查找指定目录的所有子目录,并将整个树打印到控制台。

internal class Program
{
    internal const int RootLevel = 0;
    internal const char Tab = '\t';

    internal static void Main()
    {
        Console.WriteLine("Enter the path of the root directory:");
        var rootDirectorypath = Console.ReadLine();

        Console.WriteLine(
            $"Getting directory tree of '{rootDirectorypath}'");

        PrintDirectoryTree(rootDirectorypath);
        Console.WriteLine("Press 'Enter' to quit...");
        Console.ReadLine();
    }

    internal static void PrintDirectoryTree(string rootDirectoryPath)
    {
        try
        {
            if (!Directory.Exists(rootDirectoryPath))
            {
                throw new DirectoryNotFoundException(
                    $"Directory '{rootDirectoryPath}' not found.");
            }

            var rootDirectory = new DirectoryInfo(rootDirectoryPath);
            PrintDirectoryTree(rootDirectory, RootLevel);
        }
        catch (DirectoryNotFoundException e)
        {
            Console.WriteLine(e.Message);
        }
    }

    private static void PrintDirectoryTree(
        DirectoryInfo directory, int currentLevel)
    {
        var indentation = string.Empty;
        for (var i = RootLevel; i < currentLevel; i++)
        {
            indentation += Tab;
        }

        Console.WriteLine($"{indentation}-{directory.Name}");
        var nextLevel = currentLevel + 1;
        try
        {
            foreach (var subDirectory in directory.GetDirectories())
            {
                PrintDirectoryTree(subDirectory, nextLevel);
            }
        }
        catch (UnauthorizedAccessException e)
        {
            Console.WriteLine($"{indentation}-{e.Message}");
        }
    }
}

此代码比完成此任务的最低限度更复杂,因为它包括异常检查以处理获取目录的任何问题。你可以在下面找到将代码拆分为较小的段,并对每个段进行说明。

Main

main 方法将来自用户的输入作为字符串,该字符串用作根目录的路径。然后使用此字符串作为参数调用 PrintDirectoryTree 方法。

PrintDirectoryTree(string)

这是处理实际目录树打印的两种方法中的第一种。此方法将表示根目录路径的字符串作为参数。它检查路径是否是实际目录,如果不是,则抛出 DirectoryNotFoundException,然后在 catch 块中处理。如果路径是真实目录,则从路径创建 DirectoryInfo 对象 rootDirectory,并使用 rootDirectory 对象和 RootLevel 调用第二个 PrintDirectoryTree 方法,RootLevel 是一个值为零的整数常量。

PrintDirectoryTree(DirectoryInfo, int)

第二种方法处理工作的主要问题。它需要一个 DirectoryInfo 和一个整数作为参数。DirectoryInfo 是当前目录,整数是目录相对于根的深度。为了便于阅读,输出将缩进当前目录的每个级别,以便输出如下所示:

-Root
    -Child 1
    -Child 2
        -Grandchild 2.1
    -Child 3

打印当前目录后,将检索其子目录,然后在每个目录上调用此方法,其深度级别值比当前值大 1。那部分是递归:调用自身的方法。程序将以这种方式运行,直到它访问了树中的每个目录。当它到达没有子目录的目录时,该方法将自动返回。

此方法还捕获 UnauthorizedAccessException,如果当前目录的任何子目录受系统保护,则抛出 UnauthorizedAccessException。为了一致性,将在当前缩进级别打印错误消息。

下面的方法提供了解决此问题的更基本方法:

internal static void PrintDirectoryTree(string directoryName)
{
    try
    {
        if (!Directory.Exists(directoryName)) return;
        Console.WriteLine(directoryName);
        foreach (var d in Directory.GetDirectories(directoryName))
        {
            PrintDirectoryTree(d);
        }
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }
}

这不包括第一种方法的特定错误检查或输出格式,但它实际上做了同样的事情。由于它只使用字符串而不是 DirectoryInfo,因此无法访问其他目录属性(如权限)。