Как удалить недопустимые символы из пути и имен файлов?

Мне нужен надежный и простой способ удалить недопустимые пути и символы файла из простой строки. Я использовал приведенный ниже код, но, похоже, он ничего не делает, что мне не хватает?

using System;
using System.IO;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string illegal = "\"M<>\"\a/ry/ h**ad:>> a/:*?\"<>| li*tt|le|| la\"mb.?";

            illegal = illegal.Trim(Path.GetInvalidFileNameChars());
            illegal = illegal.Trim(Path.GetInvalidPathChars());

            Console.WriteLine(illegal);
            Console.ReadLine();
        }
    }
}

Обрезка удаляет символы в начале и конце строк. Однако вы, вероятно, должны спросить, почему данные недействительны, и вместо того, чтобы пытаться очистить / исправить данные, отклоните данные.

user7116 28.09.2008 19:54

Имена в стиле Unix недействительны в Windows, и я не хочу иметь дело с короткими именами 8.3.

Gary Willoughby 16.10.2009 16:04

GetInvalidFileNameChars() удалит такие вещи, как: \ etc из путей к папкам.

CAD bloke 20.05.2016 06:18

Path.GetInvalidPathChars(), похоже, не удаляет * или ?

CAD bloke 20.05.2016 06:24

Я проверил пять ответов на этот вопрос (временной цикл 100 000), и следующий метод является самым быстрым. Регулярное выражение заняло 2-е место и было на 25% медленнее: общедоступная строка GetSafeFilename (строка имени файла) {return string.Join ("_", filename.Split (Path.GetInvalidFileNameChars ())); }

Brain2000 15.07.2016 18:20

Я добавил новую быструю альтернативу и несколько тестов в этот ответ

c-chavez 29.09.2020 17:07
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
498
6
476 641
30
Перейти к ответу Данный вопрос помечен как решенный

Ответы 30

Для начала, Обрезка удаляет символы только из начала или конца строки. Во-вторых, вы должны оценить, действительно ли вы хотите удалить оскорбительные символы или быстро вывести из строя и сообщить пользователю, что их имя файла недействительно. Мой выбор - последнее, но мой ответ должен, по крайней мере, показать вам, как поступать правильно И неправильно:

Вопрос StackOverflow, показывающий, как проверить, является ли данная строка допустимым именем файла. Обратите внимание, что вы можете использовать регулярное выражение из этого вопроса для удаления символов с заменой регулярного выражения (если вам действительно нужно это сделать).

Особенно согласен со вторым советом.

OregonGhost 28.09.2008 19:59

Обычно я согласен со вторым, но у меня есть программа, которая генерирует имя файла и в некоторых ситуациях может содержать недопустимые символы. Поскольку моя программа генерирует недопустимые имена файлов, я считаю целесообразным удалить / заменить эти символы. (Просто укажите допустимый вариант использования)

JDB still remembers Monica 09.05.2013 19:48
Ответ принят как подходящий

Попробуйте вместо этого что-нибудь подобное;

string illegal = "\"M\"\a/ry/ h**ad:>> a/:*?\"| li*tt|le|| la\"mb.?";
string invalid = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());

foreach (char c in invalid)
{
    illegal = illegal.Replace(c.ToString(), ""); 
}

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

Обновлено: или потенциально «лучшее» решение с использованием Regex.

string illegal = "\"M\"\a/ry/ h**ad:>> a/:*?\"| li*tt|le|| la\"mb.?";
string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
illegal = r.Replace(illegal, "");

Тем не менее, возникает вопрос, зачем вы вообще это делаете.

Я не знаю, стоит ли мне +1 к вашему ответу из-за такого неэффективного решения, которое оттолкнет пользователя от этого пути, или мне следует +1 к вашему ответу за то, что оно фактически отвечает на его вопрос! :)

user7116 28.09.2008 20:05

@Michael Stum: они «компилируются» и должны быть своего рода конечным автоматом, но было бы наивно предполагать, что они гарантированно будут более эффективными под капотом, чем цикл.

user7116 28.09.2008 20:10

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

Matthew Scharley 28.09.2008 20:15

Я бы придерживался решения без регулярных выражений: в большинстве случаев оно, вероятно, будет более эффективным. Если вы используете решение с регулярным выражением, измените string.Format () просто на "[" + "...". Если вы собираетесь рассматривать illegal как имя файла без пути после замены специальных символов, вам понадобится только Path.InvalidFileNameChars ().

Rory 19.08.2010 21:58

Нет необходимости складывать два списка вместе. Список символов недопустимого имени файла содержит список символов недопустимого пути и еще несколько. Вот списки обоих списков, приведенных к int: 34,60,62,124,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16, 17,18, ‌ 19,20,21,22,23,24,25‌, 26,27,28,29,30,31,5‌ 8,42,63,92,47 34,60,62,124, 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18, ‌ 19,20,21,22,23, 24,25‌, 26,27,28,29,30,31

Sarel Botha 11.04.2011 22:12

@sjbotha, это может быть правдой для Windows и реализации .NET от Microsoft. Я не хочу делать то же самое предположение, скажем, для монофонического запуска Linux.

Matthew Scharley 17.04.2011 05:24

По поводу первого решения. Разве StringBuilder не должен быть более эффективным, чем присвоение строк?

epignosisx 30.12.2011 19:53

Если строка содержит китайские символы, решение может не сработать.

PerlDev 02.01.2012 09:13

@PerlDev: Вы действительно это проверяли? characters должен быть многобайтовым (sizeof(char) == 2), так что это не должно быть проблемой. Решение с регулярным выражением также должно быть в порядке.

Matthew Scharley 17.01.2012 12:47

В чем проблема с очисткой, Bob Tables?

cregox 09.11.2013 01:02

Поправьте меня, если я ошибаюсь, но звонить как Path.GetInvalidFileNameChars(), так и Path.GetInvalidPathChars() излишне. Одного Path.GetInvalidFileNameChars() должно быть достаточно.

Joey Adams 13.11.2013 22:34

@JoeyAdams: см. Мой ответ Сарелу Боте. Короче говоря, в Windows одно надмножество другого. Лично я не желаю делать одинаковые ставки на кросс-платформу, а C# и .NET в целом все время получают все более широкую аудиторию через Mono.

Matthew Scharley 15.11.2013 12:18

