Перебор файлов с использованием FindFirstFile/FindNextFile и
От: VladD2 Российская Империя www.nemerle.org
Дата: 27.09.05 05:52
Оценка: 77 (7)
#Имя: FAQ.src.cs.RsdnDirectory
Перебор файлов с использованием 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>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re: Перебор файлов с использованием FindFirstFile/FindNextFi
От: GlebZ Россия  
Дата: 27.09.05 11:39
Оценка:
Здравствуйте, VladD2, Вы писали:

Не есть good. Если предположить алгоритм Pavel Dvorkin, то если мы пройдем 100 файлов и потом остановимся, то FindClose не будет вызван.

С уважением, Gleb.
ЗЫ GetFilse — это круто Внушаеть. Даже для 9 утра
Re[2]: Перебор файлов с использованием FindFirstFile/FindNex
От: VladD2 Российская Империя www.nemerle.org
Дата: 27.09.05 15:14
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>Здравствуйте, VladD2, Вы писали:


GZ>Не есть good. Если предположить алгоритм Pavel Dvorkin, то если мы пройдем 100 файлов и потом остановимся, то FindClose не будет вызван.


Создай тест вроде:
try
{
    foreach (string file in System.IO.RsdnDirectory.GetFilse(@"C:\"))
    {
        Console.WriteLine("\t" + file);
        throw new Exception();
    }
}
catch
{
    Console.WriteLine("Произошло исключение. :)");
}

Поставь точку прерывания на строке:
FindClose(findHandle);

и убедись в том, что ты недооценивашь создателей компиляторов и меня.
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[3]: Перебор файлов с использованием FindFirstFile/FindNex
От: GlebZ Россия  
Дата: 27.09.05 15:24
Оценка:
Здравствуйте, VladD2, Вы писали:

Я не об этом.
А вот так?

IEnumerator<string> enumerator=System.IO.RsdnDirectory.GetFilse(@"C:\").GetEnumerator();
for (int i=0;i<10;i++)
{
     enumerator.MoveNext();
}

Когда FindClose вызовется? Есть подозрение что никогда.

С уважением, Gleb.
Re[4]: Перебор файлов с использованием FindFirstFile/FindNex
От: VladD2 Российская Империя www.nemerle.org
Дата: 27.09.05 16:52
Оценка: +1
Здравствуйте, GlebZ, Вы писали:

GZ>
GZ>IEnumerator<string> enumerator=System.IO.RsdnDirectory.GetFilse(@"C:\").GetEnumerator();
GZ>for (int i=0;i<10;i++)
GZ>{
GZ>     enumerator.MoveNext();
GZ>}
GZ>


GZ>Когда FindClose вызовется? Есть подозрение что никогда.


И часто ты такой фигней занимашься? Файлы, кстати, так же читашь? Хотя у файлов конечно есть финалйзеры. Но я бы на них рассчитывать не стал бы.

Какой смысл вести речь, о намеренно ошибочном коде?
... << RSDN@Home 1.2.0 alpha rev. 618>>
Есть логика намерений и логика обстоятельств, последняя всегда сильнее.
Re[4]: Перебор файлов с использованием FindFirstFile/FindNex
От: Блудов Павел Россия  
Дата: 28.09.05 01:14
Оценка:
Здравствуйте, GlebZ, Вы писали:

GZ>А вот так?


GZ>
GZ>IEnumerator<string> enumerator=System.IO.RsdnDirectory.GetFilse(@"C:\").GetEnumerator();
GZ>for (int i=0;i<10;i++)
GZ>{
GZ>     enumerator.MoveNext();
GZ>}
GZ>

GZ>Когда FindClose вызовется? Есть подозрение что никогда.

Вот так нельзя. нужно так

using(IEnumerator<string> enumerator=System.IO.RsdnDirectory.GetFilse(@"C:\").GetEnumerator())
for (int i=0;i<10;i++)
{
     enumerator.MoveNext();
}


Иначе FindClose вызовется когда-то а не когда надо.
... << RSDN@Home 1.1.4 beta 6a rev. 436>>
Re[5]: Перебор файлов с использованием FindFirstFile/FindNex
От: GlebZ Россия  
Дата: 28.09.05 08:17
Оценка:
Здравствуйте, VladD2, Вы писали:

VD>И часто ты такой фигней занимашься? Файлы, кстати, так же читашь? Хотя у файлов конечно есть финалйзеры. Но я бы на них рассчитывать не стал бы.


VD>Какой смысл вести речь, о намеренно ошибочном коде?

Точно, недооценил

С уважением, Gleb.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.