在字符串比较中忽略重音字母

168

我需要在C#中比较2个字符串,并将带重音的字母视为与不带重音的字母相同。例如:

string s1 = "hello";
string s2 = "héllo";

s1.Equals(s2, StringComparison.InvariantCultureIgnoreCase);
s1.Equals(s2, StringComparison.OrdinalIgnoreCase);

就我的应用程序而言,这两个字符串需要相同,但是这两个语句都被评估为false。在C#中有没有一种方法可以实现这一点?

6个回答

279

就我个人而言,knightfor的回答(截至本文撰写)应该是被采纳的答案。

这里有一个从字符串中剥离变音符号的函数:

static string RemoveDiacritics(string text)
{
  string formD = text.Normalize(NormalizationForm.FormD);
  StringBuilder sb = new StringBuilder();

  foreach (char ch in formD)
  {
    UnicodeCategory uc = CharUnicodeInfo.GetUnicodeCategory(ch);
    if (uc != UnicodeCategory.NonSpacingMark)
    {
      sb.Append(ch);
    }
  }

  return sb.ToString().Normalize(NormalizationForm.FormC);
}

更多详细信息请参见MichKap的博客已故...)。

其原理是将' é '转换为2个连续字符'e'和音符。然后迭代字符并跳过变音符号。

"héllo" 变成 "he<acute>llo",再变为 "hello"。

Debug.Assert("hello"==RemoveDiacritics("héllo"));
注意:这是同一函数的更紧凑的.NET4+友好版本:
static string RemoveDiacritics(string text)
{
  return string.Concat( 
      text.Normalize(NormalizationForm.FormD)
      .Where(ch => CharUnicodeInfo.GetUnicodeCategory(ch)!=
                                    UnicodeCategory.NonSpacingMark)
    ).Normalize(NormalizationForm.FormC);
}

3
在 .NET Core 中如何操作,因为它没有 string.Normalize 方法? - Andre Soares
谢谢您,我希望我可以投多次赞!然而,它不能处理所有带重音符号的字母,例如 ð、ħ 和 ø 无法转换为 o、h 和 o。有没有办法也处理这些字母呢? - Avrohom Yisroel
@AvrohomYisroel,“ð”是“拉丁小写字母Eth”,它是一个独立的字母,不是“带重音的o”或“带重音的d”。其他的是“带斜杠的拉丁小写字母H”和“带斜杠的拉丁小写字母O”,也可以被视为独立的字母。 - Hans Kesting

163

如果您不需要转换字符串,只想检查是否相等,您可以使用

string s1 = "hello";
string s2 = "héllo";

if (String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace) == 0)
{
    // both strings are equal
}

或者如果您希望比较时也不区分大小写

string s1 = "HEllO";
string s2 = "héLLo";

if (String.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) == 0)
{
    // both strings are equal
}

3
如果其他人对IgnoreNonSpace选项感到好奇,您可能想阅读一下关于它的讨论。http://www.pcreview.co.uk/forums/accent-insensitive-t3924592.html 简而言之,它还不错 :) - Jim W says reinstate Monica
这个方法对于这两个字符串失败了:tarafli / TARAFLI,然而SQL服务器说它们相等,就像应该的一样。 - Furkan Gözükara
5
通常情况下,SQL Server 配置为不区分大小写,但是在 .Net 中默认的比较是区分大小写的。我已更新答案以显示如何使其不区分大小写。 - knightpfhor
1
如果有人对HashCode感兴趣:CultureInfo.CurrentCulture.CompareInfo.GetHashCode(obj, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase) - Yepeekai
1
更好的是,使用 .Net Core,我们可以获得一个 StringComparer:StringComparer.Create(CultureInfo.CurrentCulture, CompareOptions.IgnoreCase | CompareOptions.IgnoreNonSpace)。(除非通过反射,否则在 .Net Framework 中不可用。) - Frédéric
显示剩余2条评论

6