Как бы то ни было, @MatthewScharley, реализация GetInvalidPathChars () Mono возвращает только 0x00, а GetInvalidFileNameChars () возвращает только 0x00 и '/' при работе на платформах, отличных от Windows. В Windows списки недопустимых символов намного длиннее, а GetInvalidPathChars () полностью дублируется внутри GetInvalidFileNameChars (). Это не изменится в обозримом будущем, поэтому все, что вы на самом деле делаете, - это удваиваете время, необходимое для выполнения этой функции, потому что вы беспокоитесь, что определение допустимого пути когда-нибудь изменится. Чего не будет.

Warren Rumak 27.01.2014 23:09

И давайте проясним это: эта часть исходного кода Mono не менялась ВОСЕМЬ ЛЕТ, за исключением незначительного улучшения производительности в 2007 году.

Warren Rumak 27.01.2014 23:11

@Warren: Не стесняйтесь вывести результирующую строку, если вы действительно обеспокоены, но давайте будем совершенно честны: разница между 20 и 40 итерациями по сравнению со строкой длиной вашего среднего пути (скажем, 100 символов, чтобы быть щедрым) сделает ровно нет разница во времени выполнения вашей функции. Для всех целей практичный об этом не нужно беспокоиться. С другой стороны, эти две функции служат разным целям, и (по крайней мере, на мой взгляд) было бы вполне разумно, чтобы одна функция не возвращала надмножество другой для некоторой данной файловой системы.

Matthew Scharley 29.01.2014 09:40

Как может удвоение работы (будь то дедупликация массива или двукратное прохождение через почти одни и те же значения массива) не требует «абсолютно никакой разницы»? Вы не хуже меня знаете, что это неправильно, поэтому -не--говори- -это-. Мы пытаемся быть образовательным ресурсом в Stackoverflow, а не местом для риторических расцветов, вызванных тем, что вы ошибаетесь. Давайте проясним: то, что вы здесь рекомендуете, фактически то же самое, что и старая утка Daily WTF о предоставлении вашего собственного определения ИСТИНА и ЛОЖЬ, потому что вы не можете доверять компилятору или библиотекам, чтобы всегда делать это правильно.

Warren Rumak 29.01.2014 20:43

GetInvalidFileNameChars () всегда - ВСЕГДА, вы меня слышите - будет включать все в GetInvalidPathChars (), потому что файл не может содержать недопустимый символ в имени пути. Ни одна файловая система сегодня не позволяет этого, ни одна файловая система никогда не позволит. В любом случае, собственная документация Microsoft по этим функциям очень четко заявляет, что не следует ожидать, что список символов будет гарантированно точным, потому что файловые системы в любом случае могут поддерживать что-то другое.

Warren Rumak 29.01.2014 20:52

Я бы, вероятно, встал на сторону Мэтью и просто сказал, что это предположение - мать всех неудач. Вы говорите об оптимизации кода, который, вероятно, не требует оптимизации из-за потенциальной правильности. Я бы принял правильность над преждевременной оптимизацией в любой день

Charleh 15.03.2014 21:50

@Charleh, это обсуждение настолько ненужно ... код всегда должен быть оптимизирован, и нет риска, что это будет неправильным. Имя файла также является частью пути. Поэтому нелогично, что GetInvalidPathChars() может содержать символы, которых нет в GetInvalidFileNameChars(). Вы не принимаете правильность перед «преждевременной» оптимизацией. Вы просто используете плохой код.

Stefan Fabian 09.08.2014 15:54

Лично я бы предпочел такой способ: var invalid = Path.GetInvalidFileNameChars().Union(Path.GetInvalidPathChar‌​s()); foreach(char c in invalid) illegal = illegal.Replace(c.ToString(), "_");

Tim Schmelter 09.09.2015 15:20

Я не уверен, почему вы, ребята, так любопытны, почему он хочет это использовать. Существуют различные допустимые сценарии, в которых это было бы полезно. Наше приложение, например, выводит файлы xlsx по электронной почте в виде отчетов, и если мы не проверим его при входе, вы не узнаете до запланированного времени создания отчета, что имя файла недействительно. У нас были проблемы, когда в прошлом кто-то случайно вводил «меньше чем» в имени файла и сохранял его. Кроме того, некоторые из наших клиентов запускают Linux, а некоторые - окна, поэтому разрешенные файлы не совпадают.

John Lord 30.11.2018 20:51

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

Byron 03.06.2020 20:00

Я думаю, что гораздо проще проверить, используя регулярное выражение и указав, какие символы разрешены, вместо того, чтобы пытаться проверить все плохие символы. Смотрите эти ссылки: http://www.c-sharpcorner.com/UploadFile/prasad_1/RegExpPSD12062005021717AM/RegExpPSD.aspxhttp://www.windowsdevcenter.com/pub/a/oreilly/windows/news/csharp_0101.html

Также поищите "редактор регулярных выражений", они очень помогают. Есть такие, которые даже выводят для вас код на C#.

Учитывая, что .net - это фреймворк, который предназначен для запуска программ на нескольких платформах (например, Linux / Unix, а также Windows), я считаю, что Path.GetInvalidFileNameChars () лучше всего, поскольку он будет содержать информацию о том, что есть, а что нет. t действителен для файловой системы, в которой выполняется ваша программа. Даже если ваша программа никогда не будет работать в Linux (возможно, она полна кода WPF), всегда есть шанс, что в будущем появится новая файловая система Windows с другими допустимыми / недопустимыми символами. Прокатывая собственное с помощью регулярного выражения, вы изобретаете колесо и переносите проблему платформы в ваш собственный код.

Daniel Scott 04.10.2018 02:54

Я согласен с вашим советом относительно онлайн-редакторов / тестеров регулярных выражений. Я считаю их бесценными (поскольку регулярные выражения - штука сложная и полна тонкости, которая может легко сбить вас с толку, давая вам регулярное выражение, которое ведет себя совершенно неожиданным образом с крайними случаями). Мне больше всего нравится regex101.com (мне нравится, как он разбивает регулярное выражение и ясно показывает, что он ожидает найти). Мне также очень нравится debuggex.com, поскольку он имеет компактное визуальное представление групп совпадений, классов символов и прочего.

Daniel Scott 04.10.2018 03:05

Для этого я использую регулярные выражения. Во-первых, я динамически создаю регулярное выражение.

