Перебор файлов с использованием FindFirstFile/FindNextFile и итераторов C# 2.0

Демонстрирует использование итераторов C# 2.0. В том числе такие не тривиальные вещи как обработка исключений внутри итераторов и рекурсивные итераторы.
Сразу предупреждаю. Код не из рабочего проекта, а так... тест написанный под впечатлением от этого
Автор: Sinclair
Дата: 26.09.05
сообщения и вообще той задолбавшей темы.
Так как код писался параллельно процессу отладки генератора архива для RSDN CD, да еще и в 9 утра после бессонной ночи, притензии по качеству кода и темблее орфографии не приниаются . Так что незабудьте прочесть последнюю фразу в этом сообщении.

Итак... к делу!

Тест:
using System;

class Program
{
    static void Main(string[] args)
    {
        foreach (string dir in System.IO.RsdnDirectory.GetAllDirectories(@"..\..\.."))
        {
          Console.WriteLine("Dir: '" + dir + "' contains:");

            foreach (string file in System.IO.RsdnDirectory.GetFilse(dir))
                Console.WriteLine("\t" + file);
        }
    }
}

Выводит:
Dir: '..\..\..\FindFirstFile' contains:
        FindFirstFile.csproj
        FindFirstFile.csproj.user
        Program.cs
Dir: '..\..\..\FindFirstFile\bin' contains:
Dir: '..\..\..\FindFirstFile\bin\Debug' contains:
        FindFirstFile.exe
        FindFirstFile.pdb
Dir: '..\..\..\FindFirstFile\bin\Release' contains:
Dir: '..\..\..\FindFirstFile\obj' contains:
        FindFirstFile.csproj.FileList.txt
Dir: '..\..\..\FindFirstFile\obj\Debug' contains:
        FindFirstFile.exe
        FindFirstFile.pdb
Dir: '..\..\..\FindFirstFile\obj\Debug\Refactor' contains:
Dir: '..\..\..\FindFirstFile\obj\Debug\TempPE' contains:
Dir: '..\..\..\FindFirstFile\obj\Release' contains:
Dir: '..\..\..\FindFirstFile\Properties' contains:
        AssemblyInfo.cs

Исходник:
namespace System.IO
{
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Runtime.InteropServices;
    using System.IO;
    using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;
    using System.ComponentModel;

    class RsdnDirectory
    {
        /// <summary>
        /// Формирует путь требуемый функцией FindFirstFile.
        /// </summary>
        private static string MakePath(string path)
        {
            return Path.Combine(path, "*");
        }

        /// <summary>
        /// Возвращает список файлов или каталогов находящихся по заданному пути.
        /// </summary>
        /// <param name="path">Путь для которого нужно возвратать список.</param>
        /// <param name="isGetDirs">
        /// Если true - функция возвращает список каталогов, иначе файлов.
        /// </param>
        /// <returns>Список файлов или каталогов.</returns>
        private static IEnumerable<string> GetInternal(string path, bool isGetDirs)
        {
            // Структура в которую функции FindFirstFile и FindNextFileвозвращают
            // информацию о текущем файле.
            WIN32_FIND_DATA findData;
            // Получаем информацию о текущем файле и хэндл перечислителя виндовс.
            // Этот хэндл требуется передавать функции FindNextFile для плучения
            // следующих файлов.
            IntPtr findHandle = FindFirstFile(MakePath(path), out findData);

            // Хреновый хэндл говорит, о том, что произошел облом. Следовательно
            // нужно вынуть информацию об ошибке и перепаковать ее в исключение.
            if (findHandle == INVALID_HANDLE_VALUE)
                throw new Win32Exception(Marshal.GetLastWin32Error());

            try
            {
                do
                    if (isGetDirs 
                        ? (findData.dwFileAttributes & FileAttributes.Directory) != 0
                        : (findData.dwFileAttributes & FileAttributes.Directory) == 0)
                        yield return findData.cFileName;
                while (FindNextFile(findHandle, out findData));
            }
            finally
            {
                FindClose(findHandle);
            }
        }

        /// <summary>
        /// Возвращает список файлов для некоторого пути.
        /// </summary>
        /// <param name="path">
        /// Каталог для которого нужно получить список файлов.
        /// </param>
        /// <returns>Список файлов каталога.</returns>
        public static IEnumerable<string> GetFilse(string path)
        {
            return GetInternal(path, false);
        }

        /// <summary>
        /// Возвращает список каталогов для некоторого пути. Функция не перебирает
        /// вложенные подкаталоги!
        /// </summary>
        /// <param name="path">
        /// Каталог для которого нужно получить список подкаталогов.
        /// </param>
        /// <returns>Список файлов каталога.</returns>
        public static IEnumerable<string> GetDirectories(string path)
        {
            return GetInternal(path, true);
        }

        /// <summary>
        /// Функция возвращает список относительных путей ко всем подкаталогам
        /// (в том числе и вложенным) заданного пути.
        /// </summary>
        /// <param name="path">Путь для которого унжно получить подкаталоги.</param>
        /// <returns>Список подкатлогов.</returns>
        public static IEnumerable<string> GetAllDirectories(string path)
        {
            // Сначала перебираем подкаталоги первого уровня вложенности...
            foreach (string subDir in GetDirectories(path))
            {
                // игнорируем имя текущего каталога и родительского.
                if (subDir == ".." || subDir == ".")
                    continue;

                // Комбинируем базовый путь и имя подкаталога.
                string relativePath = Path.Combine(path, subDir);

                // возвращам пользователю относительный путь.
                yield return relativePath;

                // Создаем, рекурсивно, итератор для каждого подкаталога и...
                // возвращаем каждый его элемент в качестве элементов текущего итератоа.
                // Этот прием позволяет обойти ограничение итераторов C# 2.0 связанное
                // с нвозможностью вызовов "yield return" из функций вызваемых из 
                // функции итератора. К сожалению это приводит к созданию временного
                // вложенного итератора на каждом шаге рекурсии, но затраты на создание
                // такого объекта относительно не велики, а удобство очень даже ощутимо.
                foreach (string subDir2 in GetAllDirectories(relativePath))
                    yield return subDir2;
            }
        }

        #region Импорт из kernel32

        private const int MAX_PATH = 260;

        [Serializable]
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        [BestFitMapping(false)]
        private struct WIN32_FIND_DATA
        {
            public FileAttributes dwFileAttributes;
            public FILETIME ftCreationTime;
            public FILETIME ftLastAccessTime;
            public FILETIME ftLastWriteTime;
            public int nFileSizeHigh;
            public int nFileSizeLow;
            public int dwReserved0;
            public int dwReserved1;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = MAX_PATH)]
            public string cFileName;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
            public string cAlternate;
        }

        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr FindFirstFile(string lpFileName,
            out WIN32_FIND_DATA lpFindFileData);

        [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool FindNextFile(IntPtr hFindFile,
            out WIN32_FIND_DATA lpFindFileData);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool FindClose(IntPtr hFindFile);

        private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

        #endregion
    }
}


До кондиции довести напильником.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Автор: VladD2    Оценить