这是一个相当不够优雅的问题,因此没有一种方法会真正地优雅。
不过,我们肯定可以改进。哪种方法最好取决于需要进行的更改数量(以及要更改的字符串大小,尽管通常最好假设它可能很大)。
在一个替换字符的情况下,到目前为止使用.Replace的方法是优越的,但我会用"\u2013"替换char.ConvertFromUtf32(8211)。对性能的影响微不足道,但它更易读,因为通常用十六进制引用该字符而不是用十进制表示法(当然,char.ConvertFromUtf32(0x2013)在这方面也有同样的优势,但在只使用字符符号时没有优势)。 (在某些情况下,也可以直接将'–'放入代码中-在某些情况下更易读,但在这种情况下,它看起来与‒、—或-几乎相同,对读者来说不太容易分辨)。
我还会用稍快的字符替换(在这种情况下至少是这样,因为你要用单个字符替换另一个单个字符)来代替字符串替换。
采用这种方法改进您的代码如下:
formattedString = formattedString.Replace('\u2013', '-');
formattedString = formattedString.Replace('\u2014', '-');
formattedString = formattedString.Replace('\u2015', '-');
即使只有三个替换,这种方法的效率可能仍然不如一次性完成所有这些替换(我不会测试需要多长的
formattedString才能达到此效果,在某个数量级以上,即使对于只有几个字符的字符串,一次性处理也更有效)。一种方法是:
StringBuilder sb = new StringBuilder(formattedString.length);
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 = {}
StringBuilder sb = new StringBuilder(formattedString.length);
foreach(char c in formattedString)
sb.Append(replacements[(int)c]);
formattedString = sb.ToString();
现在,对于Unicode来说这是不切实际的。它有超过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;
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个字符的内存。此中块的最佳大小因数据而异(即,您要进行哪些替换),但假设将有相互相同的块通常保持。现在,最后,如果您关心“天体平面”中的字符,它们在.NET内部使用的UTF-16中表示为代理对,或者如果您关心以特定方式替换某些多字符字符串呢?在这种情况下,您可能至少需要在switch中读取一个或多个字符(如果使用块方法处理大多数情况,则可以使用无法转换的情况来表示需要这样的操作)。在这种情况下,最好将其转换为US-ASCII,然后再使用System.Text.Encoding和EncoderFallback和EncoderFallbackBuffer的自定义实现进行处理。这意味着大多数转换(明显情况)将为您完成,而您的实现仅需处理特殊情况。