string regex = string.Format(
                   "[{0}]",
                   Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Затем я просто вызываю removeInvalidChars.Replace, чтобы выполнить поиск и замену. Очевидно, это можно расширить, чтобы охватить и символы пути.

Странно, у меня это сработало. Я перепроверяю это, когда у меня будет возможность. Не могли бы вы быть более конкретными и объяснить, что именно вам не подходит?

Jeff Yates 08.02.2010 18:56

Это не сработает (по крайней мере, правильно), потому что вы неправильно экранируете символы пути, а некоторые из них имеют особое значение. Обратитесь к моему ответу, чтобы узнать, как это сделать.

Matthew Scharley 09.04.2010 01:39

@Jeff: Ваша версия все равно лучше, чем у Мэтью, если вы немного ее измените. Обратитесь к моему ответу о том, как.

Jan 13.02.2012 12:28

Я бы также добавил несколько других шаблонов недопустимых имен файлов, которые можно найти на MSDN, и расширил бы ваше решение до следующего регулярного выражения: new Regex(String.Format("^(CON|PRN|AUX|NUL|CLOCK$|COM[1-9]|LPT[‌​1-9])(?=\..|$)|(^(\.‌​+|\s+)$)|((\.+|\s+)$‌​)|([{0}])", Regex.Escape(new String(Path.GetInvalidFileNameChars()))), RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.CultureInvariant);

yar_shukan 10.09.2014 18:46

Небольшое улучшение синтаксиса для комментария @yar_shukan: добавьте @ перед строковым выражением, если вы столкнулись с ошибкой «Нераспознанная escape-последовательность», то есть String.Format(@"^CON| ... )"

hotenov 26.09.2020 12:29

Выбросить исключение.

if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
            {
                throw new ArgumentException();
            }

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

PHenry 20.11.2020 04:30

Вот фрагмент кода, который должен помочь для .NET 3 и выше.

using System.IO;
using System.Text.RegularExpressions;

public static class PathValidation
{
    private static string pathValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex pathValidator = new Regex(pathValidatorExpression, RegexOptions.Compiled);

    private static string fileNameValidatorExpression = "^[^" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]+$";
    private static Regex fileNameValidator = new Regex(fileNameValidatorExpression, RegexOptions.Compiled);

    private static string pathCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidPathChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex pathCleaner = new Regex(pathCleanerExpression, RegexOptions.Compiled);

    private static string fileNameCleanerExpression = "[" + string.Join("", Array.ConvertAll(Path.GetInvalidFileNameChars(), x => Regex.Escape(x.ToString()))) + "]";
    private static Regex fileNameCleaner = new Regex(fileNameCleanerExpression, RegexOptions.Compiled);

    public static bool ValidatePath(string path)
    {
        return pathValidator.IsMatch(path);
    }

    public static bool ValidateFileName(string fileName)
    {
        return fileNameValidator.IsMatch(fileName);
    }

    public static string CleanPath(string path)
    {
        return pathCleaner.Replace(path, "");
    }

    public static string CleanFileName(string fileName)
    {
        return fileNameCleaner.Replace(fileName, "");
    }
}

Вы можете удалить недопустимые символы с помощью Linq следующим образом:

var invalidChars = Path.GetInvalidFileNameChars();

var invalidCharsRemoved = stringWithInvalidChars
.Where(x => !invalidChars.Contains(x))
.ToArray();

РЕДАКТИРОВАТЬ
Вот как это выглядит с необходимыми изменениями, упомянутыми в комментариях:

var invalidChars = Path.GetInvalidFileNameChars();

string invalidCharsRemoved = new string(stringWithInvalidChars
  .Where(x => !invalidChars.Contains(x))
  .ToArray());

Мне нравится этот способ: вы сохраняете только разрешенные символы в строке (которая представляет собой не что иное, как массив символов).

Dude Pascalou 04.07.2012 13:36

Я знаю, что это старый вопрос, но это отличный ответ. Однако я хотел добавить, что в C# вы не можете преобразовать char [] в строку ни неявно, ни явно (сумасшедший, я знаю), поэтому вам нужно перетащить его в конструктор строк.

JNYRanger 21.10.2014 22:52

Я не подтверждал это, но ожидаю, что Path.GetInvalidPathChars () будет надмножеством GetInvalidFileNameChars () и будет охватывать как имена файлов, так и пути, поэтому я бы, вероятно, использовал это вместо этого.

angularsen 10.01.2015 01:11

@anjdreas на самом деле Path.GetInvalidPathChars () кажется подмножеством Path.GetInvalidFileNameChars (), а не наоборот. Например, Path.GetInvalidPathChars () не вернет "?".

Rafael Costa 30.12.2015 13:21

Это хороший ответ. Я использую как список имен файлов, так и список путей к файлам: ____________________________ string cleanData = new string (data.Where (x =>! Path.GetInvalidFileNameChars (). Contains (x) &&! Path.GetInvalidPathChars (). Contains (x)). ToArray ());

goamn 30.11.2017 08:28

Вы также можете сделать var invalidChars = new HashSet <char> (Path.GetInvalidFileNameChars ()) и сделать его O (n) вместо O (n ^ 2). Почему бы и нет.

Cesar 24.09.2020 17:34

Я абсолютно предпочитаю идею Джеффа Йейтса. Он будет работать отлично, если вы его немного измените:

string regex = String.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars())));
Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);

Улучшение заключается в том, чтобы избежать автоматически сгенерированного регулярного выражения.

Я использую Linq для очистки имен файлов. Вы можете легко расширить это, чтобы также проверять действительные пути.

private static string CleanFileName(string fileName)
{
    return Path.GetInvalidFileNameChars().Aggregate(fileName, (current, c) => current.Replace(c.ToString(), string.Empty));
}

Обновлять

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

https://dotnetfiddle.net/nw1SWY

У меня это не сработало. Метод не возвращает чистую строку. Он возвращает переданное имя файла как есть.

Karan 17.07.2013 10:29

Что сказал @Karan, это не работает, возвращается исходная строка.

Jon 20.03.2014 19:26

На самом деле вы можете сделать это с помощью Linq вот так: var invalid = new HashSet<char>(Path.GetInvalidPathChars()); return new string(originalString.Where(s => !invalid.Contains(s)).ToArray()). Производительность, вероятно, невелика, но это, вероятно, не имеет значения.

Casey 09.07.2015 17:12

@Karan или Jon Какой вклад вы отправляете этой функции? См. Мою правку для проверки этого метода.

Michael Minton 24.09.2015 05:45

Это просто - парни передавали строки с действительными символами. Проголосовали за крутое решение для агрегатов.

