Получить файлы рекурсивно, как относительные пути


Я хочу сделать рекурсивный список файлов в Каталоге, но я хочу, чтобы относительные пути, а не полные пути Directory.GetDirectories() вернется с помощью AllDirectories поиск варианта. Это потому, что я хочу, чтобы прокрутить список файлов в другое место и выполнить выборочную проверку на каждый файл. Я мог бы попробовать конвертировать каждый абсолютный путь в относительный путь, но это немного грязный, поэтому я решил создать рекурсивную функцию, которая генерирует относительные пути, как она идет.

public static string[] GetRelativeFilesRecursive(string path)
{
    IEnumerable<string> GetRelativeFilesRecursive(string basePath, string subFolder)
    {
        IEnumerable<string> getRelativeArray(Func<string, IEnumerable<string>> func)
        {
            return func(Path.Combine(basePath, subFolder)).Select(file => Path.Combine(subFolder, Path.GetFileName(file)));
        }

        IEnumerable<string> files = getRelativeArray(Directory.GetFiles);
        IEnumerable<string> directories = getRelativeArray(Directory.GetDirectories);

        foreach (string directory in directories)
        {
            files = files.Concat(GetRelativeFilesRecursive(basePath, directory));
        }

        return files;
    }

    return GetRelativeFilesRecursive(path, "").ToArray();
}

Я использую вложенные функции, чтобы сделать его более модульным, т. е. я предпочел бы иметь одну функцию, что я могу скопировать и вставить в целом, чем иметь несколько функций, которые могут быть разделены. Я использую IEnumerable<string> вместо string[] потому что иначе мне пришлось бы звонить ToArray() на каждом Concat() звоните и я боюсь, что будет плохо для производительности.

Что вы думаете? Это хороший способ сделать это?



1839
3
задан 28 марта 2018 в 05:03 Источник Поделиться
Комментарии
3 ответа

Опираясь на ответ Хенриком, используя Лин, чтобы вернуться в интерфейс IEnumerable:

return Directory.GetFiles(root, "*", SearchOption.AllDirectories)
.Select(p => p.Remove(0, rootLength));

5
ответ дан 28 марта 2018 в 09:03 Источник Поделиться

Я бы вернулся IEnumerable<string> в последней функции, чтобы избежать вызова методом toArray() для того, чтобы позволить клиенту решить, как обрабатывать вывод:

public static IEnumerable<string> GetRelativeFilesRecursive(string path)
{
...

...

return GetRelativeFilesRecursive(path, "");
}


Как я вижу, ты производить относительные пути к начальной корневой путь.

Я думаю, это можно сделать немного проще, так как это:

IEnumerable<string> GetRelativePaths(string root)
{
int rootLength = root.Length + (root[root.Length - 1] == '\\' ? 0 : 1);

foreach (string path in Directory.GetFiles(root, "*", SearchOption.AllDirectories))
{
yield return path.Remove(0, rootLength);
}
}

4
ответ дан 28 марта 2018 в 06:03 Источник Поделиться

Комментарий из будущего

Этот обзор, если из будущего, поэтому вы не видите его прямо сейчас, еще не готовы ;-)

Я буду писать его в течение нескольких недель или месяцев, когда новые System.Memory пакет официальных. Но если вы думаете, что вам это нравится, то можно в любом случае оставьте свое будущее голосование ;-П


Компоненты, необходимые для изменения, которые я здесь описывать не выпустили и новые возможности по-прежнему в бета-версии, однако они очень интересный и стоит попробовать.


Span<T>

Итак, мы, наконец, можем использовать новые [ReadOnly]Span<T> и [ReadOnly]Memory<T> типов. Но каковы они?


Span<T> это новый тип мы добавляем к платформе для представления смежных областей произвольной памяти, с характеристиками, на равне с T[]. Ее API похожи на массив, но в отличие от массивов, это может указывать на управляемый или родной памяти, или памяти, выделенной на стеке. [2]

