1 //------------------------------------------------------------------------------
2 // <copyright file="FileEnumerator.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
5 //------------------------------------------------------------------------------
10 * Copyright (c) 2003 Microsoft Corporation
12 * Class to efficiently enumerate the files in a directory. The only thing the framework provides
13 * to do this is Directory.GetFiles(), which is unusable on large directories because it returns an
14 * array containing all the file names at once (huge memory allocation).
16 * An efficient alternative is to use FindFirstFile/FindNextFile, which works but requires a lot
17 * more code. Also, it makes our code base harder to port to non-windows platforms.
19 * This FileEnumerator class solves both problem, by providing a simple and efficient wrapper.
20 * By working with a single object, it is almost as efficient as calling FindFirstFile/FindNextFile,
21 * but is much easier to use. e.g. instead of:
23 * UnsafeNativeMethods.WIN32_FIND_DATA wfd;
24 * IntPtr hFindFile = UnsafeNativeMethods.FindFirstFile(physicalDir + @"\*.*", out wfd);
26 * if (hFindFile == INVALID_HANDLE_VALUE)
30 * for (bool more=true; more; more=UnsafeNativeMethods.FindNextFile(hFindFile, out wfd)) {
32 * // Skip false directories
33 * if (wfd.cFileName == "." || wfd.cFileName == "..")
36 * string fullPath = Path.Combine(physicalDir, wfd.cFileName);
38 * ProcessFile(fullPath);
42 * UnsafeNativeMethods.FindClose(hFindFile);
47 * foreach (FileData fileData in FileEnumerator.Create(physicalDir)) {
48 * ProcessFile(fileData.FullName);
53 namespace System.Web.Util {
56 using System.Collections;
59 * This is a somewhat artificial base class for FileEnumerator. The main reason
60 * for it is to allow user code to be more readable, by looking like:
61 * foreach (FileData fileData in FileEnumerator.Create(path)) { ... }
63 * foreach (FileEnumerator fileData in FileEnumerator.Create(path)) { ... }
65 internal abstract class FileData {
67 protected string _path;
68 protected UnsafeNativeMethods.WIN32_FIND_DATA _wfd;
70 internal string Name {
71 get { return _wfd.cFileName; }
74 internal string FullName {
75 get { return _path + @"\" + _wfd.cFileName; }
78 internal bool IsDirectory {
79 get { return (_wfd.dwFileAttributes & UnsafeNativeMethods.FILE_ATTRIBUTE_DIRECTORY) != 0; }
82 internal bool IsHidden {
83 get { return (_wfd.dwFileAttributes & UnsafeNativeMethods.FILE_ATTRIBUTE_HIDDEN) != 0; }
86 internal FindFileData GetFindFileData() {
87 return new FindFileData(ref _wfd);
91 internal class FileEnumerator: FileData, IEnumerable, IEnumerator, IDisposable {
92 private IntPtr _hFindFile = UnsafeNativeMethods.INVALID_HANDLE_VALUE;
94 internal static FileEnumerator Create(string path) {
95 return new FileEnumerator(path);
98 private FileEnumerator(string path) {
99 _path = Path.GetFullPath(path);
103 ((IDisposable)this).Dispose();
106 // Should the current file be excluded from the enumeration
107 private bool SkipCurrent() {
109 // Skip false directories
110 if (_wfd.cFileName == "." || _wfd.cFileName == "..")
116 // We just return ourselves for the enumerator, to avoid creating a new object
117 IEnumerator IEnumerable.GetEnumerator() {
121 bool IEnumerator.MoveNext() {
124 if (_hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
125 _hFindFile = UnsafeNativeMethods.FindFirstFile(_path + @"\*.*", out _wfd);
127 // Empty enumeration case
128 if (_hFindFile == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
132 bool hasMoreFiles = UnsafeNativeMethods.FindNextFile(_hFindFile, out _wfd);
142 // The current object of the enumeration is always ourselves. No new object created.
143 object IEnumerator.Current {
147 void IEnumerator.Reset() {
148 // We don't support reset, though it would be easy to add if needed
149 throw new InvalidOperationException();
152 void IDisposable.Dispose() {
153 if (_hFindFile != UnsafeNativeMethods.INVALID_HANDLE_VALUE) {
154 UnsafeNativeMethods.FindClose(_hFindFile);
155 _hFindFile = UnsafeNativeMethods.INVALID_HANDLE_VALUE;
157 System.GC.SuppressFinalize(this);