Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Bug in the string comparing of the .NET Framework

It's a requirement for any comparison sort to work that the underlying order operator is transitive and antisymmetric.

In .NET, that's not true for some strings:

static void CompareBug() {   string x = "\u002D\u30A2";  // or just "-ア" if charset allows   string y = "\u3042";        // or just "あ" if charset allows    Console.WriteLine(x.CompareTo(y));  // positive one   Console.WriteLine(y.CompareTo(x));  // positive one   Console.WriteLine(StringComparer.InvariantCulture.Compare(x, y));  // positive one   Console.WriteLine(StringComparer.InvariantCulture.Compare(y, x));  // positive one    var ja = StringComparer.Create(new CultureInfo("ja-JP", false), false);   Console.WriteLine(ja.Compare(x, y));  // positive one   Console.WriteLine(ja.Compare(y, x));  // positive one } 

You see that x is strictly greater than y, and y is strictly greater than x.

Because x.CompareTo(x) and so on all give zero (0), it is clear that this is not an order. Not surprisingly, I get unpredictable results when I Sort arrays or lists containing strings like x and y. Though I haven't tested this, I'm sure SortedDictionary<string, WhatEver> will have problems keeping itself in sorted order and/or locating items if strings like x and y are used for keys.

Is this bug well-known? What versions of the framework are affected (I'm trying this with .NET 4.0)?

EDIT:

Here's an example where the sign is negative either way:

x = "\u4E00\u30A0";         // equiv: "一゠" y = "\u4E00\u002D\u0041";   // equiv: "一-A" 
like image 785
Jeppe Stig Nielsen Avatar asked Nov 06 '12 15:11

Jeppe Stig Nielsen


People also ask

Which is the string method used to compare two strings with each other in dotnet?

C# String Compare() The C# Compare() method is used to compare first string with second string lexicographically. It returns an integer value. If both strings are equal, it returns 0.

What does OrdinalIgnoreCase mean?

The OrdinalIgnoreCase property actually returns an instance of an anonymous class derived from the StringComparer class.

How to compare the values of two strings in net?

.NET provides several methods to compare the values of strings. The following table lists and describes the value-comparison methods. The static String.Compare method provides a thorough way of comparing two strings. This method is culturally aware. You can use this function to compare two strings or substrings of two strings.

What is the default search and comparison type for a string?

For a more detailed analysis of the default behavior of each String API, see the Default search and comparison types section. Ordinal (also known as non-linguistic) search and comparison decomposes a string into its individual char elements and performs a char-by-char search or comparison.

Do string comparison methods disregard embedded null characters?

Although string comparison methods disregard embedded null characters, string search methods such as String.Contains, String.EndsWith, String.IndexOf, String.LastIndexOf, and String.StartsWithdo not.

What is the best way to compare strings in a project?

When you develop with .NET, follow these simple recommendations when you use strings: Use overloads that explicitly specify the string comparison rules for string operations. Use StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase for comparisons as your safe default for culture-agnostic string matching.


2 Answers

If correct sorting is so important in your problem, just use ordinal string comparison instead of culture-sensitive. Only this one guarantees transitive and antisymmetric comparing you want.

What MSDN says:

Specifying the StringComparison.Ordinal or StringComparison.OrdinalIgnoreCase value in a method call signifies a non-linguistic comparison in which the features of natural languages are ignored. Methods that are invoked with these StringComparison values base string operation decisions on simple byte comparisons instead of casing or equivalence tables that are parameterized by culture. In most cases, this approach best fits the intended interpretation of strings while making code faster and more reliable.

And it works as expected:

    Console.WriteLine(String.Compare(x, y, StringComparison.Ordinal));  // -12309     Console.WriteLine(String.Compare(y, x, StringComparison.Ordinal));  // 12309 

Yes, it doesn't explain why culture-sensitive comparison gives inconsistent results. Well, strange culture — strange result.

like image 150
shuribot Avatar answered Oct 12 '22 06:10

shuribot


I came across this SO post, while I was trying to figure out why I was having problems retrieving (string) keys that were inserted into a SortedList, after I discovered the cause was the odd behaviour of the .Net 40 and above comparers (a1 < a2 and a2 < a3, but a1 > a3).

My struggle to figure out what was going on can be found here: c# SortedList<string, TValue>.ContainsKey for successfully added key returns false.

You may want to have a look at the "UPDATE 3" section of my SO question. It appears that the issue was reported to Microsoft in Dec 2012, and closed before the end of january 2013 as "won't be fixed". Additionally it lists a workaround that may be used.

I created an implementation of this recommended workaround, and verified that it fixed the problem that I had encountered. I also just verified that this resolves the issue you reported.

public static void SO_13254153_Question() {     string x = "\u002D\u30A2";  // or just "-ア" if charset allows     string y = "\u3042";        // or just "あ" if charset allows              var invariantComparer = new WorkAroundStringComparer();     var japaneseComparer = new WorkAroundStringComparer(new System.Globalization.CultureInfo("ja-JP", false));     Console.WriteLine(x.CompareTo(y));  // positive one     Console.WriteLine(y.CompareTo(x));  // positive one     Console.WriteLine(invariantComparer.Compare(x, y));  // negative one     Console.WriteLine(invariantComparer.Compare(y, x));  // positive one     Console.WriteLine(japaneseComparer.Compare(x, y));  // negative one     Console.WriteLine(japaneseComparer.Compare(y, x));  // positive one } 

The remaining problem is that this workaround is so slow it is hardly practical for use with large collections of strings. So I hope Microsoft will reconsider closing this issue or that someone knows of a better workaround.

like image 40
Alex Avatar answered Oct 12 '22 06:10

Alex