C#中的双字节字符串比较

4

我有两个字符串,一个是双字节值,另一个是单字节值。进行字符串比较后返回false,我该如何忽略单字节/双字节的差异使它们正确比较?

string s1 = "smatsumoto11"
string s2 = "smatsumoto11"

在同样的情况下,如果你在SQL Server中有一个包含值smatsumoto11的nvarchar列,在where条件中使用字符串smatsumoto11查询数据将返回相同的行。我需要类似于C#字符串比较的语义。
我已经尝试了MSDN上提到的一些选项,但它们似乎不起作用。
有什么想法吗?

3
我尝试了MSDN上提到的几个选项,但它们似乎都不起作用。 - Binary Worrier
3
另外,你从哪里获取单字节字符串? - Binary Worrier
如果单字节字符串存储在 C# string 对象中,则它已经处于2字节形式。可能这些字符串来自不同的代码页或编码,因此与您的预期不同。 - Binary Worrier
我认为我误解了这个问题;最终你是想找到一种方法来查找某些更高的Unicode值的公共等效字符数据,对吗? - Marc Gravell
@BinaryWorrier,由于.NET中的字符串是Unicode编码,因此字符串不能来自不同的代码页。有关详细信息,请参见我下面的答案 - Solomon Rutzky
@MarcGravell和Metallikanz:请查看我下面的答案,了解这些特殊字符的详细信息以及为什么这些字符串在SQL Server中相等。 - Solomon Rutzky
4个回答

6

您的s1包含所谓的“全角”字符,因此可以使用string.Compare并告诉它忽略字符宽度:

string.Compare(s1, s2, CultureInfo.CurrentCulture, CompareOptions.IgnoreWidth);

(当然,如果需要的话,请指定不同的 CultureInfo。)

还有一个问题...是否可能将“全角”字符转换为普通字符? - agp

3
在进行比较之前,您可以尝试“规范化”字符串:Normalize
返回一个新的字符串,其文本值与此字符串相同,但其二进制表示形式符合指定的Unicode规范化形式。一些Unicode字符具有多个等效的二进制表示形式,由组合和/或复合Unicode字符集组成。单个字符存在多个表示形式会使搜索、排序、匹配和其他操作变得复杂。

1

我的机器显示s1是在MS明朝字体中。

MS明朝(MS 明朝)-随日本版Windows 3.1或更高版本,某些版本的Internet Explorer 3日语字体包,所有地区的Windows XP,Microsoft Office v.X至2004年一起分发。

以下内容已被Arnout的答案完全淘汰。

我知道一个技巧,类似于iconv中的//TRANSLIT,在这里似乎也可以使用。

        string s1 = "smatsumoto11";
        string s2 = "smatsumoto11";

        string conv = Encoding.ASCII.GetString(Encoding.GetEncoding("Cyrillic").GetBytes(s1));

        if (conv == s2) Console.WriteLine("They are the same!");

有一天我真的得试着找出这是如何工作的...


字体选择只是您的浏览器处理这些字符的方式。 - Will Dean
是的,我觉得它更像是一条评论而不是一条答案。 - Jonas Elfström

1
虽然接受的答案有效,并且在涉及“宽”字符的主要问题上是正确的,但问题中存在一些误解和技术细节,应该予以解决,以便更好地理解在.NET和SQL Server中实际发生了什么。
首先:
我有两个字符串,一个带有双字节值,另一个是单字节。
不,你并没有。你有两个Unicode字符串,编码为UTF-16小端(这是Windows和.NET的工作方式)。虽然在实际情况下,大多数情况下字符是双字节的,但这仅涵盖了62,000-63,000个字符(即U+0000和U+FFFF之间的代码点或0-65,535个“有效”字符)。但Unicode允许映射超过1.1百万个代码点,并且目前已经映射了260,000个以上的代码点(已经映射)。 U+FFFF / 65,535以上的代码点称为补充字符,映射到两个双字节值集合,称为代理对。因此,虽然它们使用较少,但大多数Unicode代码点实际上是4个字节。
第二:
字符串比较结果返回false,如何使它们正确比较? 中的字母被称为"全角"字符。您可以在此处查看它们的完整列表:

http://unicode.org/cldr/utility/list-unicodeset.jsp?a=[:East_Asian_Width=Fullwidth:]

一些关于为什么会存在不同宽度的解释可以在这里找到:

http://unicode-table.com/en/blocks/halfwidth-and-fullwidth-forms/

如果您想比较问题中的两个字符串是否相等,您可以使用String.Compare(String, String, CultureInfo, CompareOptions)方法(如@ Arnout的答案中所述),或者您可以按照以下方式使用CompareInfo.Compare(String, String, CompareOptions)

CompareInfo.Compare(s1, s2, CompareOptions.IgnoreWidth)

第三点:

在同样的情况下,如果你有一个 SQL Server 中的 nvarchar 列,其中包含值为 smatsumoto11 的数据,在使用条件为 smatsumoto11 的 where 子句查询数据时将返回同一行。

这是一种潜在的危险思维方式,用于比较字符串。在几乎所有数据库中,字符串没有特定的比较方式,除非字符串在7位ASCII(值为0-127)中,这甚至不包括代码页,而且我不知道这是否是一个选项。比较基于特定的LCID / Locale / Culture / Collation。SQL Server中的默认排序规则(至少在美国)是SQL_Latin1_General_CP1_CI_AS,它是大小写不敏感和重音符号敏感的。它还使用Code Page 1252(影响CHAR / VARCHAR数据,而不是NCHAR / NVARCHAR),以及“en-US”文化。其他文化/ LCID的排序规则可能不等同于全角和“半角”。而且,排序规则名称中有_WS的排序规则肯定不会等同于这两个字符串,因为_WS代表“宽度敏感”,如果您不指定CompareOptions.IgnoreWidth选项,则是.NET比较的默认设置。

如果你运行以下的查询来查找名称中有 _WS 的排序规则,你会发现在 SQL Server 2012 中,共有 3885 个排序规则中有 1776 个是区分宽度的,它们不会匹配这两个字符串。当然,还有262个二进制排序规则(即以废弃的 _BIN 或优先的 _BIN2 结尾的名称),它们也不会使这两个字符串相等,但这并不是宽度敏感的问题。

SELECT *
FROM sys.fn_helpcollations()
WHERE [name] LIKE N'%[_]WS%'
ORDER BY [name];
-- 1776 out of 3885 on SQL Server 2012

此外,正如我刚提到的,不幸的是(已经被弃用的)默认排序规则SQL_Latin1_General_CP1_CI_AS,甚至更好的版本Latin1_General_100_CI_AS是不区分大小写的。因此,您要比较的字符串都是小写字母,因此在仅使用CompareOptions.IgnoreWidth时它们是相等的,但如果您想在 SQL Server 中模拟这些特定的排序规则,则 .NET 的默认行为是区分大小写的将无法匹配 SQL Server 行为。为了更好地匹配 SQL Server 行为(至少对于那些排序规则或任何标记为具有_CI而没有_WS的排序规则),您还需要包括CompareOptions.IgnoreCase选项,如下所示:
CompareInfo.Compare(s1, s2, CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase)

// or

String.Compare(s1, s2, CultureInfo.CurrentCulture, 
               CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase)

额外资源:

.NET Framework 中比较字符串

.NET Framework 中使用字符串的最佳实践


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