Search code examples
.netiodirectory

How to quickly check if folder is empty (.NET)?


I have to check, if directory on disk is empty. It means, that it does not contain any folders/files. I know, that there is a simple method. We get array of FileSystemInfo's and check if count of elements equals to zero. Something like that:

public static bool CheckFolderEmpty(string path)
{
    if (string.IsNullOrEmpty(path))
    {
        throw new ArgumentNullException("path");
    }

    var folder = new DirectoryInfo(path);
    if (folder.Exists)
    {
        return folder.GetFileSystemInfos().Length == 0;
    }

    throw new DirectoryNotFoundException();
}

This approach seems OK. BUT!! It is very, very bad from a perspective of performance. GetFileSystemInfos() is a very hard method. Actually, it enumerates all filesystem objects of folder, gets all their properties, creates objects, fills typed array etc. And all this just to simply check Length. That's stupid, isn't it?

I just profiled such code and determined, that ~250 calls of such method are executed in ~500ms. This is very slow and I believe, that it is possible to do it much quicker.

Any suggestions?


Solution

  • Here is the extra fast solution, that I finally implemented. Here I am using WinAPI and functions FindFirstFile, FindNextFile. It allows to avoid enumeration of all items in Folder and stops right after detecting the first object in the Folder. This approach is ~6(!!) times faster, than described above. 250 calls in 36ms!

    private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
    
    [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
    private struct WIN32_FIND_DATA
    {
        public uint dwFileAttributes;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
        public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
        public uint nFileSizeHigh;
        public uint nFileSizeLow;
        public uint dwReserved0;
        public uint dwReserved1;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
        public string cFileName;
        [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
        public string cAlternateFileName;
    }
    
    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);
    
    [DllImport("kernel32.dll", CharSet=CharSet.Auto)]
    private static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);
    
    [DllImport("kernel32.dll")]
    private static extern bool FindClose(IntPtr hFindFile);
    
    public static bool CheckDirectoryEmpty_Fast(string path)
    {
        if (string.IsNullOrEmpty(path))
        {
            throw new ArgumentNullException(path);
        }
    
        if (Directory.Exists(path))
        {
            if (path.EndsWith(Path.DirectorySeparatorChar.ToString()))
                path += "*";
            else
                path += Path.DirectorySeparatorChar + "*";
    
            WIN32_FIND_DATA findData;
            var findHandle = FindFirstFile(path, out findData);
    
            if (findHandle != INVALID_HANDLE_VALUE)
            {
                try
                {
                    bool empty = true;
                    do
                    {
                        if (findData.cFileName != "." && findData.cFileName != "..")
                            empty = false;
                    } while (empty && FindNextFile(findHandle, out findData));
    
                    return empty;
                }
                finally
                {
                    FindClose(findHandle);
                }
            }
    
            throw new Exception("Failed to get directory first file",
                Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
        }
        throw new DirectoryNotFoundException();
    }
    

    I hope it will be useful for somebody in the future.