意外と知らない?.NETの文字列比較の落とし穴

C#を使い始めて7,8年くらいたつけど、すごい初歩的なところでハマったので記事にします。
C言語に慣れている人ほどハマりやすいと思うorz

CとC#で簡単な文字列比較のプログラムを作ります。

/* strcmp.c */
#include <stdio.h>
#include <string.h>
void main(int argc, char* argv[]) {
	printf("%d", strcmp(argv[1], argv[2]));
}
// Compare.cs
using System;
class Compare {
	public static void Main(String[] args) {
		Console.WriteLine("{0}", String.Compare(args[0], args[1]));
	}
}

パラメータの1つ目と2つ目を文字列比較して結果を表示するだけです。

.NETの文字列比較は文字コード順じゃない!

実行結果をまとめるとこうなります。

引数1 引数2 Cの結果 C#の結果 つまり
a a 0 0 どちらも "a" == "a"
a b -1 -1 どちらも "a" < "b"
b a 1 1 どちらも "b" > "a"
a A 1 -1 Cだと "a" > "A"、C#(.NET)だと "a" < "A"

C標準ライブラリの文字列比較は文字コード順の比較です。"a"の文字コードは0x61、"A"の文字コードは0x41なので、"a" > "A" となります。
しかし.NETの文字列比較は単純な文字コード順ではなく、カルチャを考慮した順序のため、"a" < "A"となり、Cの結果と異なってしまいます。カルチャというのは国ごとの一般的な通貨や日付の表示方法をまとめたもので、要するにその国の一般的な感覚にあった順序になっています。幸いなことに日本だけというわけではなく、多くの国で "a" < "A" なようです。

文字コード順に比較するには?

C#文字コード順の比較をするには、String.Compareの代わりにString.CompareOrdinalを使います。合言葉はOrdinal。

引数1 引数2 Cの結果 C#(Ordinal)の結果 つまり
a A 1 32 どちらも "a" > "A"

結果が全然違うように見えますが、文字列比較メソッドは結果が0より大きいか小さいかで判定するのがお約束なので、これでいいんです。
他に、String.Compareの第3引数に StringComparison.Ordinal を指定するという方法もあります。

他の文字列比較は?

C#で文字列比較する方法はほかにもあります。

"a".CompareTo("A")
結果は-1。カルチャ考慮の結果になります。文字コード順にする方法はなさそう。
文字比較('a'<'A')
結果はFalse。Charの比較は文字コード順です。
Array.Sort
文字列配列の並び替えも内部で比較を行っていて、結果はカルチャ考慮になります。文字コード順にするには、引数に StringComparer.Ordinal を追加します。
LinqのOrderBy
結果はカルチャ考慮になります。文字コード順にするには、引数に StringComparer.Ordinal を追加します。クエリ構文は知らない。