簡單, 就 Directory.GetDirectories() 就好啦, 但是如果要列舉的對象有著為數眾多(比如說有好幾萬)的子資料夾或是檔案的話, 那可有得等了, 而且還要有充足的記憶體供它使用, 因為它的回傳值是 string[], 所以如果有類似的需求的話, 只能另想它法囉!
當下想到的就是 WIN32 API 的 FindFirstFile, FindNextFile 函式, 可是要在 .NET 環境中使用的話, 還是稍稍封裝一下比較好, 所以第一步當然是要準備那些 DllImport 的宣告, 和最煩人的 WIN32_FIND_DATA 資料結構囉, 自己參考文件照樣重新製作一個當然也是可以, 可是如果有現成的話該多好!
先用 Reflector 參考一下 Directory.GetDirectories 是怎麼寫的, 它既然要列舉資料夾, 應該也一定會用到那些 API 才對, 果然! 相關的宣告如下:
[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr FindFirstFile(string fileName, [In, Out] WIN32_FIND_DATA data); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern bool FindNextFile(IntPtr hndFindFile, [In, Out, MarshalAs(UnmanagedType.LPStruct)] WIN32_FIND_DATA lpFindFileData); [DllImport("kernel32.dll")] [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)] private static extern bool FindClose(IntPtr handle);
[Serializable, StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto), BestFitMapping(false)] private class WIN32_FIND_DATA { internal int dwFileAttributes; internal int ftCreationTime_dwLowDateTime; internal int ftCreationTime_dwHighDateTime; internal int ftLastAccessTime_dwLowDateTime; internal int ftLastAccessTime_dwHighDateTime; internal int ftLastWriteTime_dwLowDateTime; internal int ftLastWriteTime_dwHighDateTime; internal int nFileSizeHigh; internal int nFileSizeLow; internal int dwReserved0; internal int dwReserved1; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] internal string cFileName; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] internal string cAlternateFileName; }
接下來要就要自己實作一個回傳 IEnumerable 的方法, 每列舉一次只回傳一筆結果, 既可避免等待的問題, 又可以隨時停止, 如果在 .NET 1.x 的時代, 要實作一個 IEnumerable 比較麻煩, 但是 .NET 2.0 因為有 yield 這個新元素可以使用, 所以問題就簡化到一個不可思議的地步, 所以先實作一個內部使用的 internalGetFileDirectoryNames 的方法, 如下:
private const int FILE_ATTRIBUTE_DIRECTORY = 0x10; private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);
private static IEnumerable internalGetFileDirectoryNames(string path, string searchPattern, bool includeFiles, bool includeDirs, SearchOption searchOption) { WIN32_FIND_DATA data = new WIN32_FIND_DATA(); IntPtr findHandle = FindFirstFile(Path.Combine(path, searchPattern), data); if (findHandle == INVALID_HANDLE_VALUE) yield break; try { List<string> dirs = new List<string>(); do { bool isDirectory = (data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0; if (data.cFileName != "." && data.cFileName != "..") { if (isDirectory && searchOption == SearchOption.AllDirectories) dirs.Add(data.cFileName); if ((isDirectory && includeDirs) || (!isDirectory && includeFiles)) { yield return Path.Combine(path, data.cFileName); } } } while (FindNextFile(findHandle, data)); foreach (string dir in dirs) { foreach (string f in internalGetFileDirectoryNames(Path.Combine(path, dir), searchPattern, includeFiles, includeDirs, searchOption)) yield return Path.Combine(path, f); } } finally { bool r = FindClose(findHandle); System.Diagnostics.Debug.Assert(r); } }
最後, 模擬原生的 Directory.GetFiles, Directory.GetDirectories, Directory.GetFileSystemEntries 函式簽名, 自己 overload 一下就搞定啦, 以下列出最多屬性的函式宣告:
public static IEnumerable GetFiles(string path, string searchPattern, SearchOption searchOption) { return internalGetFileDirectoryNames(path, searchPattern, true, false, searchOption); }
public static IEnumerable GetDirectories(string path, string searchPattern, SearchOption searchOption) { return internalGetFileDirectoryNames(path, searchPattern, false, true, searchOption); }
public static IEnumerable GetFileSystemEntries(string path, string searchPattern, SearchOption searchOption) { return internalGetFileDirectoryNames(path, searchPattern, true, true, searchOption); }
完成啦! 完整的程式碼在這裡!
沒有留言:
張貼留言