Это довольно не элегантная проблема, поэтому ни один метод не будет действительно элегантным.
Тем не менее, мы, безусловно, можем улучшить вещи. Какой именно подход будет работать лучше всего, будет зависеть от количества изменений, которые необходимо внести (и размера изменяемой строки, хотя часто лучше предположить, что это либо может быть, либо может быть довольно большим).
Для одного заменяющего символа подход, который вы используете до сих пор - использование .Replace
лучше, хотя я бы заменил char.ConvertFromUtf32(8211)
на "\u2013"
. Влияние на производительность ничтожно мало, но оно более читабельно, так как более привычно ссылаться на этот символ в шестнадцатеричном, как в U + 2013, чем в десятичной записи (конечно, char.ConvertFromUtf32(0x2013)
будет иметь то же преимущество, но не имеет никакого преимущества только при использовании обозначение символа). (Можно также просто вставить '–'
прямо в код - в некоторых случаях он более читабелен, но в этом случае он менее читается, когда он выглядит так же, как -, - или - для читателя).
Я бы также заменил строку replace на немного более быструю замену символа (по крайней мере, в этом случае, когда вы заменяете один символ одним символом).
Принимая этот подход к вашему коду, он становится:
formattedString = formattedString.Replace('\u2013', '-');
formattedString = formattedString.Replace('\u2014', '-');
formattedString = formattedString.Replace('\u2015', '-');
Даже с таким количеством замен, как 3, это, вероятно, будет менее эффективным, чем выполнение всех таких замен за один проход (я не собираюсь делать тест, чтобы выяснить, сколько потребуется formattedString
для этого, выше определенного числа становится более эффективным использование одного прохода даже для строк только из нескольких символов). Один из подходов:
StringBuilder sb = new StringBuilder(formattedString.length);//we know this is the capacity so we initialise with it:
foreach(char c in formattedString)
switch(c)
{
case '\u2013': case '\u2014': case '\u2015':
sb.Append('-');
default:
sb.Append(c)
}
formattedString = sb.ToString();
(Другая возможность состоит в том, чтобы проверить, если (int)c >= 0x2013 && (int)c <= 0x2015
, но уменьшение количества ветвей невелико и не имеет значения, если большинство символов, которые вы ищете, не численно близки друг к другу).
С различными вариантами (например, если formattedString будет выводиться в поток в какой-то момент, может быть лучше сделать это после получения каждого последнего символа, а не буферизации снова).
Обратите внимание, что этот подход не имеет дело со строками из нескольких символов в вашем поиске, но может работать со строками в вашем выводе, например, мы могли бы включить:
case 'ß':
sb.Append("ss");
Теперь, это более эффективно, чем предыдущее, но все еще становится громоздким после определенного количества случаев замены. Он также включает в себя множество веток, которые имеют свои собственные проблемы с производительностью.
Давайте на минуту рассмотрим противоположную проблему. Скажем, вы хотите конвертировать символы из источника, который был только в диапазоне US-ASCII. У вас будет только 128 возможных символов, поэтому ваш подход может быть:
char[] replacements = {/*list of replacement characters*/}
StringBuilder sb = new StringBuilder(formattedString.length);
foreach(char c in formattedString)
sb.Append(replacements[(int)c]);
formattedString = sb.ToString();
Теперь, это не практично с Юникодом, который назначил более 109 000 символов в диапазоне от 0 до 1114111. Однако есть вероятность, что символы, которые вас интересуют, не только намного меньше, чем это (и если вы действительно это сделали Не забывайте, что во многих случаях вам нужен подход, описанный выше), но также в относительно ограниченном блоке.
Подумайте также, если вы не особенно заботитесь о каких-либо суррогатах (мы подойдем к ним позже) Ну, большинство персонажей вас не волнует, поэтому давайте рассмотрим это:
char[] unchanged = new char[128];
for(int i = 0; i != 128; ++i)
unchanged[i] = (char)i;
char[] error = new string('\uFFFD', 128).ToCharArray();
char[] block0 = (new string('\uFFFD', 13) + "---" + new string('\uFFFD', 112)).ToCharArray();
char[][] blocks = new char[8704][];
for(int i = 1; i != 8704; ++i)
blocks[i] = error;
blocks[0] = unchanged;
blocks[64] = block0;
/* the above need only happen once, so it could be done with static members of a helper class that are initialised in a static constructor*/
StringBuilder sb = new StringBuilder(formattedString.Length);
foreach(char c in formattedString)
{
int cAsI = (int)c;
sb.Append(blocks[i / 128][i % 128]);
}
string ret = sb.ToString();
if(ret.IndexOf('\uFFFD') != -1)
throw new ArgumentException("Unconvertable character");
formattedString = ret;
Баланс между тем, лучше ли проверять обнаруживаемый символ за один проход в конце (как указано выше) или при каждом преобразовании, зависит от вероятности того, что это произойдет. Очевидно, даже лучше, если вы можете быть уверены (благодаря знанию ваших данных), что это не так, и можете удалить эту проверку - но вы должны быть действительно уверенными.
Преимущество здесь состоит в том, что, хотя мы используем метод поиска, мы берем только 384 символа памяти для хранения поиска (и еще больше для массива), а не 109 000 символов , Наилучший размер для блоков в пределах этого варьируется в зависимости от ваших данных (то есть, какие замены вы хотите сделать), но предположение, что будут блоки, которые идентичны друг другу, имеет тенденцию удерживаться.
Теперь, наконец, что, если вам небезразличен символ в «астральных планах», которые представлены как суррогатные пары в UTF-16, используемом внутренне в .NET, или если вы хотите заменить некоторые строки из нескольких символов в определенномway?
В этом случае вам, вероятно, придется как минимум прочитать символ или более вперед в вашем переключателе (если в большинстве случаев вы используете блочный метод, вы можете использовать необратимый регистр длясигнал такой работы не требуется).В таком случае, возможно, стоит конвертировать в US-ASCII и затем обратно с System.Text.Encoding
и пользовательской реализацией EncoderFallback
и EncoderFallbackBuffer
и обрабатывать его там.Это означает, что большая часть преобразования (очевидные случаи) будет выполнена для вас, в то время как ваша реализация может иметь дело только с особыми случаями.