Nickmaovich 20.01.2016 16:10

Очень хорошее решение, но очищает только имя файла (как указано), но не фактический путь, поскольку он рассматривает «\» как недопустимый символ и если у вас есть что-то вроде «\\ MyServer \ e $ \ demo \ Output \ Test \ 1111_joe_soap. pdf ", он возвращает" MyServere $ demoOutputTest1111_joe_soap.pdf "

Thierry 16.03.2017 13:45

Плюс 1 за творческое использование агрегата.

Teejay 17.07.2020 16:49

Все это отличные решения, но все они полагаются на Path.GetInvalidFileNameChars, который может быть не таким надежным, как вы думаете. Обратите внимание на следующее замечание в документации MSDN по Path.GetInvalidFileNameChars:

The array returned from this method is not guaranteed to contain the complete set of characters that are invalid in file and directory names. The full set of invalid characters can vary by file system. For example, on Windows-based desktop platforms, invalid path characters might include ASCII/Unicode characters 1 through 31, as well as quote ("), less than (<), greater than (>), pipe (|), backspace (\b), null (\0) and tab (\t).

Не лучше с методом Path.GetInvalidPathChars. Он содержит точно такое же замечание.

Тогда в чем смысл Path.GetInvalidFileNameChars? Я ожидал, что он вернет именно недопустимые символы для текущей системы, полагаясь на .NET, чтобы знать, в какой файловой системе я работаю, и представляя мне подходящие недопустимые символы. Если это не так и он просто возвращает жестко закодированные символы, которые в первую очередь ненадежны, этот метод следует удалить, поскольку он имеет нулевое значение.

Jan 18.01.2014 22:08

Я знаю, что это старый комментарий, но @Jan вы могли бы захотеть написать в другой файловой системе, возможно, поэтому есть предупреждение.

fantastik78 07.07.2015 16:59

@ fantastik78 - хороший аргумент, но в этом случае я хотел бы иметь дополнительный аргумент перечисления, чтобы указать мою удаленную FS. Если это слишком много усилий по техническому обслуживанию (что наиболее вероятно), весь этот метод по-прежнему является плохой идеей, потому что он создает неправильное впечатление о безопасности.

Jan 03.09.2015 13:33

@Jan Я полностью с тобой согласен, я просто спорил из-за предупреждения.

fantastik78 03.09.2015 17:39

Интересно, что это своего рода «черный список» недопустимых символов. Не лучше ли занести в белый список только известные действительные символы ?! Напоминает мне глупую идею "сканера вирусов" вместо добавления разрешенных приложений в белый список ....

Bernhard 10.07.2018 11:48

Обратите внимание на то, что имена файлов указаны в предупреждении. На самом деле он говорит вам, что он не проверяет сами имена файлов, а только недопустимые символы. У вас все еще может быть неправильное имя файла, которое является зарезервированным словом. Также как бы вы занесли приложение в белый список? Я бы просто сделал так, чтобы у моего вируса было ваше имя файла и подпись.

John Lord 29.11.2018 20:04

Большинство вышеперечисленных решений объединяют недопустимые символы как для пути, так и для имени файла, что неверно (даже если оба вызова в настоящее время возвращают один и тот же набор символов). Сначала я бы разделил путь + имя файла на путь и имя файла, затем применил соответствующий набор к любому из них, а затем снова объединил их.

wvd_vegt

+1: Совершенно верно. Сегодня, работая в .NET 4.0, решение с регулярным выражением из верхнего ответа уничтожило все обратные косые черты на полном пути. Поэтому я сделал регулярное выражение для пути к каталогу и регулярное выражение только для имени файла, очищенное отдельно и рекомбинированное

dario_ramos 23.05.2013 01:03

Это может быть правдой, но это не отвечает на вопрос. Я не уверен, что расплывчатое «Я бы сделал это так» ужасно полезно по сравнению с некоторыми из уже имеющихся здесь полных решений (см., Например, ответ Лилли ниже)

Ian Grainger 12.05.2016 14:20

Лучший способ удалить недопустимый символ из пользовательского ввода - заменить недопустимый символ с помощью класса Regex, создать метод в коде позади или также его проверить на стороне клиента с помощью элемента управления RegularExpression.

public string RemoveSpecialCharacters(string str)
{
    return Regex.Replace(str, "[^a-zA-Z0-9_]+", "_", RegexOptions.Compiled);
}

ИЛИ ЖЕ

<asp:RegularExpressionValidator ID = "regxFolderName" 
                                runat = "server" 
                                ErrorMessage = "Enter folder name with  a-z A-Z0-9_" 
                                ControlToValidate = "txtFolderName" 
                                Display = "Dynamic" 
                                ValidationExpression = "^[a-zA-Z0-9_]*$" 
                                ForeColor = "Red">

IMHO это решение намного лучше, чем другие. Вместо поиска всех недопустимых символов просто определите, какие из них действительны.

igorushi 29.09.2015 10:55

public static bool IsValidFilename(string testName)
{
    return !new Regex("[" + Regex.Escape(new String(System.IO.Path.GetInvalidFileNameChars())) + "]").IsMatch(testName);
}

Я написал этого монстра для удовольствия, он позволяет вам путешествовать туда и обратно:

public static class FileUtility
{
    private const char PrefixChar = '%';
    private static readonly int MaxLength;
    private static readonly Dictionary<char,char[]> Illegals;
    static FileUtility()
    {
        List<char> illegal = new List<char> { PrefixChar };
        illegal.AddRange(Path.GetInvalidFileNameChars());
        MaxLength = illegal.Select(x => ((int)x).ToString().Length).Max();
        Illegals = illegal.ToDictionary(x => x, x => ((int)x).ToString("D" + MaxLength).ToCharArray());
    }

    public static string FilenameEncode(string s)
    {
        var builder = new StringBuilder();
        char[] replacement;
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (Illegals.TryGetValue(c,out replacement))
                {
                    builder.Append(PrefixChar);
                    builder.Append(replacement);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static string FilenameDecode(string s)
    {
        var builder = new StringBuilder();
        char[] buffer = new char[MaxLength];
        using (var reader = new StringReader(s))
        {
            while (true)
            {
                int read = reader.Read();
                if (read == -1)
                    break;
                char c = (char)read;
                if (c == PrefixChar)
                {
                    reader.Read(buffer, 0, MaxLength);
                    var encoded =(char) ParseCharArray(buffer);
                    builder.Append(encoded);
                }
                else
                {
                    builder.Append(c);
                }
            }
        }
        return builder.ToString();
    }

    public static int ParseCharArray(char[] buffer)
    {
        int result = 0;
        foreach (char t in buffer)
        {
            int digit = t - '0';
            if ((digit < 0) || (digit > 9))
            {
                throw new ArgumentException("Input string was not in the correct format");
            }
            result *= 10;
            result += digit;
        }
        return result;
    }
}

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

Kim 29.01.2014 20:25

Или вы можете просто сделать

[YOUR STRING].Replace('\', ' ').Replace('/', ' ').Replace('"', ' ').Replace('*', ' ').Replace(':', ' ').Replace('?', ' ').Replace('<', ' ').Replace('>', ' ').Replace('|', ' ').Trim();

Для имен файлов:

var cleanFileName = string.Join("", fileName.Split(Path.GetInvalidFileNameChars()));

Для полных путей:

var cleanPath = string.Join("", path.Split(Path.GetInvalidPathChars()));

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

Исходный вопрос, заданный для «удаления недопустимых символов»:

public string RemoveInvalidChars(string filename)
{
    return string.Concat(filename.Split(Path.GetInvalidFileNameChars()));
}

Вместо этого вы можете заменить их:

public string ReplaceInvalidChars(string filename)
{
    return string.Join("_", filename.Split(Path.GetInvalidFileNameChars()));    
}

Этот ответ был в другой ветке Цереры, мне очень нравится аккуратно и просто.

Чтобы точно ответить на вопрос OP, вам нужно будет использовать "" вместо "_", но ваш ответ, вероятно, применим к большему количеству из нас на практике. Я думаю, чаще всего делается замена недопустимых символов на какие-то допустимые.

B H 08.01.2016 23:27

Я протестировал пять методов из этого вопроса (временной цикл 100000), и этот метод является самым быстрым. Регулярное выражение заняло 2-е место и было на 25% медленнее, чем этот метод.

Brain2000 15.07.2016 18:19

Чтобы обратиться к комментарию @BH, можно просто использовать string.Concat (name.Split (Path.GetInvalidFileNameChars ()))

Michael Sutton 07.06.2017 17:06

Это будет вам нужно, и избежать столкновений

 static string SanitiseFilename(string key)
    {
        var invalidChars = Path.GetInvalidFileNameChars();
        var sb = new StringBuilder();
        foreach (var c in key)
        {
            var invalidCharIndex = -1;
            for (var i = 0; i < invalidChars.Length; i++)
            {
                if (c == invalidChars[i])
                {
                    invalidCharIndex = i;
                }
            }
            if (invalidCharIndex > -1)
            {
                sb.Append("_").Append(invalidCharIndex);
                continue;
            }

            if (c == '_')
            {
                sb.Append("__");
                continue;
            }

            sb.Append(c);
        }
        return sb.ToString();

    }

Если вы удалите или замените одним символом недопустимые символы, могут возникнуть коллизии:

<abc -> abc
>abc -> abc

Вот простой способ избежать этого:

public static string ReplaceInvalidFileNameChars(string s)
{
    char[] invalidFileNameChars = System.IO.Path.GetInvalidFileNameChars();
    foreach (char c in invalidFileNameChars)
        s = s.Replace(c.ToString(), "[" + Array.IndexOf(invalidFileNameChars, c) + "]");
    return s;
}

Результат:

 <abc -> [1]abc
 >abc -> [2]abc

Это похоже на O (n) и не тратит слишком много памяти на строки:

    private static readonly HashSet<char> invalidFileNameChars = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string RemoveInvalidFileNameChars(string name)
    {
        if (!name.Any(c => invalidFileNameChars.Contains(c))) {
            return name;
        }

        return new string(name.Where(c => !invalidFileNameChars.Contains(c)).ToArray());
    }

Я не думаю, что это O (n), когда вы используете функцию «Any».

II ARROWS 30.08.2016 13:42

@IIARROWS а что это на ваш взгляд?

Alexey F 30.08.2016 15:32

Я не знаю, я просто не чувствовал этого, когда писал свой комментарий ... теперь, когда я попытался его вычислить, похоже, вы правы.

II ARROWS 30.08.2016 22:48

Я выбрал этот из-за вашего соображения производительности. Спасибо.

Berend Engelbrecht 16.10.2019 12:54

Думаю, на вопрос уже не дан полный ответ ... Ответы описывают только чистое имя файла ИЛИ путь ... не то и другое. Вот мое решение:

private static string CleanPath(string path)
{
    string regexSearch = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
    Regex r = new Regex(string.Format("[{0}]", Regex.Escape(regexSearch)));
    List<string> split = path.Split('\').ToList();
    string returnValue = split.Aggregate(string.Empty, (current, s) => current + (r.Replace(s, "") + @"\"));
    returnValue = returnValue.TrimEnd('\');
    return returnValue;
}

Просматривая здесь ответы, все они **, похоже, связаны с использованием массива char недопустимых символов имени файла.

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

В прошлом я был очень удивлен (шокирован) тем, насколько быстро хэш-набор (или словарь) превосходит итерацию по списку. Для строк это смехотворно малое число (около 5-7 элементов из памяти). С большинством других простых данных (ссылки на объекты, числа и т. д.) Магический переход составляет около 20 элементов.

В «списке» Path.InvalidFileNameChars 40 недопустимых символов. Сделал поиск сегодня, и здесь, в StackOverflow, есть неплохой тест, который показывает, что хеш-набор займет чуть более половины времени, которое требуется для массива / списка для 40 элементов: https://stackoverflow.com/a/10762995/949129

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

Дополнительный бонусный метод "IsValidLocalPath" тоже :)

(** те, которые не используют регулярные выражения)

public static class PathExtensions
{
    private static HashSet<char> _invalidFilenameChars;
    private static HashSet<char> InvalidFilenameChars
    {
        get { return _invalidFilenameChars ?? (_invalidFilenameChars = new HashSet<char>(Path.GetInvalidFileNameChars())); }
    }


    /// <summary>Replaces characters in <c>text</c> that are not allowed in file names with the 
    /// specified replacement character.</summary>
    /// <param name = "text">Text to make into a valid filename. The same string is returned if 
    /// it is valid already.</param>
    /// <param name = "replacement">Replacement character, or NULL to remove bad characters.</param>
    /// <param name = "fancyReplacements">TRUE to replace quotes and slashes with the non-ASCII characters ” and ⁄.</param>
    /// <returns>A string that can be used as a filename. If the output string would otherwise be empty, "_" is returned.</returns>
    public static string ToValidFilename(this string text, char? replacement = '_', bool fancyReplacements = false)
    {
        StringBuilder sb = new StringBuilder(text.Length);
        HashSet<char> invalids = InvalidFilenameChars;
        bool changed = false;

        for (int i = 0; i < text.Length; i++)
        {
            char c = text[i];
            if (invalids.Contains(c))
            {
                changed = true;
                char repl = replacement ?? '\0';
                if (fancyReplacements)
                {
                    if (c == '"') repl = '”'; // U+201D right double quotation mark
                    else if (c == '\'') repl = '’'; // U+2019 right single quotation mark
                    else if (c == '/') repl = '⁄'; // U+2044 fraction slash
                }
                if (repl != '\0')
                    sb.Append(repl);
            }
            else
                sb.Append(c);
        }

        if (sb.Length == 0)
            return "_";

        return changed ? sb.ToString() : text;
    }


    /// <summary>
    /// Returns TRUE if the specified path is a valid, local filesystem path.
    /// </summary>
    /// <param name = "pathString"></param>
    /// <returns></returns>
    public static bool IsValidLocalPath(this string pathString)
    {
        // From solution at https://stackoverflow.com/a/11636052/949129
        Uri pathUri;
        Boolean isValidUri = Uri.TryCreate(pathString, UriKind.Absolute, out pathUri);
        return isValidUri && pathUri != null && pathUri.IsLoopback;
    }
}

public static class StringExtensions
      {
        public static string RemoveUnnecessary(this string source)
        {
            string result = string.Empty;
            string regex = new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars());
            Regex reg = new Regex(string.Format("[{0}]", Regex.Escape(regex)));
            result = reg.Replace(source, "");
            return result;
        }
    }

Вы можете четко использовать метод.

Я создал метод расширения, который сочетает в себе несколько предложений:

  1. Хранение недопустимых символов в хеш-наборе
  2. Фильтрация символов ниже ascii 127. Поскольку Path.GetInvalidFileNameChars не включает все недопустимые символы, возможные с кодами ascii от 0 до 255. Глянь сюда и MSDN
  3. Возможность определить заменяющий символ

Источник:

public static class FileNameCorrector
{
    private static HashSet<char> invalid = new HashSet<char>(Path.GetInvalidFileNameChars());

    public static string ToValidFileName(this string name, char replacement = '\0')
    {
        var builder = new StringBuilder();
        foreach (var cur in name)
        {
            if (cur > 31 && cur < 128 && !invalid.Contains(cur))
            {
                builder.Append(cur);
            }
            else if (replacement != '\0')
            {
                builder.Append(replacement);
            }
        }

        return builder.ToString();
    }
}

Имя файла не может содержать символы из символов Path.GetInvalidPathChars(), + и #, а также другие конкретные имена. Мы объединили все проверки в один класс:

public static class FileNameExtensions
{
    private static readonly Lazy<string[]> InvalidFileNameChars =
        new Lazy<string[]>(() => Path.GetInvalidPathChars()
            .Union(Path.GetInvalidFileNameChars()
            .Union(new[] { '+', '#' })).Select(c => c.ToString(CultureInfo.InvariantCulture)).ToArray());


    private static readonly HashSet<string> ProhibitedNames = new HashSet<string>
    {
        @"aux",
        @"con",
        @"clock$",
        @"nul",
        @"prn",

        @"com1",
        @"com2",
        @"com3",
        @"com4",
        @"com5",
        @"com6",
        @"com7",
        @"com8",
        @"com9",

        @"lpt1",
        @"lpt2",
        @"lpt3",
        @"lpt4",
        @"lpt5",
        @"lpt6",
        @"lpt7",
        @"lpt8",
        @"lpt9"
    };

    public static bool IsValidFileName(string fileName)
    {
        return !string.IsNullOrWhiteSpace(fileName)
            && fileName.All(o => !IsInvalidFileNameChar(o))
            && !IsProhibitedName(fileName);
    }

    public static bool IsProhibitedName(string fileName)
    {
        return ProhibitedNames.Contains(fileName.ToLower(CultureInfo.InvariantCulture));
    }

    private static string ReplaceInvalidFileNameSymbols([CanBeNull] this string value, string replacementValue)
    {
        if (value == null)
        {
            return null;
        }

        return InvalidFileNameChars.Value.Aggregate(new StringBuilder(value),
            (sb, currentChar) => sb.Replace(currentChar, replacementValue)).ToString();
    }

    public static bool IsInvalidFileNameChar(char value)
    {
        return InvalidFileNameChars.Value.Contains(value.ToString(CultureInfo.InvariantCulture));
    }

    public static string GetValidFileName([NotNull] this string value)
    {
        return GetValidFileName(value, @"_");
    }

    public static string GetValidFileName([NotNull] this string value, string replacementValue)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new ArgumentException(@"value should be non empty", nameof(value));
        }

        if (IsProhibitedName(value))
        {
            return (string.IsNullOrWhiteSpace(replacementValue) ? @"_" : replacementValue) + value; 
        }

        return ReplaceInvalidFileNameSymbols(value, replacementValue);
    }

    public static string GetFileNameError(string fileName)
    {
        if (string.IsNullOrWhiteSpace(fileName))
        {
            return CommonResources.SelectReportNameError;
        }

        if (IsProhibitedName(fileName))
        {
            return CommonResources.FileNameIsProhibited;
        }

        var invalidChars = fileName.Where(IsInvalidFileNameChar).Distinct().ToArray();

        if (invalidChars.Length > 0)
        {
            return string.Format(CultureInfo.CurrentCulture,
                invalidChars.Length == 1 ? CommonResources.InvalidCharacter : CommonResources.InvalidCharacters,
                StringExtensions.JoinQuoted(@",", @"'", invalidChars.Select(c => c.ToString(CultureInfo.CurrentCulture))));
        }

        return string.Empty;
    }
}

Метод GetValidFileName заменяет все неверные данные на _.

Один лайнер для очистки строки от любых недопустимых символов для именования файлов Windows:

public static string CleanIllegalName(string p_testName) => new Regex(string.Format("[{0}]", Regex.Escape(new string(Path.GetInvalidFileNameChars()) + new string(Path.GetInvalidPathChars())))).Replace(p_testName, "");

Вот функция, которая заменяет все недопустимые символы в имени файла символом замены:

public static string ReplaceIllegalFileChars(string FileNameWithoutPath, char ReplacementChar)
{
  const string IllegalFileChars = "*?/\:<>|\"";
  StringBuilder sb = new StringBuilder(FileNameWithoutPath.Length);
  char c;

  for (int i = 0; i < FileNameWithoutPath.Length; i++)
  {
    c = FileNameWithoutPath[i];
    if (IllegalFileChars.IndexOf(c) >= 0)
    {
      c = ReplacementChar;
    }
    sb.Append(c);
  }
  return (sb.ToString());
}

Например, подчеркивание можно использовать как заменяющий символ:

NewFileName = ReplaceIllegalFileChars(FileName, '_');

В дополнение к предоставленному вами ответу, пожалуйста, рассмотрите возможность предоставления краткого объяснения того, почему и как это решает проблему.

jtate 14.05.2020 16:18

Вот мой небольшой вклад. Метод для замены в той же строке без создания новых строк или построителей строк. Это быстро, легко для понимания и является хорошей альтернативой всем упомянутым в этом посте.

private static HashSet<char> _invalidCharsHash;
private static HashSet<char> InvalidCharsHash
{
  get { return _invalidCharsHash ?? (_invalidCharsHash = new HashSet<char>(Path.GetInvalidFileNameChars())); }
}

private static string ReplaceInvalidChars(string fileName, string newValue)
{
  char newChar = newValue[0];

  char[] chars = fileName.ToCharArray();
  for (int i = 0; i < chars.Length; i++)
  {
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
      chars[i] = newChar;
  }

  return new string(chars);
}

Вы можете назвать это так:

string illegal = "\"M<>\"\a/ry/ h**ad:>> a/:*?\"<>| li*tt|le|| la\"mb.?";
string legal = ReplaceInvalidChars(illegal);

и возвращает:

_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

Стоит отметить, что этот метод всегда заменяет недопустимые символы заданным значением, но не удаляет их. Если вы хотите удалить недопустимые символы, эта альтернатива поможет:

private static string RemoveInvalidChars(string fileName, string newValue)
{
  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];
  bool remove = newChar == char.MinValue;

  char[] chars = fileName.ToCharArray();
  char[] newChars = new char[chars.Length];
  int i2 = 0;
  for (int i = 0; i < chars.Length; i++)
  {
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
    {
      if (!remove)
        newChars[i2++] = newChar;
    }
    else
      newChars[i2++] = c;

  }

  return new string(newChars, 0, i2);
}

ЭТАЛОН

Я выполнил запуск тестов по времени с большинством методов, описанных в этом посте, если вам нужна производительность. Некоторые из этих методов не заменяются заданным char, поскольку OP запрашивал очистку строки. Я добавил тесты, заменяющие заданный символ, а некоторые другие заменяю пустым символом, если ваш предполагаемый сценарий требует только удаления нежелательных символов. Код, используемый для этого теста, находится в конце, поэтому вы можете запускать свои собственные тесты.

Примечание. В этом посте предлагаются методы Test1 и Test2.

Первый забег

replacing with '_', 1000000 iterations

Полученные результаты:

============Test1===============
Elapsed=00:00:01.6665595
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test2===============
Elapsed=00:00:01.7526835
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test3===============
Elapsed=00:00:05.2306227
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test4===============
Elapsed=00:00:14.8203696
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test5===============
Elapsed=00:00:01.8273760
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test6===============
Elapsed=00:00:05.4249985
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test7===============
Elapsed=00:00:07.5653833
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test8===============
Elapsed=00:12:23.1410106
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test9===============
Elapsed=00:00:02.1016708
Result=_M ____a_ry_ h__ad___ a_________ li_tt_le__ la_mb._

============Test10===============
Elapsed=00:00:05.0987225
Result=M ary had a little lamb.

============Test11===============
Elapsed=00:00:06.8004289
Result=M ary had a little lamb.

Второй прогон

removing invalid chars, 1000000 iterations

Примечание: Test1 не удалит, а только заменит.

Полученные результаты:

============Test1===============
Elapsed=00:00:01.6945352
Result= M     a ry  h  ad    a          li tt le   la mb.

============Test2===============
Elapsed=00:00:01.4798049
Result=M ary had a little lamb.

============Test3===============
Elapsed=00:00:04.0415688
Result=M ary had a little lamb.

============Test4===============
Elapsed=00:00:14.3397960
Result=M ary had a little lamb.

============Test5===============
Elapsed=00:00:01.6782505
Result=M ary had a little lamb.

============Test6===============
Elapsed=00:00:04.9251707
Result=M ary had a little lamb.

============Test7===============
Elapsed=00:00:07.9562379
Result=M ary had a little lamb.

============Test8===============
Elapsed=00:12:16.2918943
Result=M ary had a little lamb.

============Test9===============
Elapsed=00:00:02.0770277
Result=M ary had a little lamb.

============Test10===============
Elapsed=00:00:05.2721232
Result=M ary had a little lamb.

============Test11===============
Elapsed=00:00:05.2802903
Result=M ary had a little lamb.

ЭТАЛОННЫЕ РЕЗУЛЬТАТЫ

Методы Test1, Test2 и Test5 - самые быстрые. Метод Test8 - самый медленный.

КОД

Вот полный код теста:

private static HashSet<char> _invalidCharsHash;
private static HashSet<char> InvalidCharsHash
{
  get { return _invalidCharsHash ?? (_invalidCharsHash = new HashSet<char>(Path.GetInvalidFileNameChars())); }
}

private static string _invalidCharsValue;
private static string InvalidCharsValue
{
  get { return _invalidCharsValue ?? (_invalidCharsValue = new string(Path.GetInvalidFileNameChars())); }
}

private static char[] _invalidChars;
private static char[] InvalidChars
{
  get { return _invalidChars ?? (_invalidChars = Path.GetInvalidFileNameChars()); }
}

static void Main(string[] args)
{
  string testPath = "\"M <>\"\a/ry/ h**ad:>> a/:*?\"<>| li*tt|le|| la\"mb.?";

  int max = 1000000;
  string newValue = "";

  TimeBenchmark(max, Test1, testPath, newValue);
  TimeBenchmark(max, Test2, testPath, newValue);
  TimeBenchmark(max, Test3, testPath, newValue);
  TimeBenchmark(max, Test4, testPath, newValue);
  TimeBenchmark(max, Test5, testPath, newValue);
  TimeBenchmark(max, Test6, testPath, newValue);
  TimeBenchmark(max, Test7, testPath, newValue);
  TimeBenchmark(max, Test8, testPath, newValue);
  TimeBenchmark(max, Test9, testPath, newValue);
  TimeBenchmark(max, Test10, testPath, newValue);
  TimeBenchmark(max, Test11, testPath, newValue);

  Console.Read();
}

private static void TimeBenchmark(int maxLoop, Func<string, string, string> func, string testString, string newValue)
{
  var sw = new Stopwatch();
  sw.Start();
  string result = string.Empty;

  for (int i = 0; i < maxLoop; i++)
    result = func?.Invoke(testString, newValue);

  sw.Stop();

  Console.WriteLine($"=========== = {func.Method.Name}============== = ");
  Console.WriteLine("Elapsed = {0}", sw.Elapsed);
  Console.WriteLine("Result = {0}", result);
  Console.WriteLine("");
}

private static string Test1(string fileName, string newValue)
{
  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];

  char[] chars = fileName.ToCharArray();
  for (int i = 0; i < chars.Length; i++)
  {
    if (InvalidCharsHash.Contains(chars[i]))
      chars[i] = newChar;
  }

  return new string(chars);
}

private static string Test2(string fileName, string newValue)
{
  char newChar = string.IsNullOrEmpty(newValue) ? char.MinValue : newValue[0];
  bool remove = newChar == char.MinValue;

  char[] chars = fileName.ToCharArray();
  char[] newChars = new char[chars.Length];
  int i2 = 0;
  for (int i = 0; i < chars.Length; i++)
  {
    char c = chars[i];
    if (InvalidCharsHash.Contains(c))
    {
      if (!remove)
        newChars[i2++] = newChar;
    }
    else
      newChars[i2++] = c;

  }

  return new string(newChars, 0, i2);
}

private static string Test3(string filename, string newValue)
{
  foreach (char c in InvalidCharsValue)
  {
    filename = filename.Replace(c.ToString(), newValue);
  }

  return filename;
}

private static string Test4(string filename, string newValue)
{
  Regex r = new Regex(string.Format("[{0}]", Regex.Escape(InvalidCharsValue)));
  filename = r.Replace(filename, newValue);
  return filename;
}

private static string Test5(string filename, string newValue)
{
  return string.Join(newValue, filename.Split(InvalidChars));
}

private static string Test6(string fileName, string newValue)
{
  return InvalidChars.Aggregate(fileName, (current, c) => current.Replace(c.ToString(), newValue));
}

private static string Test7(string fileName, string newValue)
{
  string regex = string.Format("[{0}]", Regex.Escape(InvalidCharsValue));
  return Regex.Replace(fileName, regex, newValue, RegexOptions.Compiled);
}

private static string Test8(string fileName, string newValue)
{
  string regex = string.Format("[{0}]", Regex.Escape(InvalidCharsValue));
  Regex removeInvalidChars = new Regex(regex, RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.CultureInvariant);
  return removeInvalidChars.Replace(fileName, newValue);
}

private static string Test9(string fileName, string newValue)
{
  StringBuilder sb = new StringBuilder(fileName.Length);
  bool changed = false;

  for (int i = 0; i < fileName.Length; i++)
  {
    char c = fileName[i];
    if (InvalidCharsHash.Contains(c))
    {
      changed = true;
      sb.Append(newValue);
    }
    else
      sb.Append(c);
  }

  if (sb.Length == 0)
    return newValue;

  return changed ? sb.ToString() : fileName;
}

private static string Test10(string fileName, string newValue)
{
  if (!fileName.Any(c => InvalidChars.Contains(c)))
  {
    return fileName;
  }

  return new string(fileName.Where(c => !InvalidChars.Contains(c)).ToArray());
}

private static string Test11(string fileName, string newValue)
{
  string invalidCharsRemoved = new string(fileName
    .Where(x => !InvalidChars.Contains(x))
    .ToArray());

  return invalidCharsRemoved;
}

Я использовал свой собственный метод, который, кажется, намного быстрее других опубликованных здесь (особенно регулярное выражение, которое так медленно), но я не тестировал все опубликованные методы.

https://dotnetfiddle.net/haIXiY

Первый метод (мой) и второй (тоже мой, но старый) также выполняют дополнительную проверку обратных косых черт, поэтому тест не идеален, но в любом случае он просто дает вам представление.

Результат на моем ноутбуке (за 100 000 итераций):

StringHelper.RemoveInvalidCharacters 1: 451 ms  
StringHelper.RemoveInvalidCharacters 2: 7139 ms  
StringHelper.RemoveInvalidCharacters 3: 2447 ms  
StringHelper.RemoveInvalidCharacters 4: 3733 ms  
StringHelper.RemoveInvalidCharacters 5: 11689 ms  (==> Regex!)

Самый быстрый способ:

public static string RemoveInvalidCharacters(string content, char replace = '_', bool doNotReplaceBackslashes = false)
{
    if (string.IsNullOrEmpty(content))
        return content;

    var idx = content.IndexOfAny(InvalidCharacters);
    if (idx >= 0)
    {
        var sb = new StringBuilder(content);
        while (idx >= 0)
        {
            if (sb[idx] != '\' || !doNotReplaceBackslashes)
                sb[idx] = replace;
            idx = content.IndexOfAny(InvalidCharacters, idx+1);
        }
        return sb.ToString();
    }
    return content;
}

Метод не компилируется "как есть" в соответствии со свойством InvalidCharacters, проверьте скрипт на предмет полного кода.

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

 public static class StringExtension
    {
        public static string RemoveInvalidChars(this string originalString)
        {            
            string finalString=string.Empty;
            if (!string.IsNullOrEmpty(originalString))
            {
                return string.Concat(originalString.Split(Path.GetInvalidFileNameChars()));
            }
            return finalString;            
        }
    }

Вы можете вызвать указанный выше метод расширения как:

string illegal = "\"M<>\"\a/ry/ h**ad:>> a/:*?\"<>| li*tt|le|| la\"mb.?";
string afterIllegalChars = illegal.RemoveInvalidChars();

Потому что каждая строка - это путь. Или почему имеет смысл расширять string только для одного особого случая?

Andreas 05.02.2021 17:54

Другие вопросы по теме