我曾经需要使用StartsWith方法做类似的事情。这是一个简单的解决方案,参考自@Serge - appTranslator。

以下是一个扩展方法:

    public static bool StartsWith(this string str, string value, CultureInfo culture, CompareOptions options)
    {
        if (str.Length >= value.Length)
            return string.Compare(str.Substring(0, value.Length), value, culture, options) == 0;
        else
            return false;            
    }

对于一行代码迷 ;)

    public static bool StartsWith(this string str, string value, CultureInfo culture, CompareOptions options)
    {
        return str.Length >= value.Length && string.Compare(str.Substring(0, value.Length), value, culture, options) == 0;
    }

不区分重音和大小写的startsWith函数可以这样调用:

value.ToString().StartsWith(str, CultureInfo.InvariantCulture, CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreCase)

5
以下方法CompareIgnoreAccents(...)适用于您的示例数据。这是我获取背景信息的文章:http://www.codeproject.com/KB/cs/EncodingAccents.aspx
private static bool CompareIgnoreAccents(string s1, string s2)
{
    return string.Compare(
        RemoveAccents(s1), RemoveAccents(s2), StringComparison.InvariantCultureIgnoreCase) == 0;
}

private static string RemoveAccents(string s)
{
    Encoding destEncoding = Encoding.GetEncoding("iso-8859-8");

    return destEncoding.GetString(
        Encoding.Convert(Encoding.UTF8, destEncoding, Encoding.UTF8.GetBytes(s)));
}

我认为使用扩展方法会更好:

public static string RemoveAccents(this string s)
{
    Encoding destEncoding = Encoding.GetEncoding("iso-8859-8");

    return destEncoding.GetString(
        Encoding.Convert(Encoding.UTF8, destEncoding, Encoding.UTF8.GetBytes(s)));
}

然后使用方式如下:
if(string.Compare(s1.RemoveAccents(), s2.RemoveAccents(), true) == 0) {
   ...

1
这会使带重音的字母变成“?”。 - onmyway133
4
这是一种破坏性的比较方式,例如,ā和ē将被视为相等。您会丢失任何超过0xFF的字符,并且不能保证字符串在忽略重音符号时相等。 - Abel
你也会失去像ñ这样的东西。如果你问我,这不是一个解决方案。 - Ignacio Soler Garcia

1

一种更简单的去除重音符号的方法:

    Dim source As String = "áéíóúç"
    Dim result As String

    Dim bytes As Byte() = Encoding.GetEncoding("Cyrillic").GetBytes(source)
    result = Encoding.ASCII.GetString(bytes)

-4

尝试使用String.Compare方法的重载。

String.Compare方法(String,String,Boolean,CultureInfo)

它基于包括CultureInfo在内的比较操作生成int值。页面上的示例比较en-US和en-CZ中的“Change”。在en-CZ中,CH是一个单独的“字母”。

链接中的示例:

using System;
using System.Globalization;

class Sample {
    public static void Main() {
    String str1 = "change";
    String str2 = "dollar";
    String relation = null;

    relation = symbol( String.Compare(str1, str2, false, new CultureInfo("en-US")) );
    Console.WriteLine("For en-US: {0} {1} {2}", str1, relation, str2);

    relation = symbol( String.Compare(str1, str2, false, new CultureInfo("cs-CZ")) );
    Console.WriteLine("For cs-CZ: {0} {1} {2}", str1, relation, str2);
    }

    private static String symbol(int r) {
    String s = "=";
    if      (r < 0) s = "<";
    else if (r > 0) s = ">";
    return s;
    }
}
/*
This example produces the following results.
For en-US: change < dollar
For cs-CZ: change > dollar
*/

因此,对于带有重音的语言,您需要获取文化信息,然后基于该信息测试字符串。

http://msdn.microsoft.com/en-us/library/hyxc48dt.aspx


这种方法比直接比较字符串更好,但它仍然认为基本字母及其重音版本是不同的。因此它并没有回答原始问题,即希望忽略重音符号。 - C.B.

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接