Мне нужен надежный и простой способ удалить недопустимые пути и символы файла из простой строки. Я использовал приведенный ниже код, но, похоже, он ничего не делает, что мне не хватает?
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();
}
}
}
Имена в стиле Unix недействительны в Windows, и я не хочу иметь дело с короткими именами 8.3.
GetInvalidFileNameChars() удалит такие вещи, как: \ etc из путей к папкам.
Path.GetInvalidPathChars(), похоже, не удаляет * или ?
Я проверил пять ответов на этот вопрос (временной цикл 100 000), и следующий метод является самым быстрым. Регулярное выражение заняло 2-е место и было на 25% медленнее: общедоступная строка GetSafeFilename (строка имени файла) {return string.Join ("_", filename.Split (Path.GetInvalidFileNameChars ())); }
Я добавил новую быструю альтернативу и несколько тестов в этот ответ





Для начала, Обрезка удаляет символы только из начала или конца строки. Во-вторых, вы должны оценить, действительно ли вы хотите удалить оскорбительные символы или быстро вывести из строя и сообщить пользователю, что их имя файла недействительно. Мой выбор - последнее, но мой ответ должен, по крайней мере, показать вам, как поступать правильно И неправильно:
Вопрос StackOverflow, показывающий, как проверить, является ли данная строка допустимым именем файла. Обратите внимание, что вы можете использовать регулярное выражение из этого вопроса для удаления символов с заменой регулярного выражения (если вам действительно нужно это сделать).
Особенно согласен со вторым советом.
Обычно я согласен со вторым, но у меня есть программа, которая генерирует имя файла и в некоторых ситуациях может содержать недопустимые символы. Поскольку моя программа генерирует недопустимые имена файлов, я считаю целесообразным удалить / заменить эти символы. (Просто укажите допустимый вариант использования)
Попробуйте вместо этого что-нибудь подобное;
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 к вашему ответу за то, что оно фактически отвечает на его вопрос! :)
@Michael Stum: они «компилируются» и должны быть своего рода конечным автоматом, но было бы наивно предполагать, что они гарантированно будут более эффективными под капотом, чем цикл.
На длине пути это, вероятно, не будет иметь большого значения. На более длинной строке, я полагаю, регулярное выражение будет быстрее.
Я бы придерживался решения без регулярных выражений: в большинстве случаев оно, вероятно, будет более эффективным. Если вы используете решение с регулярным выражением, измените string.Format () просто на "[" + "...". Если вы собираетесь рассматривать illegal как имя файла без пути после замены специальных символов, вам понадобится только Path.InvalidFileNameChars ().
Нет необходимости складывать два списка вместе. Список символов недопустимого имени файла содержит список символов недопустимого пути и еще несколько. Вот списки обоих списков, приведенных к 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
@sjbotha, это может быть правдой для Windows и реализации .NET от Microsoft. Я не хочу делать то же самое предположение, скажем, для монофонического запуска Linux.
По поводу первого решения. Разве StringBuilder не должен быть более эффективным, чем присвоение строк?
Если строка содержит китайские символы, решение может не сработать.
@PerlDev: Вы действительно это проверяли? characters должен быть многобайтовым (sizeof(char) == 2), так что это не должно быть проблемой. Решение с регулярным выражением также должно быть в порядке.
В чем проблема с очисткой, Bob Tables?
Поправьте меня, если я ошибаюсь, но звонить как Path.GetInvalidFileNameChars(), так и Path.GetInvalidPathChars() излишне. Одного Path.GetInvalidFileNameChars() должно быть достаточно.
@JoeyAdams: см. Мой ответ Сарелу Боте. Короче говоря, в Windows одно надмножество другого. Лично я не желаю делать одинаковые ставки на кросс-платформу, а C# и .NET в целом все время получают все более широкую аудиторию через Mono.
Как бы то ни было, @MatthewScharley, реализация GetInvalidPathChars () Mono возвращает только 0x00, а GetInvalidFileNameChars () возвращает только 0x00 и '/' при работе на платформах, отличных от Windows. В Windows списки недопустимых символов намного длиннее, а GetInvalidPathChars () полностью дублируется внутри GetInvalidFileNameChars (). Это не изменится в обозримом будущем, поэтому все, что вы на самом деле делаете, - это удваиваете время, необходимое для выполнения этой функции, потому что вы беспокоитесь, что определение допустимого пути когда-нибудь изменится. Чего не будет.
И давайте проясним это: эта часть исходного кода Mono не менялась ВОСЕМЬ ЛЕТ, за исключением незначительного улучшения производительности в 2007 году.
@Warren: Не стесняйтесь вывести результирующую строку, если вы действительно обеспокоены, но давайте будем совершенно честны: разница между 20 и 40 итерациями по сравнению со строкой длиной вашего среднего пути (скажем, 100 символов, чтобы быть щедрым) сделает ровно нет разница во времени выполнения вашей функции. Для всех целей практичный об этом не нужно беспокоиться. С другой стороны, эти две функции служат разным целям, и (по крайней мере, на мой взгляд) было бы вполне разумно, чтобы одна функция не возвращала надмножество другой для некоторой данной файловой системы.
Как может удвоение работы (будь то дедупликация массива или двукратное прохождение через почти одни и те же значения массива) не требует «абсолютно никакой разницы»? Вы не хуже меня знаете, что это неправильно, поэтому -не--говори- -это-. Мы пытаемся быть образовательным ресурсом в Stackoverflow, а не местом для риторических расцветов, вызванных тем, что вы ошибаетесь. Давайте проясним: то, что вы здесь рекомендуете, фактически то же самое, что и старая утка Daily WTF о предоставлении вашего собственного определения ИСТИНА и ЛОЖЬ, потому что вы не можете доверять компилятору или библиотекам, чтобы всегда делать это правильно.
GetInvalidFileNameChars () всегда - ВСЕГДА, вы меня слышите - будет включать все в GetInvalidPathChars (), потому что файл не может содержать недопустимый символ в имени пути. Ни одна файловая система сегодня не позволяет этого, ни одна файловая система никогда не позволит. В любом случае, собственная документация Microsoft по этим функциям очень четко заявляет, что не следует ожидать, что список символов будет гарантированно точным, потому что файловые системы в любом случае могут поддерживать что-то другое.
Я бы, вероятно, встал на сторону Мэтью и просто сказал, что это предположение - мать всех неудач. Вы говорите об оптимизации кода, который, вероятно, не требует оптимизации из-за потенциальной правильности. Я бы принял правильность над преждевременной оптимизацией в любой день
@Charleh, это обсуждение настолько ненужно ... код всегда должен быть оптимизирован, и нет риска, что это будет неправильным. Имя файла также является частью пути. Поэтому нелогично, что GetInvalidPathChars() может содержать символы, которых нет в GetInvalidFileNameChars(). Вы не принимаете правильность перед «преждевременной» оптимизацией. Вы просто используете плохой код.
Лично я бы предпочел такой способ: var invalid = Path.GetInvalidFileNameChars().Union(Path.GetInvalidPathChars()); foreach(char c in invalid) illegal = illegal.Replace(c.ToString(), "_");
Я не уверен, почему вы, ребята, так любопытны, почему он хочет это использовать. Существуют различные допустимые сценарии, в которых это было бы полезно. Наше приложение, например, выводит файлы xlsx по электронной почте в виде отчетов, и если мы не проверим его при входе, вы не узнаете до запланированного времени создания отчета, что имя файла недействительно. У нас были проблемы, когда в прошлом кто-то случайно вводил «меньше чем» в имени файла и сохранял его. Кроме того, некоторые из наших клиентов запускают Linux, а некоторые - окна, поэтому разрешенные файлы не совпадают.
@JohnLord - еще один распространенный вариант использования, связанный с именами файлов, поступающими из внешних электронных писем. Вы не можете контролировать отправляемое вам имя файла. Вы, конечно, можете выбросить оригинал и заменить его чем-то собственным изобретением, но бывают случаи, когда вы хотите сохранить как можно больше оригинала для целей ИИ.
Я думаю, что гораздо проще проверить, используя регулярное выражение и указав, какие символы разрешены, вместо того, чтобы пытаться проверить все плохие символы. Смотрите эти ссылки: 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 с другими допустимыми / недопустимыми символами. Прокатывая собственное с помощью регулярного выражения, вы изобретаете колесо и переносите проблему платформы в ваш собственный код.
Я согласен с вашим советом относительно онлайн-редакторов / тестеров регулярных выражений. Я считаю их бесценными (поскольку регулярные выражения - штука сложная и полна тонкости, которая может легко сбить вас с толку, давая вам регулярное выражение, которое ведет себя совершенно неожиданным образом с крайними случаями). Мне больше всего нравится regex101.com (мне нравится, как он разбивает регулярное выражение и ясно показывает, что он ожидает найти). Мне также очень нравится debuggex.com, поскольку он имеет компактное визуальное представление групп совпадений, классов символов и прочего.
Для этого я использую регулярные выражения. Во-первых, я динамически создаю регулярное выражение.
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: Ваша версия все равно лучше, чем у Мэтью, если вы немного ее измените. Обратитесь к моему ответу о том, как.
Я бы также добавил несколько других шаблонов недопустимых имен файлов, которые можно найти на 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: добавьте @ перед строковым выражением, если вы столкнулись с ошибкой «Нераспознанная escape-последовательность», то есть String.Format(@"^CON| ... )"
Выбросить исключение.
if ( fileName.IndexOfAny(Path.GetInvalidFileNameChars()) > -1 )
{
throw new ArgumentException();
}
Я не думаю, что здесь ценно создание исключения, поскольку в вопросе говорится об удалении недопустимых символов, а не просто о создании исключения.
Вот фрагмент кода, который должен помочь для .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());
Мне нравится этот способ: вы сохраняете только разрешенные символы в строке (которая представляет собой не что иное, как массив символов).
Я знаю, что это старый вопрос, но это отличный ответ. Однако я хотел добавить, что в C# вы не можете преобразовать char [] в строку ни неявно, ни явно (сумасшедший, я знаю), поэтому вам нужно перетащить его в конструктор строк.
Я не подтверждал это, но ожидаю, что Path.GetInvalidPathChars () будет надмножеством GetInvalidFileNameChars () и будет охватывать как имена файлов, так и пути, поэтому я бы, вероятно, использовал это вместо этого.
@anjdreas на самом деле Path.GetInvalidPathChars () кажется подмножеством Path.GetInvalidFileNameChars (), а не наоборот. Например, Path.GetInvalidPathChars () не вернет "?".
Это хороший ответ. Я использую как список имен файлов, так и список путей к файлам: ____________________________ string cleanData = new string (data.Where (x =>! Path.GetInvalidFileNameChars (). Contains (x) &&! Path.GetInvalidPathChars (). Contains (x)). ToArray ());
Вы также можете сделать var invalidChars = new HashSet <char> (Path.GetInvalidFileNameChars ()) и сделать его O (n) вместо O (n ^ 2). Почему бы и нет.
Я абсолютно предпочитаю идею Джеффа Йейтса. Он будет работать отлично, если вы его немного измените:
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, это не работает, возвращается исходная строка.
На самом деле вы можете сделать это с помощью Linq вот так: var invalid = new HashSet<char>(Path.GetInvalidPathChars()); return new string(originalString.Where(s => !invalid.Contains(s)).ToArray()). Производительность, вероятно, невелика, но это, вероятно, не имеет значения.
@Karan или Jon Какой вклад вы отправляете этой функции? См. Мою правку для проверки этого метода.
Это просто - парни передавали строки с действительными символами. Проголосовали за крутое решение для агрегатов.
Очень хорошее решение, но очищает только имя файла (как указано), но не фактический путь, поскольку он рассматривает «\» как недопустимый символ и если у вас есть что-то вроде «\\ MyServer \ e $ \ demo \ Output \ Test \ 1111_joe_soap. pdf ", он возвращает" MyServere $ demoOutputTest1111_joe_soap.pdf "
Плюс 1 за творческое использование агрегата.
Все это отличные решения, но все они полагаются на 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 вы могли бы захотеть написать в другой файловой системе, возможно, поэтому есть предупреждение.
@ fantastik78 - хороший аргумент, но в этом случае я хотел бы иметь дополнительный аргумент перечисления, чтобы указать мою удаленную FS. Если это слишком много усилий по техническому обслуживанию (что наиболее вероятно), весь этот метод по-прежнему является плохой идеей, потому что он создает неправильное впечатление о безопасности.
@Jan Я полностью с тобой согласен, я просто спорил из-за предупреждения.
Интересно, что это своего рода «черный список» недопустимых символов. Не лучше ли занести в белый список только известные действительные символы ?! Напоминает мне глупую идею "сканера вирусов" вместо добавления разрешенных приложений в белый список ....
Обратите внимание на то, что имена файлов указаны в предупреждении. На самом деле он говорит вам, что он не проверяет сами имена файлов, а только недопустимые символы. У вас все еще может быть неправильное имя файла, которое является зарезервированным словом. Также как бы вы занесли приложение в белый список? Я бы просто сделал так, чтобы у моего вируса было ваше имя файла и подпись.
Большинство вышеперечисленных решений объединяют недопустимые символы как для пути, так и для имени файла, что неверно (даже если оба вызова в настоящее время возвращают один и тот же набор символов). Сначала я бы разделил путь + имя файла на путь и имя файла, затем применил соответствующий набор к любому из них, а затем снова объединил их.
wvd_vegt
+1: Совершенно верно. Сегодня, работая в .NET 4.0, решение с регулярным выражением из верхнего ответа уничтожило все обратные косые черты на полном пути. Поэтому я сделал регулярное выражение для пути к каталогу и регулярное выражение только для имени файла, очищенное отдельно и рекомбинированное
Это может быть правдой, но это не отвечает на вопрос. Я не уверен, что расплывчатое «Я бы сделал это так» ужасно полезно по сравнению с некоторыми из уже имеющихся здесь полных решений (см., Например, ответ Лилли ниже)
Лучший способ удалить недопустимый символ из пользовательского ввода - заменить недопустимый символ с помощью класса 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 это решение намного лучше, чем другие. Вместо поиска всех недопустимых символов просто определите, какие из них действительны.
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;
}
}
Мне это нравится, потому что это позволяет избежать двух разных строк, создающих один и тот же результирующий путь.
Или вы можете просто сделать
[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, вам нужно будет использовать "" вместо "_", но ваш ответ, вероятно, применим к большему количеству из нас на практике. Я думаю, чаще всего делается замена недопустимых символов на какие-то допустимые.
Я протестировал пять методов из этого вопроса (временной цикл 100000), и этот метод является самым быстрым. Регулярное выражение заняло 2-е место и было на 25% медленнее, чем этот метод.
Чтобы обратиться к комментарию @BH, можно просто использовать string.Concat (name.Split (Path.GetInvalidFileNameChars ()))
Это будет вам нужно, и избежать столкновений
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».
@IIARROWS а что это на ваш взгляд?
Я не знаю, я просто не чувствовал этого, когда писал свой комментарий ... теперь, когда я попытался его вычислить, похоже, вы правы.
Я выбрал этот из-за вашего соображения производительности. Спасибо.
Думаю, на вопрос уже не дан полный ответ ... Ответы описывают только чистое имя файла ИЛИ путь ... не то и другое. Вот мое решение:
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;
}
}
Вы можете четко использовать метод.
Я создал метод расширения, который сочетает в себе несколько предложений:
Источник:
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, '_');
В дополнение к предоставленному вами ответу, пожалуйста, рассмотрите возможность предоставления краткого объяснения того, почему и как это решает проблему.
Вот мой небольшой вклад. Метод для замены в той же строке без создания новых строк или построителей строк. Это быстро, легко для понимания и является хорошей альтернативой всем упомянутым в этом посте.
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 только для одного особого случая?
Обрезка удаляет символы в начале и конце строк. Однако вы, вероятно, должны спросить, почему данные недействительны, и вместо того, чтобы пытаться очистить / исправить данные, отклоните данные.