Один из API-интерфейсов, что делает его настолько полезным является Slice метод, как Substring для string но не копируя ничего.

но


Полный API поверх пролета еще не доработан [2]

В Slice однако следует остановиться.

К сожалению, мы не можем использовать этот тип здесь, потому что


Пядь-это ссылка на тип, так как он содержит поля ref, и поля ссылки может относиться не только к началу объекты, такие как массивы, но и к их середине. [1]

Эти ссылки называются внутренние указатели, и слежение за ними является относительно дорогостоящей операцией для .Коллектор выполнения фигня. Как таковая, среда выполнения сдерживает этих ссылок, чтобы жить только на стек, так как он обеспечивает неявное низкий лимит на количество внутренних указателей, которые могут быть в наличии. [1]

и потому, что


В результате, случаях продолжительность может жить только в стеке, а не в куче. Это означает, что вы можете не коробка пролетов. [1]

но большинство импорта против нее в нашем случае заключается в том, что


вы не можете использовать диапазон, как универсальный аргумент, как экземпляры, что тип аргумента может в конечном итоге получить в штучной упаковке или иным образом сохранены до кучи (и в настоящее время нет “где T : структура ссылки” ограничение имеется). [1]

и нам нужно что-то, что мы можем использовать для IEnumerable<T> перечислить пути.


Memory<T> на помощь


если Span<T> нельзя положить до кучи и, следовательно, не могут быть сохранены через асинхронные операции, каков ответ? Memory<T>. [1]

Это можно использовать только как Span<T> поэтому мы можем положить его внутрь IEnumerable<T> и переписать метод перечисления путей такой:

public static IEnumerable<ReadOnlyMemory<char>> EnumeratePaths(string path, SearchOption searchOption)
{
return
Directory
.EnumerateFiles(path, "*", searchOption)
.Concat(Directory.EnumerateDirectories(path, "*", searchOption))
.Select(name => new ReadOnlyMemory<char>(name.ToArray()));
}

Он объединяет результаты EnumerateFiles и EnumerateDirectories и возвращает их в виде ReadOnlyMemory<char>.

Для того, чтобы получить относительный путь, мы не используем Replace ни Substring методов, но вместо этого Slice:

public static ReadOnlyMemory<char> RelativePath(this ReadOnlyMemory<char> path, int relativePathStart)
{
return path.Slice(relativePathStart, path.Length - relativePathStart);
}

С его помощью мы можем создать любой относительный путь, мы хотим без струн копирования:

static void Main(string[] args)
{
var rootPath = @"C:\foo\bar";
foreach (var path in EnumeratePaths(rootPath, SearchOption.AllDirectories))
{
Console.WriteLine(path.RelativePath(rootPath.Length).Span.ToString());
}

Console.ReadKey();
}


Строки нарезки может быть в некоторых случаях очень дорогостоящая операция, поэтому хорошо знаю, что есть новая функция, которая может существенно повысить его производительность, когда это необходимо.


В настоящее время, System.String.Substring главная .Чистый API для создания фрагментов строки, но API является неэффективной, поскольку она создает новую строку, чтобы представить срез и копирует символы из исходной строки в новую строку фрагмента. Из-за этой неэффективности, высокопроизводительных серверов уклоняться от использования этого API, где они могут (в их внутренностях), и оплатить стоимость в общедоступный API-интерфейсы. [2]

Есть и другие случаи использования, которые могут быть оптимизированы с новыми типами, как парсинг и форматирование.


Что дальше?


Типы, методы оптимизации выполнения, а также другие элементы, обсуждаемые здесь, будет включен в .Чистая ядро 2.1. [1]

После этого, они, как ожидается, внести свой путь в .Чистые рамки. Будем надеяться, что они будут...


Источники


  1. На C# - Все о span: изучение нового .Чистая основа

  2. Span<T> - dotnet ограничителя/corefxlab

1
ответ дан 30 марта 2018 в 12:03 Источник Поделиться