Comparing strings is subtler than it seemed to me at first glance. I was thinking more about IComparable<T>, IComparer<T>, and related issues, and I started looking at how the string class implements these interfaces. Interestingly, the simple idea of equality turns out to be not so simple.

Consider two strings:

string s1 = "\u00E1";
string s2 = "a\u0301";

These strings are different, right? I mean, the first one has one character, and the second two characters. And, s1.Equals(s2) returns False.

But wait! Things are not quite that simple. If you call s1.CompareTo(s2), it returns 0, indicating that the two strings are equal!

What is going on here? Let's look closer at those two strings. The first string has Unicode character E1, which is a lowercase letter a with an acute accent: “á”. The second string has a regular lowercase letter a, followed by Unicode character 301, which is the so-called “combining acute accent”. That character indicates that the previous letter should have an acute accent placed over it. So when you print out this string, it also looks like: “á”. In Unicode parlance, s1 is considered the “composed form”, and s2 is the “decomposed form”. (If you have time on your hands, you can learn much more about this by reading section 2.10 of the Unicode standard.)

So the upshot is that String.Compare considers composed and decomposed forms of accented characters to be equal, but String.Equals does not.

This becomes important when you start writing general algorithms that use the generic interface IComparable<T>, which contains two methods: Equals and CompareTo. You can't assume that if CompareTo returns 0, then Equals will return True, since String's implementation of IComparable<T> does not have this property. So when you are using IComparable<T>, you should always stick to one method or the other in your code: either always use Equals (if all you care about is equality), or always use CompareTo (if you care about ordering). Mixing the two can yield inconsistent results.

[IComparer<T> has the same issues, because the default IComparer for a type (Comparer<T>.Default) simply calles the IComparable<T> implementation on that type.]