Answered by:
Check if folder has subfolders (fastest)

Question
-
Currently I am doing it with
DirectoryInfo directory = new DirectoryInfo(SubFolderFullPath); DirectoryInfo[] subdirs = directory.GetDirectories(); if (subdirs.Length == 0) hasSubfolders = false;
This is too slow for my directory tree. I every other program with directory tree generates "+" for foder with subfolders faster.Wednesday, April 10, 2013 3:04 PM
Answers
-
My appologies...
Below is a test code with a better version for checking if the enumerable list is empty or not. Using .First() was a bad suggestion on my part. Set the list of test folders and the number of tests to run to something resonable for your requirements and post the results.
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace SubDirectories { class Program { static bool HasSubfoldersDavidDax(string path) { DirectoryInfo directory = new DirectoryInfo(path); DirectoryInfo[] subdirs = directory.GetDirectories(); return (subdirs.Length != 0); } static bool HasSubfoldersAlternate(string path) { IEnumerable<string> subfolders = Directory.EnumerateDirectories(path); return subfolders != null && subfolders.Any(); } static void Main(string[] args) { string[] TestFolders = new string[] { @"c:\test", @"c:\Forum Post Codes", @"c:\Windows", @"c:\users" }; int tests = 10000; Random r = new Random(); Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < tests; ++i) { bool b = HasSubfoldersAlternate(TestFolders[r.Next(0, TestFolders.Length)]); } Console.WriteLine("My Method: Elapsed Time: {0} ms", sw.ElapsedMilliseconds); sw = new Stopwatch(); sw.Start(); for (int i = 0; i < tests; ++i) { bool b = HasSubfoldersDavidDax(TestFolders[r.Next(0, TestFolders.Length)]); } Console.WriteLine("DavidDax Method: Elapsed Time: {0} ms", sw.ElapsedMilliseconds); Console.ReadKey(false); } } }
Mike- Marked as answer by UXDguy Friday, April 12, 2013 4:17 PM
Thursday, April 11, 2013 9:48 PM
All replies
-
Directory.EnumerateDirectories could be faster just by checking the first element returned exists. That said, if you need all the directories anyways to make the subtree, then the gain is lost when you need all for the next step.
Mike
- Proposed as answer by Patrice ScribeMVP Wednesday, April 10, 2013 3:59 PM
Wednesday, April 10, 2013 3:36 PM -
But doesn't Directory.EnumerateDirectories also return the whole list? How do I get to the first folder?Wednesday, April 10, 2013 3:56 PM
-
No, the EnumerateDirectories call returns an Enumerable Collection, which is not evaluated until you itterate through the list. It will return quicker than the GetDirectories, which must build the entire collection. See the second paragraph in the remarks section of the following link.
http://msdn.microsoft.com/en-us/library/dd383304.aspx
Mike
Wednesday, April 10, 2013 4:09 PM -
Sorry, I left the answer to your second question out. You can call .First() to get the first item returned. An exception is thrown if null, so use a try/catch.
Mike
Wednesday, April 10, 2013 4:24 PM -
I think if you use Directory.GetDirectories(subFolderPath).Length > 0 is little faster than yours, which searches the currentDirectory on SearchOption.TopDirectoryOnly
--Krishna
Wednesday, April 10, 2013 4:28 PM -
@Mike
var dirs = Directory.EnumerateDirectories(SubFolderFullPath).First();
is much slower!
199 vs 77 ms
510 vs 185 ms
Any other ideas? There must be a faster way. Look how fast Win explorer generates those arrows.
Thursday, April 11, 2013 2:21 PM -
One statement of code and four times does not tell me much...
Mike
Thursday, April 11, 2013 9:10 PM -
My appologies...
Below is a test code with a better version for checking if the enumerable list is empty or not. Using .First() was a bad suggestion on my part. Set the list of test folders and the number of tests to run to something resonable for your requirements and post the results.
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace SubDirectories { class Program { static bool HasSubfoldersDavidDax(string path) { DirectoryInfo directory = new DirectoryInfo(path); DirectoryInfo[] subdirs = directory.GetDirectories(); return (subdirs.Length != 0); } static bool HasSubfoldersAlternate(string path) { IEnumerable<string> subfolders = Directory.EnumerateDirectories(path); return subfolders != null && subfolders.Any(); } static void Main(string[] args) { string[] TestFolders = new string[] { @"c:\test", @"c:\Forum Post Codes", @"c:\Windows", @"c:\users" }; int tests = 10000; Random r = new Random(); Stopwatch sw = new Stopwatch(); sw.Start(); for (int i = 0; i < tests; ++i) { bool b = HasSubfoldersAlternate(TestFolders[r.Next(0, TestFolders.Length)]); } Console.WriteLine("My Method: Elapsed Time: {0} ms", sw.ElapsedMilliseconds); sw = new Stopwatch(); sw.Start(); for (int i = 0; i < tests; ++i) { bool b = HasSubfoldersDavidDax(TestFolders[r.Next(0, TestFolders.Length)]); } Console.WriteLine("DavidDax Method: Elapsed Time: {0} ms", sw.ElapsedMilliseconds); Console.ReadKey(false); } } }
Mike- Marked as answer by UXDguy Friday, April 12, 2013 4:17 PM
Thursday, April 11, 2013 9:48 PM -
Both Directory.EnumerateDirectories and DirectoryInf.GetDirectories use FindFirstFile / FindNextFile. EnumerateDirectories saves your time spent in FindNextFile while GetDirectories enumerates the whole list. However when you have a large folder, the bottleneck is probably FindFirstFile. You can try p-invoke FindFirstFileEx with FindExSearchLimitToDirectories, but it can't help much if you got a lot of folders.
For how Windows Explorer manages to show a plus sign quickly, check http://blogs.msdn.com/b/oldnewthing/archive/2008/02/18/7761976.aspx.
Visual C++ MVPThursday, April 11, 2013 9:58 PM -
Mike: Elapsed Time: 745 ms
DavidDax Method: Elapsed Time: 881 ms
Bazzy Method: Elapsed Time: 396 mspublic class API { // Declares a managed prototype for the unmanaged function. [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern IntPtr FindFirstFile(string fileName, [In, Out] FindData findFileData); [DllImport("Kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern bool FindNextFile(IntPtr hFindFile, [In, Out] FindData findFileData); [DllImport("kernel32.dll")] public static extern void SetLastError(uint dwErrCode); } static bool HasSubFolder(string directory) { FindData fd = new FindData(); IntPtr handle = API.FindFirstFile("C:\\" + directory + "\\*.*", fd); bool hasSubFolder = false; API.SetLastError(0); while (Marshal.GetLastWin32Error() != 18) { if (fd.fileName != "." && fd.fileName != "..") { if (fd.fileAttributes < 32) { hasSubFolder = true; break; } } API.FindNextFile(handle, fd); } return hasSubFolder; }
Friday, April 12, 2013 12:10 AM -
Any other ideas? There must be a faster way. Look how fast Win explorer generates those arrows.
The fastest way to process soemthing is to read it out of memory.
So Windwos Explorer propably scans the entire directory in a Background Thread long before you ever open it. It could even delay you from viewing the Folders until it has fully read the Folder-structure.
And of course WinExplorer is written in C++ and thus is faster then C# or any other CLI-Programm. It also may have direct access to Windows API fucntions (wich are written in C++), where your .NET Programms have to go through the .NET wrapepr for said API.
But honestly: What type of time crictical applciation do you have that those Miliseconds make any difference?
Let's talk about MVVM: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/b1a8bf14-4acd-4d77-9df8-bdb95b02dbe2
Friday, April 12, 2013 12:25 AM -
This is a good method Mike. Thank you.
I did test it also on some network drives on the different servers and you method is always faster, ranging from a few ms and up to 50%.
I do think that I will skip checking for those on network drives, as I had a few times high network traffic while testing and it gets quicker to click on a few folders than to wait for several seconds until all are enumerated.
- Edited by UXDguy Friday, April 12, 2013 4:17 PM
Friday, April 12, 2013 4:14 PM -
Bazzzy, I removed your "c:\\" so I can test other drives, and then it did not run faster than Mike's method. Maybe I made a bad struct for FindData since you did not write it there, but I often had also "AccessViolationException was unhandled" errorsFriday, April 12, 2013 4:17 PM