C#函数返回值后不停止

C# function does not stop after returning the value

我有这个功能,应该从给定的根目录和文件名中找到完整的文件名。

        static string WalkDirectoryTree(DirectoryInfo root, string fileName)
        {
            FileInfo[] files = null;
            DirectoryInfo[] subDirs = null;
            files = root.GetFiles("*.*");
            string fullFileName = "";
            string rnd = "";

            if (files != null)
            {
                foreach (FileInfo file in files)
                {
                    if(file.Name == fileName)
                    {
                        Console.WriteLine("Success!");
                        fullFileName = file.FullName;
                        rnd = file.Name;
                        return fullFileName;
                    }
                }
                subDirs = root.GetDirectories();
                foreach (DirectoryInfo dirInfo in subDirs)
                {
                    if (rnd == fileName)
                    {
                        break;
                    }
                    else
                    {
                        WalkDirectoryTree(dirInfo, fileName);
                    }
                }
            }
            return fullFileName;
        }

我正在查看这段代码如何在断点的帮助下执行。它找到搜索到的文件名并输入 if 语句(如下)

if(file.Name == fileName)
                    {
                        Console.WriteLine("Success!");
                        fullFileName = file.FullName;
                        rnd = file.Name;
                        return fullFileName;
                    }

通过执行,我还发现,它确实执行了上面 if 语句中的 return 步骤,然后直接执行到函数的末尾,但是,它继续执行那个一次又一次地运行,直到它遍历给定根目录中的所有子目录。显然,它会覆盖 returning 值,这不是此函数的目的。

有人可以发现问题并提出解决方案吗?

问题出在您对 WalktDirecotryTree 的调用中。您没有检查它的 return 值,因此如果 return 是您想要的,您需要保释。

恭喜,您发现了一个 tree-walking 问题,与递归函数调用相比,显式堆栈可能更好地解决这个问题。

虽然可以检查递归调用的 return 值,正如一些人评论的那样,但这是有代价的。这是一个更好的选择:

    static string WalkDirectoryTree(DirectoryInfo root, string fileName)
    {
        var to_process = new Stack<DirectoryInfo>();
        to_process.Push(root);

        while (to_process.TryPop(out var dir))
        {
            foreach (FileInfo file in dir.GetFiles(filename))
            {
                if(file.Name == fileName)
                {
                    Console.WriteLine("Success!");
                    return file.FullName;
                }
            }

            foreach (DirectoryInfo subdir in dir.GetDirectories())
            {
                to_process.Push(subdir);
            }
        }
        return null; // no matches
    }

这将像递归版本一样进行 depth-first 搜索,但它不会在调用堆栈上使用多个帧,因此第一个匹配项可以立即 return 到原始调用者,而递归版本 return 将其自身向上一级目录。请注意,由于使用了 LIFO 堆栈,搜索将以相反的顺序进行,子目录从 GetDirectories() returned...如果重要,可以通过反转 GetDirectories() 结果来更改在循环和推送之前。

我还将传递给 dir.GetFiles 的文件规范从 *.* 更改为 filename,因为您对目录中的所有文件都不感兴趣。我没有取出 case-sensitive 字符串相等性测试,但你可能也不想要它。


不过,学习编写和调试递归代码是个好主意。在调试器中单步执行时,确保打开“调用堆栈”调试器 window,并且 当您点击 return 语句时仔细观察调用堆栈 window .


最后,这个递归搜索被内置到框架中。简直

return root.EnumerateFiles(fileName, SearchOption.AllDirectories).FirstOrDefault();

足以执行在第一个匹配项处停止的递归搜索(与将相同的 SearchOption.AllDirectories 传递给 GetFiles() 不同,后者会浪费地查找整个目录树中的每个匹配项)

真的,除了获得经验之外,自己写这篇文章的唯一原因是为了避免文档中提到的陷阱:

If you choose AllDirectories in your search and the directory structure contains a link that creates a loop, the search operation enters an infinite loop.

自己写tree walk时,可以对这个问题做点什么(虽然上面给出的改进代码仍然忽略了这种情况)

正如其他人所指出的那样,您正在调用一个递归函数,因此您会收到很多对 WalkDirectoryTree 的调用,并且每个调用都返回一个且仅返回一个值,但是由于调用次数过多看来一个电话还在继续。

我确实想给你一个替代方案,它可能会为你提供一些food-for-thought关于如何在未来解决此类问题的方法。

你递归的关键是遍历你的目录结构。如果你从一个能做到这一点的方法开始。

static IEnumerable<DirectoryInfo> WalkDirectoryTree(DirectoryInfo root)
{
    yield return root;
    foreach (var di1 in root.GetDirectories())
        foreach (var di2 in WalkDirectoryTree(di1))
            yield return di2;
}

那只是 returns 所有以 root 开头的目录,并向下递归目录结构,直到用完目录。它是延迟评估的,因此它只有 returns 你要求的目录数量。这在您寻找文件时很有用,因为它会在找到您的文件后停止列出目录。

现在的搜索功能很简单non-recursive。

static string FindTheFile(DirectoryInfo root, string fileName)
{
    foreach (var di in WalkDirectoryTree(root))
        foreach (var fi in di.GetFiles())
            if (fi.Name == fileName)
                return fi.FullName;
    return String.Empty;
}

希望对您有所帮助。