Home > Blockchain >  C# function does not stop after returning the value
C# function does not stop after returning the value

Time:03-29

I have this function, that should find full filename from given root directory and file name.

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

I was looking how this code executes with help of breakpoints. It finds the searched file name and it enters that if statement (below)

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

By following along execution I also found out, that it does execute the return step in if statement above, and after that goes straight to the end of the function, but then, it continues to execute that function again and again, until it has gone through all the subdirectories in given root directory. And obviously, it overwrites returning value and it is not what this function was intended to do.

Can someone spot the problem and suggest any solution to this?

CodePudding user response:

Congratulations, you've found a tree-walking problem which may be better served by an explicit stack than recursive function calls.

While it is possible to inspect the return value of recursive calls as several people have commented, there's a price associated with that. Here's a better alternative:

    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
    }

This will do a depth-first search just like the recursive version, but it won't use multiple frames on the call stack, so the first match can return immediately to the original caller, while the recursive version returned to itself one directory level up. Do note that the search will go in the reverse order that subdirectories are returned from GetDirectories() due to using a LIFO stack... if important this can be changed by reversing the GetDirectories() results before looping and pushing.

I've also changed the filespec passed to dir.GetFiles from *.* to filename, since you aren't interested in all files in the directory. I didn't take out the case-sensitive string equality test, but you probably don't want that either.


Still, it's a good idea to learn to write and debug recursive code. When stepping through in the debugger, make sure to have your "Call Stack" debugger window open, and when you hit a return statement watch that Call Stack window carefully.


Finally, this recursive search is built into the framework. Simply

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

is sufficient to perform a recursive search that stops at the first match (unlike passing the same SearchOption.AllDirectories to GetFiles() which will wastefully find every match in the whole directory tree)

Really, the only reason for writing this yourself, apart from the experience gained, is to avoid the pitfall mentioned in the documentation:

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.

When writing the tree walk yourself, it's possible to do something about this problem (although the improved code presented above still ignores this situation)

CodePudding user response:

As others have pointed out, you're calling a recursive function so you're getting a lot of calls to WalkDirectoryTree and each one is returning one and only one value, but because of the many calls it appears like the one call is still going.

I did want to give you an alternative that might provide you with some food-for-thought on how to tackle this kind of problem in the future.

The key thing that you're recursing is the traversal of your directory structure. If you start with a method that just does that.

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

That just returns all of the directories starting with root and recursing down the directory structure until it runs out of directories. It's lazily evaluated so it only returns as many directories as you ask for. That's useful when you go looking for your file as it will stop listing directories once it finds your file.

Now the search function is simple and 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;
}

I hope this helps.

CodePudding user response:

The problem is in your call to WalktDirecotryTree. You don't check it's return value, so you need to bail if that returns what you wanted.

  • Related