172

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?

Noam M
  • 3,156
  • 5
  • 26
  • 41
zhe
  • 2,358
  • 2
  • 15
  • 12

18 Answers18

348

There is a new feature in Directory and DirectoryInfo in .NET 4 that allows them to return an IEnumerable instead of an array, and start returning results before reading all the directory contents.

public bool IsDirectoryEmpty(string path)
{
    IEnumerable<string> items = Directory.EnumerateFileSystemEntries(path);
    using (IEnumerator<string> en = items.GetEnumerator())
    {
        return !en.MoveNext();
    }
}

EDIT: seeing that answer again, I realize this code can be made much simpler...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}
Callum Watkins
  • 2,844
  • 4
  • 29
  • 49
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • I like this solution, can it be made to check only for certain filetypes? .Contains("jpg") instead of .any() did not seem to work – Dennis Apr 30 '13 at 11:56
  • 5
    @Dennis, you can specify a wildcard pattern in the call to `EnumerateFileSystemEntries`, or use `.Any(condition)` (specify the condition as a lambda expression, or as a method that takes a path as a parameter). – Thomas Levesque Apr 30 '13 at 12:25
  • The typecast can be removed from the first code example: `return !items.GetEnumerator().MoveNext();` – gary Feb 10 '14 at 03:47
  • 1
    @gary, if you do that, the enumerator will not be disposed, so it will lock the directory until the enumerator is garbage collected. – Thomas Levesque Feb 10 '14 at 09:43
  • This seems to work fine for Directories containing Files, but if the Directory contains other Directors, it comes back saying it is empty. – Kairan May 03 '15 at 18:22
  • @Kairan, I can't reproduce the problem with describe; are you sure you're using `EnumerateFileSystemEntries`, not `EnumerateFiles`? – Thomas Levesque May 03 '15 at 21:05
  • You might also want to exclude hidden or system files, such as `thumbs.db` – marsze Mar 07 '23 at 12:58
39

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.

Adinia
  • 3,722
  • 5
  • 40
  • 58
zhe
  • 2,358
  • 2
  • 15
  • 12
  • 3
    You need to add `SetLastError = true` to the `DllImport` for `FindFirstFile` in order for the `Marshal.GetHRForLastWin32Error()` call to work correctly, as described in the Remarks section of the [MSDN doc for GetHRForLastWin32Error()](http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.gethrforlastwin32error.aspx). – Joel V. Earnest-DeYoung Jun 19 '13 at 07:20
  • I think following answer is little better as it also looks for the files in sub directories https://stackoverflow.com/questions/724148/is-there-a-faster-way-to-scan-through-a-directory-recursively-in-net – Mayank Dec 26 '18 at 15:48
27

You could try Directory.Exists(path) and Directory.GetFiles(path) - probably less overhead (no objects - just strings etc).

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
20
private static void test()
{
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

    string [] dirs = System.IO.Directory.GetDirectories("C:\\Test\\");
    string[] files = System.IO.Directory.GetFiles("C:\\Test\\");

    if (dirs.Length == 0 && files.Length == 0)
        Console.WriteLine("Empty");
    else
        Console.WriteLine("Not Empty");

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

This quick test came back in 2 milliseconds for the folder when empty and when containing subfolders & files (5 folders with 5 files in each)

Eoin Campbell
  • 43,500
  • 17
  • 101
  • 157
16

I use this for folders and files (don't know if it's optimal)

    if(Directory.GetFileSystemEntries(path).Length == 0)
Jmu
  • 169
  • 1
  • 2
8

If you don't mind leaving pure C# and going for WinApi calls, then you might want to consider the PathIsDirectoryEmpty() function. According to the MSDN, the function:

Returns TRUE if pszPath is an empty directory. Returns FALSE if pszPath is not a directory, or if it contains at least one file other than "." or "..".

That seems to be a function which does exactly what you want, so it is probably well optimised for that task (although I haven't tested that).

To call it from C#, the pinvoke.net site should help you. (Unfortunately, it doesn't describe this certain function yet, but you should be able to find some functions with similar arguments and return type there and use them as the basis for your call. If you look again into the MSDN, it says that the DLL to import from is shlwapi.dll)

Elmo
  • 6,409
  • 16
  • 72
  • 140
akavel
  • 4,789
  • 1
  • 35
  • 66
  • Great idea. I didn't know about this function. I'll try to compare it's performance with my approach, that i described above. If it would do faster, i'll reuse it in my code. Thanks. – zhe Jun 18 '09 at 15:44
  • 4
    A note for those who wants go this route. It seems that this PathIsDirectoryEmpty() method from shlwapi.dll works fine on Vista32/64 and XP32/64 machines, but bombs out on some Win7 machines. It must be something to do with versions of shlwapi.dll shipped with different versions of Windows. Beware. – Alex_P Oct 01 '10 at 10:04
7

I don't know about the performance statistics on this one, but have you tried using the Directory.GetFiles() static method ?

It returns a string array containing filenames (not FileInfos) and you can check the length of the array in the same way as above.

Cerebrus
  • 25,615
  • 8
  • 56
  • 70
5

I'm sure the other answers are faster, and your question asked for whether or not a folder contained files or folders... but I'd think most of the time people would consider a directory empty if it contains no files. ie It's still "empty" to me if it contains empty subdirectories... this may not fit for your usage, but may for others!

  public bool DirectoryIsEmpty(string path)
  {
    int fileCount = Directory.GetFiles(path).Length;
    if (fileCount > 0)
    {
        return false;
    }

    string[] dirs = Directory.GetDirectories(path);
    foreach (string dir in dirs)
    {
      if (! DirectoryIsEmpty(dir))
      {
        return false;
      }
    }

    return true;
  }
Brad Parks
  • 66,836
  • 64
  • 257
  • 336
4

Easy and simple:

public static bool DirIsEmpty(string path) {
    int num = Directory.GetFiles(path).Length + Directory.GetDirectories(path).Length;
    return num == 0;
}
Andrew_STOP_RU_WAR_IN_UA
  • 9,318
  • 5
  • 65
  • 101
Matheus Miranda
  • 1,755
  • 2
  • 21
  • 36
3

You will have to go the hard drive for this information in any case, and this alone will trump any object creation and array filling.

Don Reba
  • 13,814
  • 3
  • 48
  • 61
  • 2
    True, although creating some of the objects involves looking up extra metadata on disk that might not be necessary. – Adam Rosenfield Apr 16 '09 at 10:58
  • The ACL would be required for every object for sure. There is no way around it. And once you have to look up those, you might as well read any other information in MFT headers for the files in the folder. – Don Reba Apr 16 '09 at 11:22
3

I'm not aware of a method that will succinctly tell you if a given folder contains any other folders or files, however, using:

Directory.GetFiles(path);
&
Directory.GetDirectories(path);

should help performance since both of these methods will only return an array of strings with the names of the files/directories rather than entire FileSystemInfo objects.

CraigTP
  • 44,143
  • 8
  • 72
  • 99
2

Some time you might want to verify whether any files exist inside sub directories and ignore those empty sub directories; in this case you can used method below:

public bool isDirectoryContainFiles(string path) {
    if (!Directory.Exists(path)) return false;
    return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any();
}
Leng Weh Seng
  • 725
  • 8
  • 6
2

Thanks, everybody, for replies. I tried to use Directory.GetFiles() and Directory.GetDirectories() methods. Good news! The performance improved ~twice! 229 calls in 221ms. But also I hope, that it is possible to avoid enumeration of all items in the folder. Agree, that still the unnecessary job is executing. Don't you think so?

After all investigations, I reached a conclusion, that under pure .NET further optimiation is impossible. I am going to play with WinAPI's FindFirstFile function. Hope it will help.

zhe
  • 2,358
  • 2
  • 15
  • 12
  • 1
    Out of interest, what are the reasons you need such high performance for this operation? – meandmycode Apr 16 '09 at 11:51
  • 1
    Rather than answer your own question, mark one of the correct answers as the answer (probably the first one posted or the clearest one). This way future users of stackoverflow will see the best answer right under your question! – Ray Hayes Apr 16 '09 at 13:38
0

Based in Brad Parks code:

    public static bool DirectoryIsEmpty(string path)
    {
        if (System.IO.Directory.GetFiles(path).Length > 0) return false;

        foreach (string dir in System.IO.Directory.GetDirectories(path))
            if (!DirectoryIsEmpty(dir)) return false;

        return true;
    }
Ángel Ibáñez
  • 329
  • 1
  • 6
-1

Here is something that might help you doing it. I managed to do it in two iterations.

 private static IEnumerable<string> GetAllNonEmptyDirectories(string path)
   {
     var directories =
        Directory.EnumerateDirectories(path, "*.*", SearchOption.AllDirectories)
        .ToList();

     var directoryList = 
     (from directory in directories
     let isEmpty = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Length == 0
     where !isEmpty select directory)
     .ToList();

     return directoryList.ToList();
   }
Gabriel Marius Popescu
  • 2,016
  • 2
  • 20
  • 22
-1

Since you are going to work with a DirectoryInfo object anyway, I'd go with an extension

public static bool IsEmpty(this DirectoryInfo directoryInfo)
{
    return directoryInfo.GetFileSystemInfos().Count() == 0;
}
The_Black_Smurf
  • 5,178
  • 14
  • 52
  • 78
-1

My code is amazing it just took 00:00:00.0007143 less than milisecond with 34 file in folder

   System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

     bool IsEmptyDirectory = (Directory.GetFiles("d:\\pdf").Length == 0);

     sw.Stop();
     Console.WriteLine(sw.Elapsed);
Prashant Cholachagudda
  • 13,012
  • 23
  • 97
  • 162
  • Actually, if you multiply it by 229 and add GetDirectories(), you will get the same result, as mine :) – zhe Apr 16 '09 at 11:45
-3

Use this. It's simple.

Public Function IsDirectoryEmpty(ByVal strDirectoryPath As String) As Boolean
        Dim s() As String = _
            Directory.GetFiles(strDirectoryPath)
        If s.Length = 0 Then
            Return True
        Else
            Return False
        End If
    End Function
Jack
  • 10,943
  • 13
  • 50
  • 65
  • 2
    Simple, perhaps. But incorrect. It has two major bugs: It does not detect if any *folders* are in the path, only files, and it will throw an exception on a path that does not exist. It's also likely to actually be *slower* than the OP's original, because I'm fairly sure it gets all the entries and filters them. – Andrew Barber Sep 06 '12 at 19:59