ここではString.EqualsメソッドString.CompareToメソッドで指定することが出来る文字列比較のオプションと、その動作について詳しく見ていきます。 文字列比較のオプションを指定することで、大文字と小文字、全角と半角などといった違いをどう扱うか指定することが出来ます。

なお、このページで解説する比較操作に関連するインターフェイスについてや、コレクションとソートについて、カルチャと文字列操作については以下のページで詳しく解説しているので必要に応じて参照してください。

この文章では一部で特殊記号を使っています。 ブラウザで使用しているフォントによっては正しく表示されない文字があるかもしれません。

この文章では実行環境のロケール設定によって動作や結果が変わる場合のあるトピックを扱っています。 特に言及のない限りはja-JP(日本語/日本)の環境で実行した場合について解説しています。

StringComparison列挙型とStringComparerクラス

String.CompareメソッドやString.Equalsメソッドなどをはじめとして、.NET Frameworkでは文字列比較の際に比較オプションを指定することでその動作を変更することができます。

一例として、大文字と小文字の違いを無視して2つの文字列が等しいかどうか比較したい場合は次のようにします。

StringComparisonを指定して大文字と小文字の違いを無視して文字列比較を行うようにする
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = "FOO";

    // 通常の文字列比較
    // (s1とs2は等しくないと判断される)
    Console.WriteLine(String.Equals(s1, s2));

    // 大文字小文字の違いを無視して文字列を比較する
    // (s1とs2は等しいと判断される)
    Console.WriteLine(String.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase));
  }
}
StringComparisonを指定して大文字と小文字の違いを無視して文字列比較を行うようにする
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = "foo"
    Dim s2 As String = "FOO"

    ' 通常の文字列比較
    ' (s1とs2は等しくないと判断される)
    Console.WriteLine(String.Equals(s1, s2))

    ' 大文字小文字の違いを無視して文字列を比較する
    ' (s1とs2は等しいと判断される)
    Console.WriteLine(String.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase))
  End Sub
End Class
実行結果
False
True

上記の例ではStringComparison列挙型で文字列の比較オプションを指定しています。


また別の例として、大文字と小文字の違いを無視してキーを扱うDictionaryを作成したい場合は次のようにします。

StringComparerを指定してキーの大文字と小文字の違いを無視するDictionaryを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    // キーの大文字小文字の違いを無視するDictionaryを作成する
    var dict = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase);

    // キー"foo"に値を設定
    dict["foo"] = "bar";

    // キー"FOO"の値を参照
    Console.WriteLine(dict["FOO"]); // KeyNotFoundExceptionはスローされない
  }
}
StringComparerを指定してキーの大文字と小文字の違いを無視するDictionaryを作成する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    ' キーの大文字小文字の違いを無視するDictionaryを作成する
    Dim dict As New Dictionary(Of String, String)(StringComparer.InvariantCultureIgnoreCase)

    ' キー"foo"に値を設定
    dict("foo") = "bar"

    ' キー"FOO"の値を参照
    Console.WriteLine(dict("FOO")) ' KeyNotFoundExceptionはスローされない
  End Sub
End Class
実行結果
bar

上記の例ではStringComparerクラスを指定することでDictionaryのキー比較処理をカスタマイズしています。

このように、大文字小文字の無視の他にも特定カルチャに依存した(あるいは依存しない)並べ替え規則の適用など、目的に応じて比較オプションを選択することができます。

StringComparison列挙型

StringComparison列挙型は、文字列比較の際の動作を指定するための列挙型で、次のメソッドにおいて文字列比較を行う際にオプションとして指定出来ます。

また、StringComparisonを指定する場合は、次のいずれかを指定できます。

StringComparisonの値と文字列比較時の動作
StringComparisonの値 文字列比較時の動作
大文字小文字の違い 比較の規則
StringComparison.CurrentCulture 無視しない 現在のカルチャの並べ替え規則に基づいて比較する
StringComparison.CurrentCultureIgnoreCase 無視する
StringComparison.InvariantCulture 無視しない インバリアントカルチャの並べ替え規則に基づいて比較する
StringComparison.InvariantCultureIgnoreCase 無視する
StringComparison.Ordinal 無視しない 文字列の各文字を数値(Unicode序数、Unicode ordinal)=コードポイントで比較する
StringComparison.OrdinalIgnoreCase 無視する

StringComparisonを指定しない場合は、StringComparison.CurrentCultureを指定した場合と同じ動作になります。

以下はStringComparisonを指定して比較を行う例です。

StringComparisonを指定して文字列同士の比較を行う
using System;

class Sample {
  static void Main()
  {
    var s1 = "foo";
    var s2 = "FOO";

    // 以下の2つは同じ動作となる
    Console.WriteLine(s1.Equals(s2));
    Console.WriteLine(s1.Equals(s2, StringComparison.CurrentCulture));

    // 以下の2つは同じ動作となる
    Console.WriteLine(String.Equals(s1, s2));
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCulture));

    // 以下の2つは同じ動作となる
    Console.WriteLine(String.Compare(s1, s2));
    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCulture));

    Console.WriteLine("[IgnoreCase]");

    // 大文字小文字を無視する/しない比較
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCultureIgnoreCase));
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCulture));

    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCultureIgnoreCase));
    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCulture));
  }
}
StringComparisonを指定して文字列同士の比較を行う
Imports System

Class Sample
  Shared Sub Main()
    Dim s1 As String = "foo"
    Dim s2 As String = "FOO"

    ' 以下の2つは同じ動作となる
    Console.WriteLine(s1.Equals(s2))
    Console.WriteLine(s1.Equals(s2, StringComparison.CurrentCulture))

    ' 以下の2つは同じ動作となる
    Console.WriteLine(String.Equals(s1, s2))
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCulture))

    ' 以下の2つは同じ動作となる
    Console.WriteLine(String.Compare(s1, s2))
    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCulture))

    Console.WriteLine("[IgnoreCase]")

    ' 大文字小文字を無視する/しない比較
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCultureIgnoreCase))
    Console.WriteLine(String.Equals(s1, s2, StringComparison.CurrentCulture))

    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCultureIgnoreCase))
    Console.WriteLine(String.Compare(s1, s2, StringComparison.CurrentCulture))
  End Sub
End Class
実行結果
False
False
False
False
-1
-1
[IgnoreCase]
True
False
0
-1

それぞれの違いについての具体的な解説は§.CurrentCultureとInvariantCultureの違い§.CurrentCultureとOrdinalの違いにて、また使い分ける際の考え方については§.StringComparisonの使い分けで別途解説します。

全角と半角の違い・ひらがなとカタカナの違いを扱いたい場合はCompareOptions列挙型を使用することができます。

カルチャについてはカルチャの基本とカルチャ情報を、特にインバリアントカルチャについてはカルチャの基本とカルチャ情報 §.インバリアントカルチャを参照してください。

CurrentCultureとInvariantCultureの違い

CurrentCultureInvariantCultureは、ともに特定のカルチャでの規則に基づいた比較を行う点では同じですが、CurrentCultureでは現在のカルチャに基づいた比較を行うのに対し、InvariantCultureでは特定の文化圏や言語に依存しないインバリアントカルチャに基づいた比較を行う点が異なります。

以下はString.Compareメソッドの結果を用いてこれらの違いを見るためのサンプルです。

CurrentCultureとInvariantCultureの違い
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Console.WriteLine("Compare CurrentCulture   : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture));
    Console.WriteLine("Compare InvariantCulture : {0}", String.Compare("亜", "井", StringComparison.InvariantCulture));

    // 現在のスレッドのカルチャをen-US(英語/米国)に変更
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US");

    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Console.WriteLine("Compare CurrentCulture   : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture));
    Console.WriteLine("Compare InvariantCulture : {0}", String.Compare("亜", "井", StringComparison.InvariantCulture));
  }
}
CurrentCultureとInvariantCultureの違い
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Console.WriteLine("Compare CurrentCulture   : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture))
    Console.WriteLine("Compare InvariantCulture : {0}", String.Compare("亜", "井", StringComparison.InvariantCulture))

    ' 現在のスレッドのカルチャをen-US(英語/米国)に変更
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("en-US")

    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Console.WriteLine("Compare CurrentCulture   : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture))
    Console.WriteLine("Compare InvariantCulture : {0}", String.Compare("亜", "井", StringComparison.InvariantCulture))
  End Sub
End Class
実行結果
ja-JP
Compare CurrentCulture   : -1
Compare InvariantCulture : 1
en-US
Compare CurrentCulture   : 1
Compare InvariantCulture : 1

スレッドのカルチャをen-US(英語/米国)に変更する前後の結果に注目してください。 InvariantCultureでの結果はどちらも変わらないのに対し、CurrentCultureでの結果は、スレッドのカルチャを変更する前後で異なっています。

これは、ja-JP(日本語/日本)の規則では漢字の読みによる並びとなっているために(あ)の方が(い)より小さい(前に並ぶ)、つまり"<"とされるのに対し、en-US(英語/米国)の規則では文字のコードポイントによる並びとなっているために、(U+4E95)の方が(U+4E9C)よりも小さい(前に並ぶ)、つまり">"とされるためです。 そのため、CurrentCultureによる比較の結果は、カルチャがja-JPとen-USの場合で異なります。

一方、InvariantCultureの場合は特定の言語に依存しない規則に基づいて比較されるため、カルチャがja-JPでもen-USでもの方がよりも小さいというどちらも同じ結果となります。 (参考: 固有カルチャのデータの比較と並べ替え)

CurrentCultureIgnoreCaseInvariantCultureIgnoreCaseの場合もこれと同様の動作となります。

カルチャ情報についての詳細やカルチャ情報の取得・変更に関してはカルチャの基本とカルチャ情報で詳しく解説しています。


続いて、CurrentCultureIgnoreCaseInvariantCultureIgnoreCaseでも違いが現れる例を見てみます。

CurrentCultureIgnoreCaseとInvariantCultureIgnoreCaseで異なる結果となる例
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Console.WriteLine("Compare CurrentCulture             : {0}", String.Compare("i", "I", StringComparison.CurrentCulture));
    Console.WriteLine("Compare CurrentCultureIgnoreCase   : {0}", String.Compare("i", "I", StringComparison.CurrentCultureIgnoreCase));
    Console.WriteLine("Compare InvariantCulture           : {0}", String.Compare("i", "I", StringComparison.InvariantCulture));
    Console.WriteLine("Compare InvariantCultureIgnoreCase : {0}", String.Compare("i", "I", StringComparison.InvariantCultureIgnoreCase));

    Console.WriteLine("{0} => {1}", "i", "i".ToUpper());
    Console.WriteLine("{0} => {1}", "I", "I".ToLower());

    // 現在のスレッドのカルチャをtr-TR(トルコ語/トルコ)に変更
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR");

    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Console.WriteLine("Compare CurrentCulture             : {0}", String.Compare("i", "I", StringComparison.CurrentCulture));
    Console.WriteLine("Compare CurrentCultureIgnoreCase   : {0}", String.Compare("i", "I", StringComparison.CurrentCultureIgnoreCase));
    Console.WriteLine("Compare InvariantCulture           : {0}", String.Compare("i", "I", StringComparison.InvariantCulture));
    Console.WriteLine("Compare InvariantCultureIgnoreCase : {0}", String.Compare("i", "I", StringComparison.InvariantCultureIgnoreCase));

    Console.WriteLine("{0} => {1}", "i", "i".ToUpper());
    Console.WriteLine("{0} => {1}", "I", "I".ToLower());
  }
}
CurrentCultureIgnoreCaseとInvariantCultureIgnoreCaseで異なる結果となる例
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Console.WriteLine("Compare CurrentCulture             : {0}", String.Compare("i", "I", StringComparison.CurrentCulture))
    Console.WriteLine("Compare CurrentCultureIgnoreCase   : {0}", String.Compare("i", "I", StringComparison.CurrentCultureIgnoreCase))
    Console.WriteLine("Compare InvariantCulture           : {0}", String.Compare("i", "I", StringComparison.InvariantCulture))
    Console.WriteLine("Compare InvariantCultureIgnoreCase : {0}", String.Compare("i", "I", StringComparison.InvariantCultureIgnoreCase))

    Console.WriteLine("{0} => {1}", "i", "i".ToUpper())
    Console.WriteLine("{0} => {1}", "I", "I".ToLower())

    ' 現在のスレッドのカルチャをtr-TR(トルコ語/トルコ)に変更
    Thread.CurrentThread.CurrentCulture = CultureInfo.GetCultureInfo("tr-TR")

    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Console.WriteLine("Compare CurrentCulture             : {0}", String.Compare("i", "I", StringComparison.CurrentCulture))
    Console.WriteLine("Compare CurrentCultureIgnoreCase   : {0}", String.Compare("i", "I", StringComparison.CurrentCultureIgnoreCase))
    Console.WriteLine("Compare InvariantCulture           : {0}", String.Compare("i", "I", StringComparison.InvariantCulture))
    Console.WriteLine("Compare InvariantCultureIgnoreCase : {0}", String.Compare("i", "I", StringComparison.InvariantCultureIgnoreCase))

    Console.WriteLine("{0} => {1}", "i", "i".ToUpper())
    Console.WriteLine("{0} => {1}", "I", "I".ToLower())
  End Sub
End Class
実行結果
ja-JP
Compare CurrentCulture             : -1
Compare CurrentCultureIgnoreCase   : 0
Compare InvariantCulture           : -1
Compare InvariantCultureIgnoreCase : 0
i => I
I => i
tr-TR
Compare CurrentCulture             : 1
Compare CurrentCultureIgnoreCase   : 1
Compare InvariantCulture           : -1
Compare InvariantCultureIgnoreCase : 0
i => İ
I => ı

トルコ語においては、i(U+0069)とI(U+0049)は大文字小文字の関係にある同じ文字ではなくそれぞれ発音の異なる文字であり、iの大文字はI(U+0049)ではなくİ(U+0131)で、Iの小文字はi(U+0069)ではなくı(U+0130)となります。

そのため、ja-JP(日本語/日本)の場合はCurrentCultureIgnoreCaseで大文字小文字を無視するとi(U+0069)とI(U+0049)は同じ文字として扱われ、tr-TR(トルコ語/トルコ)ではiIは大文字小文字を無視しても異なる文字として扱われます。 一方InvariantCultureIgnoreCaseの場合は、特定の言語に依存しない規則に基づいて比較されるため、ja-JPでもtr-TRでも大文字小文字を無視するとi(U+0069)とI(U+0049)は同じ文字として扱われます。

このように、特定の言語や文化圏に依存した規則に基づいて比較を行いたい場合はCurrentCulture/CurrentCultureIgnoreCase、特定の言語や文化圏に依存しない規則に基づいて比較を行いたい場合はInvariantCulture/InvariantCultureIgnoreCaseを使用する必要があります。

CurrentCultureとOrdinalの違い

CurrentCultureInvariantCultureは特定のカルチャでの規則に基づいて比較を行いますが、一方Ordinalではカルチャに依存せず、文字列の各文字を数値(Unicode序数、Unicode ordinal)、つまりコードポイントで比較します。

CurrentCultureとOrdinalの違い・漢字の扱い
using System;

class Sample {
  static void Main()
  {
    Console.WriteLine("Compare CurrentCulture : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture));
    Console.WriteLine("Compare Ordnal         : {0}", String.Compare("亜", "井", StringComparison.Ordinal));
  }
}
CurrentCultureとOrdinalの違い・漢字の扱い
Imports System

Class Sample
  Shared Sub Main()
    Console.WriteLine("Compare CurrentCulture : {0}", String.Compare("亜", "井", StringComparison.CurrentCulture))
    Console.WriteLine("Compare Ordnal         : {0}", String.Compare("亜", "井", StringComparison.Ordinal))
  End Sub
End Class
実行結果
Compare CurrentCulture : -1
Compare Ordnal         : 7

CurrentCultureの場合は(あ)の方が(い)より小さい(前に並ぶ)、つまり"<"とされます。

対してOrdnalの場合は、(U+4E95)の方が(U+4E9C)よりも小さい(前に並ぶ)、つまり">"とされるため、上記のような結果となります。 (0x4E9C - 0x4E95 = 0x0007 = 7である点に注目するとわかりやすいかもしれません)

また、次のような例をとって違いを見てみます。

CurrentCultureとOrdinalの違い・記号の扱い
using System;

class Sample {
  static void Main()
  {
    var p1 = new string[] {"coop", "co-op"};
    var p2 = new string[] {"cant", "can't"};

    Console.WriteLine("Compare CurrentCulture ({0}, {1}) : {2}", p1[0], p1[1], String.Compare(p1[0], p1[1], StringComparison.CurrentCulture));
    Console.WriteLine("Compare Ordinal        ({0}, {1}) : {2}", p1[0], p1[1], String.Compare(p1[0], p1[1], StringComparison.Ordinal));

    Console.WriteLine("Compare CurrentCulture ({0}, {1}) : {2}", p2[0], p2[1], String.Compare(p2[0], p2[1], StringComparison.CurrentCulture));
    Console.WriteLine("Compare Ordinal        ({0}, {1}) : {2}", p2[0], p2[1], String.Compare(p2[0], p2[1], StringComparison.Ordinal));
  }
}
CurrentCultureとOrdinalの違い・記号の扱い
Imports System

Class Sample
  Shared Sub Main()
    Dim p1 As String() = New String() {"coop", "co-op"}
    Dim p2 As String() = New String() {"cant", "can't"}

    Console.WriteLine("Compare CurrentCulture ({0}, {1}) : {2}", p1(0), p1(1), String.Compare(p1(0), p1(1), StringComparison.CurrentCulture))
    Console.WriteLine("Compare Ordinal        ({0}, {1}) : {2}", p1(0), p1(1), String.Compare(p1(0), p1(1), StringComparison.Ordinal))

    Console.WriteLine("Compare CurrentCulture ({0}, {1}) : {2}", p2(0), p2(1), String.Compare(p2(0), p2(1), StringComparison.CurrentCulture))
    Console.WriteLine("Compare Ordinal        ({0}, {1}) : {2}", p2(0), p2(1), String.Compare(p2(0), p2(1), StringComparison.Ordinal))
  End Sub
End Class
実行結果
Compare CurrentCulture (coop, co-op) : -1
Compare Ordinal        (coop, co-op) : 66
Compare CurrentCulture (cant, can't) : -1
Compare Ordinal        (cant, can't) : 77

CurrentCultureの場合は辞書的な並びでの比較となり、"coop"は"co-op"よりも前、"cant"は"can't"よりも前、つまり"coop<co-op"、"cant<can't"という結果になります。

対してOrdinalの場合は、"coop"と"co-op"の3文字目を比較するとo(U+006F)と-(U+002D)であるため"coop>co-op"という結果になります。 同様に、"cant"と"can't"の3文字目はt(U+0074)と'(U+0027)であるため"cant>can't"という結果になります。 (この結果も、0x006F - 0x002D = 0x0042 = 66、0x0074-0x0027 = 0x004D = 77である点に注目するとよりわかりやすいかもしれません)

CurrentCultureIgnoreCaseOrdinalIgnoreCaseの場合もこれと同様の動作となります。

StringComparisonの使い分け

StringComparisonの各値の動作を再掲すると次のようになっています。

StringComparisonの値と文字列比較時の動作
StringComparisonの値 文字列比較時の動作
CurrentCulture
CurrentCultureIgnoreCase
現在のカルチャの並べ替え規則に基づいて比較する
InvariantCulture
InvariantCultureIgnoreCase
インバリアントカルチャの並べ替え規則に基づいて比較する
Ordinal
OrdinalIgnoreCase
文字列の各文字を数値(Unicode序数、Unicode ordinal)=コードポイントで比較する

CurrentCultureは設定されているカルチャによって結果が変わる可能性がありますが、その結果はカルチャに合わせた適切な文字列比較動作となっています。 したがって、UIで表示されるような文字列に対して使用するのが想定される主な適用範囲となります。 例えば、言語に合わせた表示順となるようにしたいといった場合は、CurrentCultureが適していると考えられます。

一方、InvariantCultureOrdinalはカルチャによらず一定の結果となります。 したがって、実行環境によって結果が変わることが望ましくない部分、例えば内部のソートや比較処理、ユーザーが直接編集しないファイルの入出力など、ロジック部分での文字列操作が想定される主な適用範囲となります。 CurrentCultureではなくInvariantCultureが適している場合についてはカルチャの基本とカルチャ情報 §.インバリアントカルチャも参照してください。

CurrentCultureInvariantCultureでは、各文字の持つ意味的な側面(句読点や記号の扱い、アクセント記号の有無など)を考慮した比較となるため、あいまいな比較(あるいは自然な比較)でも構わない場合はこちらを使うことができます。 一方、バイト単位での比較のように、各文字の持つ意味的な側面は排除した上で、すべての文字を単なる数値ととらえ、文字種や言語環境の違いに関わらず等しく扱いたい場合はOrdinalを使うのが望ましいと言えます。

StringComparerクラス

StringComparerクラスは文字列比較の実装を提供するクラスです。 IComparer<string>IEqualityComparer<string>を実装しているため、これらのインターフェイスを使った文字列比較を行う際は、このクラスのインスタンスを使うことが出来ます。 また、StringComparisonと対応する実装を提供する静的プロパティも用意されています。

StringComparerのプロパティとStringComparisonの対応
StringComparerのプロパティ 同等なStringComparison
StringComparer.CurrentCulture StringComparison.CurrentCulture
StringComparer.CurrentCultureIgnoreCase StringComparison.CurrentCultureIgnoreCase
StringComparer.InvariantCulture StringComparison.InvariantCulture
StringComparer.InvariantCultureIgnoreCase StringComparison.InvariantCultureIgnoreCase
StringComparer.Ordinal StringComparison.Ordinal
StringComparer.OrdinalIgnoreCase StringComparison.OrdinalIgnoreCase

IComparer<T>インターフェイスの詳細や使用例・実装例については大小関係の定義と比較、IEqualityComparer<T>インターフェイスについては等価性の定義と比較を参照してください。

StringComparerとソート

例として、StringComparerとList<string>.Sortメソッドを使って、その結果の違いを見てみます。 List<string.Sortメソッドは並べ替え時の比較を行うためのIComparer<string>を引数にとるため、StringComparerを使用することが出来ます。

StringComparerを指定してListをソートする際の文字列比較順を指定する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var words = new List<string>() {"cant", "can't", "can not", "cannot"};

    Console.WriteLine(String.Join(", ", words.ToArray()));

    words.Sort(StringComparer.CurrentCulture); // CurrentCultureの比較順でソートする

    Console.WriteLine(String.Join(", ", words.ToArray()));

    words.Sort(StringComparer.Ordinal); // Ordinalの比較順でソートする

    Console.WriteLine(String.Join(", ", words.ToArray()));
  }
}
StringComparerを指定してListをソートする際の文字列比較順を指定する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim words As New List(Of String)

    words.AddRange(New String() {"cant", "can't", "can not", "cannot"})

    Console.WriteLine(String.Join(", ", words.ToArray()))

    words.Sort(StringComparer.CurrentCulture) ' CurrentCultureの比較順でソートする

    Console.WriteLine(String.Join(", ", words.ToArray()))

    words.Sort(StringComparer.Ordinal) ' Ordinalの比較順でソートする

    Console.WriteLine(String.Join(", ", words.ToArray()))
  End Sub
End Class
実行結果
cant, can't, can not, cannot
can not, cannot, cant, can't
can not, can't, cannot, cant

Listとソート順の定義については基本型のソートと昇順・降順でのソートを参照してください。


Dictionary<string, T>では、コンストラクタでキーの比較を行うためのIEqualityComparer<string>を指定することが出来るため、StringComparerを指定することでキーの大文字小文字を無視するディクショナリを作成することも出来ます。

StringComparerを指定してキーの大文字と小文字の違いを無視する・無視しないDictionaryを作成する
using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    var dict1 = new Dictionary<string, string>(StringComparer.InvariantCulture); // キーの大文字小文字を無視しない
    var dict2 = new Dictionary<string, string>(StringComparer.InvariantCultureIgnoreCase); // キーの大文字小文字を無視する

    dict1["foo"] = "bar";
    dict1["Foo"] = "Bar";
    dict1["FOO"] = "BAR";

    dict2["foo"] = "bar";
    dict2["Foo"] = "Bar";
    dict2["FOO"] = "BAR";

    Console.WriteLine("dict1");

    foreach (KeyValuePair<string, string> p in dict1) {
      Console.WriteLine("{0} = {1}", p.Key, p.Value);
    }

    Console.WriteLine("dict2");

    foreach (KeyValuePair<string, string> p in dict2) {
      Console.WriteLine("{0} = {1}", p.Key, p.Value);
    }
  }
}
StringComparerを指定してキーの大文字と小文字の違いを無視する・無視しないDictionaryを作成する
Imports System
Imports System.Collections.Generic

Class Sample
  Shared Sub Main()
    Dim dict1 As New Dictionary(Of String, String)(StringComparer.InvariantCulture)
    Dim dict2 As New Dictionary(Of String, String)(StringComparer.InvariantCultureIgnoreCase)

    dict1("foo") = "bar"
    dict1("Foo") = "Bar"
    dict1("FOO") = "BAR"

    dict2("foo") = "bar"
    dict2("Foo") = "Bar"
    dict2("FOO") = "BAR"

    Console.WriteLine("dict1")

    For Each p As KeyValuePair(Of String, String) In dict1
      Console.WriteLine("{0} = {1}", p.Key, p.Value)
    Next

    Console.WriteLine("dict2")

    For Each p As KeyValuePair(Of String, String) In dict2
      Console.WriteLine("{0} = {1}", p.Key, p.Value)
    Next
  End Sub
End Class
実行結果
dict1
foo = bar
Foo = Bar
FOO = BAR
dict2
foo = BAR

Array.Sortメソッドも、ソート時の比較処理をIComparerで指定することが出来ます。 次のコードは、CurrentCultureとOrdinalでアラビア数字・漢数字・囲み文字を並べ替えたときの違いを出力する例です。

StringComparerを指定して丸囲み・括弧付きのアラビア数字・漢数字をソートする
using System;

class Sample {
  static void Main()
  {
    string[] numbers = new string[] {
      // 半角アラビア数字
      "1", "2", "3", "4", "5", "6", "7", "8", "9", "10",
      // 丸囲みアラビア数字
      "①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩",
      // 括弧付きアラビア数字
      "⑴", "⑵", "⑶", "⑷", "⑸", "⑹", "⑺", "⑻", "⑼", "⑽",
      // 終止符付きアラビア数字
      "⒈", "⒉", "⒊", "⒋", "⒌", "⒍", "⒎", "⒏", "⒐", "⒑",
      // 漢数字
      "一", "二", "三", "四", "五", "六", "七", "八", "九", "十",
      // 丸囲み漢数字
      "㊀", "㊁", "㊂", "㊃", "㊄", "㊅", "㊆", "㊇", "㊈", "㊉",
      // 括弧付き漢数字
      "㈠", "㈡", "㈢", "㈣", "㈤", "㈥", "㈦", "㈧", "㈨", "㈩",
    };

    Console.WriteLine("StringComparer.CurrentCulture");

    Array.Sort(numbers, StringComparer.CurrentCulture);

    for (var i = 0; i < numbers.Length; i++) {
      Console.Write("{0} ", numbers[i]);

      if (i % 20 == 19)
        Console.WriteLine();
    }

    Console.WriteLine();

    Console.WriteLine("StringComparer.Ordinal");

    Array.Sort(numbers, StringComparer.Ordinal);

    for (var i = 0; i < numbers.Length; i++) {
      Console.Write("{0} ", numbers[i]);

      if (i % 20 == 19)
        Console.WriteLine();
    }

    Console.WriteLine();
  }
}
実行結果
StringComparer.CurrentCulture
1 ① ⑴ ⒈ 10 2 ② ⑵ ⒉ 3 ③ ⑶ ⒊ 4 ④ ⑷ ⒋ 5 ⑤ ⑸ 
⒌ 6 ⑥ ⑹ ⒍ 7 ⑦ ⑺ ⒎ 8 ⑧ ⑻ ⒏ 9 ⑨ ⑼ ⒐ ⑩ ⑽ ⒑ 
一 ㈠ ㊀ 九 ㈨ ㊈ 五 ㈤ ㊄ 三 ㈢ ㊂ 四 ㈣ ㊃ 七 ㈦ ㊆ 十 ㈩ 
㊉ 二 ㈡ ㊁ 八 ㈧ ㊇ 六 ㈥ ㊅ 
StringComparer.Ordinal
1 10 2 3 4 5 6 7 8 9 ① ② ③ ④ ⑤ ⑥ ⑦ ⑧ ⑨ ⑩ 
⑴ ⑵ ⑶ ⑷ ⑸ ⑹ ⑺ ⑻ ⑼ ⑽ ⒈ ⒉ ⒊ ⒋ ⒌ ⒍ ⒎ ⒏ ⒐ ⒑ 
㈠ ㈡ ㈢ ㈣ ㈤ ㈥ ㈦ ㈧ ㈨ ㈩ ㊀ ㊁ ㊂ ㊃ ㊄ ㊅ ㊆ ㊇ ㊈ ㊉ 
一 七 三 九 二 五 八 六 十 四 

CurrentCultureでは、アラビア数字は数字の大きさ、漢数字は漢字の読みに従って並べ替えられ、Ordinalではコードポイントに従って並べ替えられていることが分かると思います。

CompareOptions列挙型

CompareOptions列挙型も、StringComparisonと同じく文字列比較の際の動作を指定するための列挙型ですが、StringComparisonよりも具体的な指定を行うことが出来ます。 StringComparisonの場合と異なり、StringクラスではCompareメソッドのみがCompareOptionsを指定した比較をサポートしています。

以下はString.CompareメソッドにCompareOptionsを指定して文字列の比較を行う例です。

CompareOptionsを指定して文字列の比較を行う
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine("CompareOptions.None       : {0}", String.Compare("abc", "ABC", Thread.CurrentThread.CurrentCulture, CompareOptions.None));
    Console.WriteLine("CompareOptions.IgnoreCase : {0}", String.Compare("abc", "ABC", Thread.CurrentThread.CurrentCulture, CompareOptions.IgnoreCase));
  }
}
CompareOptionsを指定して文字列の比較を行う
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine("CompareOptions.None       : {0}", String.Compare("abc", "ABC", Thread.CurrentThread.CurrentCulture, CompareOptions.None))
    Console.WriteLine("CompareOptions.IgnoreCase : {0}", String.Compare("abc", "ABC", Thread.CurrentThread.CurrentCulture, CompareOptions.IgnoreCase))
  End Sub
End Class
実行結果
CompareOptions.None       : -1
CompareOptions.IgnoreCase : 0

CompareOptionsを指定する場合は、次の値を組み合わせて指定することができます。

CompareOptions
CompareOptionsの値 文字列比較時の動作 解説
None デフォルトの動作で比較する -
IgnoreCase 大文字小文字の違いを無視して比較する 解説へ
IgnoreKanaType ひらがなとカタカナの違いを無視して比較する
IgnoreWidth 全角と半角の違いを無視して比較する
IgnoreNonSpace 濁点付きの文字、囲み文字や発音記号など、基本となる文字と修飾記号の組み合わせになる文字について、非スペーシング組み合わせ文字となる修飾記号を無視して比較する
(例として、"えと""えど""cafe""café""1""①"は同じ文字列として扱われる)
解説へ
IgnoreSymbols 空白文字・句読点・その他の記号を無視して比較する
(例として、"Hello, world!""Helloworld"は同じ文字列として扱われる)
解説へ
StringSort ハイフン・アポストロフィが英数字や他の記号よりも前になるように比較する
(例として、"can't""cant"より小さいとして扱われる)
解説へ
Ordinal 文字列の各文字を数値(Unicode序数、Unicode ordinal)=コードポイントで比較する (StringComparison.Ordinalと同じ)
OrdinalIgnoreCase 文字列の各文字を数値(Unicode序数、Unicode ordinal)=コードポイントで比較する
大文字小文字の違いは無視する
(StringComparison.OrdinalIgnoreCaseと同じ)

個々のオプションと動作の詳細については追って解説します。 なお、OrdinalおよびOrdinalIgnoreCaseは他の値とは組み合わせることはできないため、単独で使用する必要があります。

ソートやDictionaryのキー比較にCompareOptionsを使用する方法については§.CompareOptionsに対応するStringComparerの取得で追って解説します。

CompareOptionsとCultureInfo

String.CompareメソッドでCompareOptionsを指定する場合、必ずCultureInfoも指定する必要があります。 引数で指定されたCultureInfoでの定義にしたがった比較に加えてCompareOptionsで指定した比較オプションが適用されるため、例えCompareOptionsが同じでもCultureInfoが異なれば結果も異なる場合があります。

なお、OrdinalとOrdinalIgnoreCaseはCultureInfoでの定義ではなくコードポイントでの比較が行われるため、指定したCultureInfoによらず結果は一定です。

一方、StringComparisonでは現在のカルチャまたはインバリアントカルチャのいずれかのみが指定できますが、String.CompareメソッドでCompareOptionsを指定する場合ではインバリアントカルチャを含む任意のカルチャを指定することができます。

CompareOptionsとCultureInfoの組み合わせとStringComparisonの対応については§.CompareOptionsとStringComparisonで解説します。

CultureInfoクラスおよびカルチャ情報についてはカルチャの基本とカルチャ情報を参照してください。

CompareOptionsの値と文字列比較の動作

IgnoreCase, IgnoreKanaType, IgnoreWidth

CompareOptionsのうち、IgnoreCase, IgnoreKanaType, IgnoreWidthはそれぞれ大文字小文字、ひらがなとカタカナ、全角と半角を区別しないで比較するように指定します。 これらの値や他の値と組み合わせて使用することも出来ます。

CompareOptions.IgnoreCase・IgnoreKanaType・IgnoreWidthを指定して大文字小文字・ひらがなカタカナ・全角半角を区別せずに比較する
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Compare("abc", "ABC", CompareOptions.None);
    Compare("abc", "ABC", CompareOptions.IgnoreCase);
    Compare("abc", "abc", CompareOptions.IgnoreWidth);
    Compare("abc", "ABC", CompareOptions.IgnoreCase);
    Compare("abc", "ABC", CompareOptions.IgnoreCase | CompareOptions.IgnoreWidth);

    Console.WriteLine();

    Compare("あいうえお", "アイウエオ", CompareOptions.None);
    Compare("あいうえお", "アイウエオ", CompareOptions.IgnoreKanaType);
    Compare("アイウエオ", "アイウエオ", CompareOptions.None);
    Compare("アイウエオ", "アイウエオ", CompareOptions.IgnoreWidth);
    Compare("アイウエオ", "あいうえお", CompareOptions.IgnoreKanaType);
    Compare("アイウエオ", "あいうえお", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth);
  }

  private static void Compare(string s1, string s2, CompareOptions options)
  {
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options);
  }
}
CompareOptions.IgnoreCase・IgnoreKanaType・IgnoreWidthを指定して大文字小文字・ひらがなカタカナ・全角半角を区別せずに比較する
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Compare("abc", "ABC", CompareOptions.None)
    Compare("abc", "ABC", CompareOptions.IgnoreCase)
    Compare("abc", "abc", CompareOptions.IgnoreWidth)
    Compare("abc", "ABC", CompareOptions.IgnoreCase)
    Compare("abc", "ABC", CompareOptions.IgnoreCase Or CompareOptions.IgnoreWidth)

    Console.WriteLine()

    Compare("あいうえお", "アイウエオ", CompareOptions.None)
    Compare("あいうえお", "アイウエオ", CompareOptions.IgnoreKanaType)
    Compare("アイウエオ", "アイウエオ", CompareOptions.None)
    Compare("アイウエオ", "アイウエオ", CompareOptions.IgnoreWidth)
    Compare("アイウエオ", "あいうえお", CompareOptions.IgnoreKanaType)
    Compare("アイウエオ", "あいうえお", CompareOptions.IgnoreKanaType Or CompareOptions.IgnoreWidth)
  End Sub

  Private Shared Sub Compare(ByVal s1 As String, ByVal s2 As String, ByVal options As CompareOptions)
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options)
  End Sub
End Class
実行結果
ja-JP
abc ABC : -1 (None)
abc ABC :  0 (IgnoreCase)
abc abc :  0 (IgnoreWidth)
abc ABC : -1 (IgnoreCase)
abc ABC :  0 (IgnoreCase, IgnoreWidth)

あいうえお アイウエオ :  1 (None)
あいうえお アイウエオ :  0 (IgnoreKanaType)
アイウエオ アイウエオ : -1 (None)
アイウエオ アイウエオ :  0 (IgnoreWidth)
アイウエオ あいうえお : -1 (IgnoreKanaType)
アイウエオ あいうえお :  0 (IgnoreKanaType, IgnoreWidth)

IgnoreNonSpace

CompareOptionsのうち、IgnoreNonSpaceは濁点付きの文字、囲み文字や発音記号など、基本となる文字と修飾記号の組み合わせになる文字について、その組み合わせを無視して比較するように指定します。 これらの値や他の値と組み合わせて使用することも出来ます。 CompareOptions.IgnoreNonSpaceの解説では、

文字列比較で、発音区別符など、非スペーシング組み合わせ文字を無視するように指定します。 Unicode 標準は、新しい文字を生成するために基本文字と組み合わせられる文字を組み合わせ文字として定義しています。非スペーシング組み合わせ文字は、表示されるときに文字間隔用の領域は確保しません。

CompareOptions 列挙体

とされています。 非スペーシング組み合わせ文字とは、Unicodeのカテゴリ'Mark, Nonspacing' [Mn]のことで、これに該当する文字は比較の際に無視されることになります。

まずは、具体的な例と比較結果を見てみます。

CompareOptions.IgnoreNonSpaceを指定して濁点半濁点・囲み文字・修飾記号を無視して比較する
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Compare("cafe", "café", CompareOptions.None);
    Compare("cafe", "café", CompareOptions.IgnoreNonSpace);

    Compare("cat", "cæt", CompareOptions.None);
    Compare("cat", "cæt", CompareOptions.IgnoreNonSpace);
    Compare("caet", "cæt", CompareOptions.None);
    Compare("caet", "cæt", CompareOptions.IgnoreNonSpace);
    Compare("caet", "cæt", CompareOptions.Ordinal);

    Console.WriteLine();

    Compare("えと", "えど", CompareOptions.None);
    Compare("えと", "えど", CompareOptions.IgnoreNonSpace);
    Compare("ハン", "パン", CompareOptions.None);
    Compare("ハン", "パン", CompareOptions.IgnoreNonSpace);
    Compare("ウ", "ヴ", CompareOptions.None);
    Compare("ウ", "ヴ", CompareOptions.IgnoreNonSpace);

    Console.WriteLine();

    Compare("バ゙ン", "パン", CompareOptions.None);
    Compare("◎゛", "●", CompareOptions.None);

    Console.WriteLine();

    Compare("C", "©", CompareOptions.None);
    Compare("C", "©", CompareOptions.IgnoreNonSpace);
    Compare("C", "Ⓒ", CompareOptions.None);
    Compare("C", "Ⓒ", CompareOptions.IgnoreNonSpace);

    Console.WriteLine();

    Compare("1", "①", CompareOptions.None);
    Compare("1", "①", CompareOptions.IgnoreNonSpace);
    Compare("1", "①", CompareOptions.IgnoreNonSpace);
    Compare("1", "①", CompareOptions.IgnoreNonSpace | CompareOptions.IgnoreWidth);
  }

  private static void Compare(string s1, string s2, CompareOptions options)
  {
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options);
  }
}
CompareOptions.IgnoreNonSpaceを指定して濁点半濁点・囲み文字・修飾記号を無視して比較する
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Compare("cafe", "café", CompareOptions.None)
    Compare("cafe", "café", CompareOptions.IgnoreNonSpace)

    Compare("cat", "cæt", CompareOptions.None)
    Compare("cat", "cæt", CompareOptions.IgnoreNonSpace)
    Compare("caet", "cæt", CompareOptions.None)
    Compare("caet", "cæt", CompareOptions.IgnoreNonSpace)
    Compare("caet", "cæt", CompareOptions.Ordinal)

    Console.WriteLine()

    Compare("えと", "えど", CompareOptions.None)
    Compare("えと", "えど", CompareOptions.IgnoreNonSpace)
    Compare("ハン", "パン", CompareOptions.None)
    Compare("ハン", "パン", CompareOptions.IgnoreNonSpace)
    Compare("ウ", "ヴ", CompareOptions.None)
    Compare("ウ", "ヴ", CompareOptions.IgnoreNonSpace)

    Console.WriteLine()

    Compare("バ゙ン", "パン", CompareOptions.None)
    Compare("◎゛", "●", CompareOptions.None)

    Console.WriteLine()

    Compare("C", "©", CompareOptions.None)
    Compare("C", "©", CompareOptions.IgnoreNonSpace)
    Compare("C", "Ⓒ", CompareOptions.None)
    Compare("C", "Ⓒ", CompareOptions.IgnoreNonSpace)

    Console.WriteLine()

    Compare("1", "①", CompareOptions.None)
    Compare("1", "①", CompareOptions.IgnoreNonSpace)
    Compare("1", "①", CompareOptions.IgnoreNonSpace)
    Compare("1", "①", CompareOptions.IgnoreNonSpace Or CompareOptions.IgnoreWidth)
  End Sub

  Private Shared Sub Compare(ByVal s1 As String, ByVal s2 As String, ByVal options As CompareOptions)
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options)
  End Sub
End Class
実行結果
ja-JP
cafe café : -1 (None)
cafe café :  0 (IgnoreNonSpace)
cat cæt :  1 (None)
cat cæt :  1 (IgnoreNonSpace)
caet cæt :  0 (None)
caet cæt : -133 (Ordinal)

えと えど : -1 (None)
えと えど :  0 (IgnoreNonSpace)
ハン パン : -1 (None)
ハン パン :  0 (IgnoreNonSpace)
ウ ヴ : -1 (None)
ウ ヴ :  0 (IgnoreNonSpace)

バ゙ン パン :  0 (None)
◎゛ ● :  0 (None)

C © :  1 (None)
C © :  1 (IgnoreNonSpace)
C Ⓒ : -1 (None)
C Ⓒ :  0 (IgnoreNonSpace)

1 ① : -1 (None)
1 ① :  0 (IgnoreNonSpace)
1 ① :  1 (IgnoreNonSpace)
1 ① :  0 (IgnoreNonSpace, IgnoreWidth)

個々の実行結果について、詳しく説明します。

"cafe"と"café"
é(U+00E9 LATIN SMALL LETTER E WITH ACUTE)はe(U+0065 LATIN SMALL LETTER E)と́(U+0301 COMBINING ACUTE ACCENT)の組み合わせと等価とされています。
IgnoreNonSpaceの場合、カテゴリが'Mark, Nonspacing'である́(U+0301 COMBINING ACUTE ACCENT)が無視され、e(U+0065 LATIN SMALL LETTER E)として比較されるため、"cafe"と"café"は同じであると判断されます。
"cat"と"cæt"
æ(U+00E6 LATIN SMALL LETTER AE)は組み合わせた文字としては扱われないため、IgnoreNonSpaceの場合でも"cat"と"cæt"は異なると判断されます。
"caet"と"cæt"
インバリアントカルチャおよびja-JPでは"ae"と"æ"は同等であると判断されるため、NoneでもIgnoreNonSpaceでも"caet"と"cæt"は同じであると判断されます。
Ordinalの場合、a(U+0061 LATIN SMALL LETTER A)とæ(U+00E6 LATIN SMALL LETTER AE)はコードポイントが異なるため、"caet"と"cæt"は異なると判断されます。
"えと"と"えど"
(U+3069 HIRAGANA LETTER DO)は(U+3068 HIRAGANA LETTER TO)と(U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)の組み合わせと等価とされています。
IgnoreNonSpaceの場合、カテゴリが'Mark, Nonspacing'である(U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)が無視され、(U+3068 HIRAGANA LETTER TO)として比較されるため、"えと"と"えど"は同じであると判断されます。
"ハン"と"パン"
"ウ"と"ヴ"
(U+30D1 KATAKANA LETTER PA)は(U+30CF KATAKANA LETTER HA)と(U+309A COMBINING KATAKANA-HIRAGANA SEMI-VOICED SOUND MARK)の組み合わせ、(U+30F4 KATAKANA LETTER VU)は(U+30A6 KATAKANA LETTER U)と(U+3099 COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK)の組み合わせと等価とされています。
従って"えと"と"えど"の場合と同様、IgnoreNonSpaceの場合は濁点・半濁点が無視され、"ハン"と"パン"、"ウ"と"ヴ"はそれぞれ同じであると判断されます。
"バ゙ン"と"パン"
"◎゛ "と"●"
(U+30CF KATAKANA LETTER HA)に濁点が二つつけられたものと(U+30D1 KATAKANA LETTER PA)は等しくなります。 これは、濁点が基本となる文字に対して重み1を与え、半濁点が重み2を与えるためで、濁点二つと半濁点一つではどちらも同じ重みが与えられます。 その結果、"バ゙ン"と"パン"は同じであると判断されます。
(U+25CE BULLSEYE)に濁点がつけられたものと(U+25CF BLACK CIRCLE)が等しくなるのもこれと同じで、濁点によって重みが与えらることにより、"◎゛ "と"●"は同じであると判断されます。
参考: 「MS ACCESS 95/97 の美しいソート順」の謎に答える - ものがたり
"C"と"©"
"C"と"Ⓒ"
©(U+00A9 COPYRIGHT SIGN)は組み合わせた文字としては扱われないため、IgnoreNonSpaceの場合でも"C"と"©"は異なると判断されます。
一方(U+24B8 CIRCLED LATIN CAPITAL LETTER C)はC(U+0043 LATIN CAPITAL LETTER C)とほぼ等価とされているため、IgnoreNonSpaceの場合"C"と"Ⓒ"は同じであると判断されます。
"1"と"①"
"1"と"①"
(U+2460 CIRCLED DIGIT ONE)は1(U+0031 DIGIT ONE)とほぼ等価とされているため、IgnoreNonSpaceの場合"1"と"①"は同じであると判断されます。
また、(U+FF11 FULLWIDTH DIGIT ONE)は1(U+0031 DIGIT ONE)と全角半角の関係にあるため、IgnoreNonSpace+IgnoreWidthの場合"1"と"①"は同じであると判断されます。

IgnoreSymbols

(このドキュメントは未整理です)

IgnoreSymbolsは、空白文字、句読点等の記号を無視して比較するように指定します。 これらの値や他の値と組み合わせて使用することも出来ます。 CompareOptions.IgnoreSymbolsの解説では

文字列比較において、空白文字、句読点、通貨記号、パーセント記号、算術記号、アンパサンドなどの記号を無視することを示します。

CompareOptions 列挙体

とされているものの、具体的にどの記号が無視される対象なのかは明記されていないようです。 また、.NET FrameworkとMonoでも実行結果は異なるようです。

CompareOptions.IgnoreSymbolsを指定して空白文字・句読点・記号等を無視して比較する
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Compare("ABC", "\\A,B!C*", CompareOptions.None);
    Compare("ABC", "\\A,B!C*", CompareOptions.IgnoreSymbols);
    Compare("ABC", "A[ B ]C", CompareOptions.None);
    Compare("ABC", "A[ B ]C", CompareOptions.IgnoreSymbols);

    Console.WriteLine();

    Compare("あいうえお", "あ い う/え・お", CompareOptions.None);
    Compare("あいうえお", "あ い う/え・お", CompareOptions.IgnoreSymbols);
    Compare("あいうえお", "(あ){い}(う)「え」【お】", CompareOptions.None);
    Compare("あいうえお", "(あ){い}(う)「え」【お】", CompareOptions.IgnoreSymbols);
    Compare("あいうえお", "¥あ~い!う☆え@お。", CompareOptions.None);
    Compare("あいうえお", "¥あ~い!う☆え@お。", CompareOptions.IgnoreSymbols);
  }

  private static void Compare(string s1, string s2, CompareOptions options)
  {
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options);
  }
}
CompareOptions.IgnoreSymbolsを指定して空白文字・句読点・記号等を無視して比較する
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Compare("ABC", "\A,B!C*", CompareOptions.None)
    Compare("ABC", "\A,B!C*", CompareOptions.IgnoreSymbols)
    Compare("ABC", "A[ B ]C", CompareOptions.None)
    Compare("ABC", "A[ B ]C", CompareOptions.IgnoreSymbols)

    Console.WriteLine()

    Compare("あいうえお", "あ い う/え・お", CompareOptions.None)
    Compare("あいうえお", "あ い う/え・お", CompareOptions.IgnoreSymbols)
    Compare("あいうえお", "(あ){い}(う)「え」【お】", CompareOptions.None)
    Compare("あいうえお", "(あ){い}(う)「え」【お】", CompareOptions.IgnoreSymbols)
    Compare("あいうえお", "¥あ~い!う☆え@お。", CompareOptions.None)
    Compare("あいうえお", "¥あ~い!う☆え@お。", CompareOptions.IgnoreSymbols)
  End Sub

  Private Shared Sub Compare(ByVal s1 As String, ByVal s2 As String, ByVal options As CompareOptions)
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options)
  End Sub
End Class
実行結果
ja-JP
ABC \A,B!C* :  1 (None)
ABC \A,B!C* :  0 (IgnoreSymbols)
ABC A[ B ]C :  1 (None)
ABC A[ B ]C :  0 (IgnoreSymbols)

あいうえお あ い う/え・お :  1 (None)
あいうえお あ い う/え・お :  1 (IgnoreSymbols)
あいうえお (あ){い}(う)「え」【お】 :  1 (None)
あいうえお (あ){い}(う)「え」【お】 :  1 (IgnoreSymbols)
あいうえお ¥あ〜い!う☆え@お。 :  1 (None)
あいうえお ¥あ〜い!う☆え@お。 :  1 (IgnoreSymbols)
実行結果
ja-JP
ABC \A,B!C* :  1 (None)
ABC \A,B!C* :  0 (IgnoreSymbols)
ABC A[ B ]C :  1 (None)
ABC A[ B ]C :  0 (IgnoreSymbols)

あいうえお あ い う/え・お :  1 (None)
あいうえお あ い う/え・お :  0 (IgnoreSymbols)
あいうえお (あ){い}(う)「え」【お】 :  1 (None)
あいうえお (あ){い}(う)「え」【お】 :  0 (IgnoreSymbols)
あいうえお ¥あ~い!う☆え@お。 :  1 (None)
あいうえお ¥あ~い!う☆え@お。 :  0 (IgnoreSymbols)

StringSort

(このドキュメントは未整理です)

StringSortは、ハイフン・アポストロフィが英数字や他の記号よりも前になるように比較するように指定します。 これらの値や他の値と組み合わせて使用することも出来ます。 CompareOptions.StringSortの解説では、

文字列の比較時に、文字列での並べ替えアルゴリズムを使用することを示します。文字列での並べ替えでは、ハイフン、アポストロフィ、およびその他の英数字以外の記号が英数字よりも前に来ます。

CompareOptions 列挙体

とされているものの、具体的にハイフン・アポストロフィ以外のどの記号が無視される対象なのかは明記されていないようです。 また、.NET FrameworkとMonoでも実行結果は異なるようです。

CompareOptions.StringSortを指定してハイフン・アポストロフィが英数字よりも前となるように比較する
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    Console.WriteLine(Thread.CurrentThread.CurrentCulture);

    Compare("cant", "can't", CompareOptions.None);
    Compare("cant", "can't", CompareOptions.StringSort);
    Compare("cant", "can't", CompareOptions.Ordinal);

    Console.WriteLine();

    Compare("coop", "co-op", CompareOptions.None);
    Compare("coop", "co-op", CompareOptions.StringSort);
    Compare("coop", "co-op", CompareOptions.Ordinal);

    Console.WriteLine();

    Compare("あい", "あ-い", CompareOptions.None);
    Compare("あい", "あ-い", CompareOptions.StringSort);
    Compare("あい", "あ-い", CompareOptions.Ordinal);

    Console.WriteLine();

    Compare("coop", "co#op", CompareOptions.None);
    Compare("coop", "co#op", CompareOptions.StringSort);
    Compare("coop", "co@op", CompareOptions.None);
    Compare("coop", "co@op", CompareOptions.StringSort);
    Compare("co-op", "co#op", CompareOptions.None);
    Compare("co-op", "co#op", CompareOptions.StringSort);
    Compare("co-op", "co@op", CompareOptions.None);
    Compare("co-op", "co@op", CompareOptions.StringSort);
  }

  private static void Compare(string s1, string s2, CompareOptions options)
  {
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options);
  }
}
CompareOptions.StringSortを指定してハイフン・アポストロフィが英数字よりも前となるように比較する
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Console.WriteLine(Thread.CurrentThread.CurrentCulture)

    Compare("cant", "can't", CompareOptions.None)
    Compare("cant", "can't", CompareOptions.StringSort)
    Compare("cant", "can't", CompareOptions.Ordinal)

    Console.WriteLine()

    Compare("coop", "co-op", CompareOptions.None)
    Compare("coop", "co-op", CompareOptions.StringSort)
    Compare("coop", "co-op", CompareOptions.Ordinal)

    Console.WriteLine()

    Compare("あい", "あ-い", CompareOptions.None)
    Compare("あい", "あ-い", CompareOptions.StringSort)
    Compare("あい", "あ-い", CompareOptions.Ordinal)

    Console.WriteLine()

    Compare("coop", "co#op", CompareOptions.None)
    Compare("coop", "co#op", CompareOptions.StringSort)
    Compare("coop", "co@op", CompareOptions.None)
    Compare("coop", "co@op", CompareOptions.StringSort)
    Compare("co-op", "co#op", CompareOptions.None)
    Compare("co-op", "co#op", CompareOptions.StringSort)
    Compare("co-op", "co@op", CompareOptions.None)
    Compare("co-op", "co@op", CompareOptions.StringSort)
  End Sub

  Private Shared Sub Compare(ByVal s1 As String, ByVal s2 As String, ByVal options As CompareOptions)
    Console.WriteLine("{0} {1} : {2,2} ({3:f})", s1, s2, String.Compare(s1, s2, Thread.CurrentThread.CurrentCulture, options), options)
  End Sub
End Class
実行結果
ja-JP
cant can't : -1 (None)
cant can't :  1 (StringSort)
cant can't : 77 (Ordinal)

coop co-op : -1 (None)
coop co-op :  1 (StringSort)
coop co-op : 66 (Ordinal)

あい あ-い :  1 (None)
あい あ-い :  1 (StringSort)
あい あ-い : -52937 (Ordinal)

coop co#op :  1 (None)
coop co#op :  1 (StringSort)
coop co@op :  1 (None)
coop co@op :  1 (StringSort)
co-op co#op :  1 (None)
co-op co#op : -1 (StringSort)
co-op co@op :  1 (None)
co-op co@op : -1 (StringSort)
実行結果
ja-JP
cant can't : -1 (None)
cant can't :  0 (StringSort)
cant can't : 77 (Ordinal)

coop co-op : -1 (None)
coop co-op :  0 (StringSort)
coop co-op : 66 (Ordinal)

あい あ-い : -1 (None)
あい あ-い :  0 (StringSort)
あい あ-い : -52937 (Ordinal)

coop co#op :  1 (None)
coop co#op :  1 (StringSort)
coop co@op :  1 (None)
coop co@op :  1 (StringSort)
co-op co#op :  1 (None)
co-op co#op :  1 (StringSort)
co-op co@op :  1 (None)
co-op co@op :  1 (StringSort)

CompareOptionsとStringComparison

StringComparisonを指定した場合と同じ比較処理をCompareOptionsでも行うことが出来ます。 以下の表は、StringComparisonの値と、対応するCompareOptionsとCultureInfoの値の組み合わせです。

StringComparisonと等価なCompareOptionsとCultureInfoの組み合わせ
StringComparisonの値 対応するCompareOptionsとCultureInfoの組み合わせ
CompareOptions CultureInfo
StringComparison.Ordinal CompareOptions.Ordinal (任意のCultureInfo)
StringComparison.OrdinalIgnoreCase CompareOptions.OrdinalIgnoreCase (任意のCultureInfo)
StringComparison.CurrentCulture CompareOptions.None CultureInfo.CurrentCulture
(またはThread.CurrentThread.CurrentCulture)
StringComparison.CurrentCultureIgnoreCase CompareOptions.IgnoreCase CultureInfo.CurrentCulture
(またはThread.CurrentThread.CurrentCulture)
StringComparison.InvariantCulture CompareOptions.None CultureInfo.InvariantCulture
StringComparison.InvariantCultureIgnoreCase CompareOptions.IgnoreCase CultureInfo.InvariantCulture

次の例は、同じ比較処理をStringComparisonで指定した場合とCompareOptionsで指定した場合の例です。 記述は異なりますが、結果は同じです。

StringComparisonと同等となるCompareOptionsの値
using System;
using System.Globalization;
using System.Threading;

class Sample {
  static void Main()
  {
    string[] p1 = new string[] {"coop", "co-op"};
    string[] p2 = new string[] {"亜", "井"};
    CultureInfo c = Thread.CurrentThread.CurrentCulture;

    Console.WriteLine(c);

    Console.WriteLine("{0} {1}", p1[0], p1[1]);
    Console.WriteLine("Compare StringComparison.Ordinal          : {0}", String.Compare(p1[0], p1[1], StringComparison.Ordinal));
    Console.WriteLine("Compare StringComparison.CurrentCulture   : {0}", String.Compare(p1[0], p1[1], StringComparison.CurrentCulture));
    Console.WriteLine("Compare StringComparison.InvariantCulture : {0}", String.Compare(p1[0], p1[1], StringComparison.InvariantCulture));
    Console.WriteLine("Compare CompareOptions.Ordinal            : {0}", String.Compare(p1[0], p1[1], c, CompareOptions.Ordinal));
    Console.WriteLine("Compare CompareOptions.None               : {0}", String.Compare(p1[0], p1[1], c, CompareOptions.None));
    Console.WriteLine("Compare CompareOptions.None (Invariant)   : {0}", String.Compare(p1[0], p1[1], CultureInfo.InvariantCulture, CompareOptions.None));

    Console.WriteLine("{0} {1}", p2[0], p2[1]);
    Console.WriteLine("Compare StringComparison.Ordinal          : {0}", String.Compare(p2[0], p2[1], StringComparison.Ordinal));
    Console.WriteLine("Compare StringComparison.CurrentCulture   : {0}", String.Compare(p2[0], p2[1], StringComparison.CurrentCulture));
    Console.WriteLine("Compare StringComparison.InvariantCulture : {0}", String.Compare(p2[0], p2[1], StringComparison.InvariantCulture));
    Console.WriteLine("Compare CompareOptions.Ordinal            : {0}", String.Compare(p2[0], p2[1], c, CompareOptions.Ordinal));
    Console.WriteLine("Compare CompareOptions.None               : {0}", String.Compare(p2[0], p2[1], c, CompareOptions.None));
    Console.WriteLine("Compare CompareOptions.None (Invariant)   : {0}", String.Compare(p2[0], p2[1], CultureInfo.InvariantCulture, CompareOptions.None));
  }
}
StringComparisonと同等となるCompareOptionsの値
Imports System
Imports System.Globalization
Imports System.Threading

Class Sample
  Shared Sub Main()
    Dim p1 As String() = New String() {"coop", "co-op"}
    Dim p2 As String() = New String() {"亜", "井"}
    Dim c As CultureInfo = Thread.CurrentThread.CurrentCulture

    Console.WriteLine(c)

    Console.WriteLine("{0} {1}", p1(0), p1(1))
    Console.WriteLine("Compare StringComparison.Ordinal          : {0}", String.Compare(p1(0), p1(1), StringComparison.Ordinal))
    Console.WriteLine("Compare StringComparison.CurrentCulture   : {0}", String.Compare(p1(0), p1(1), StringComparison.CurrentCulture))
    Console.WriteLine("Compare StringComparison.InvariantCulture : {0}", String.Compare(p1(0), p1(1), StringComparison.InvariantCulture))
    Console.WriteLine("Compare CompareOptions.Ordinal            : {0}", String.Compare(p1(0), p1(1), c, CompareOptions.Ordinal))
    Console.WriteLine("Compare CompareOptions.None               : {0}", String.Compare(p1(0), p1(1), c, CompareOptions.None))
    Console.WriteLine("Compare CompareOptions.None (Invariant)   : {0}", String.Compare(p1(0), p1(1), CultureInfo.InvariantCulture, CompareOptions.None))

    Console.WriteLine("{0} {1}", p2(0), p2(1))
    Console.WriteLine("Compare StringComparison.Ordinal          : {0}", String.Compare(p2(0), p2(1), StringComparison.Ordinal))
    Console.WriteLine("Compare StringComparison.CurrentCulture   : {0}", String.Compare(p2(0), p2(1), StringComparison.CurrentCulture))
    Console.WriteLine("Compare StringComparison.InvariantCulture : {0}", String.Compare(p2(0), p2(1), StringComparison.InvariantCulture))
    Console.WriteLine("Compare CompareOptions.Ordinal            : {0}", String.Compare(p2(0), p2(1), c, CompareOptions.Ordinal))
    Console.WriteLine("Compare CompareOptions.None               : {0}", String.Compare(p2(0), p2(1), c, CompareOptions.None))
    Console.WriteLine("Compare CompareOptions.None (Invariant)   : {0}", String.Compare(p2(0), p2(1), CultureInfo.InvariantCulture, CompareOptions.None))
  End Sub
End Class
実行結果
ja-JP
coop co-op
Compare StringComparison.Ordinal          : 66
Compare StringComparison.CurrentCulture   : -1
Compare StringComparison.InvariantCulture : -1
Compare CompareOptions.Ordinal            : 66
Compare CompareOptions.None               : -1
Compare CompareOptions.None (Invariant)   : -1
亜 井
Compare StringComparison.Ordinal          : 7
Compare StringComparison.CurrentCulture   : -1
Compare StringComparison.InvariantCulture : 1
Compare CompareOptions.Ordinal            : 7
Compare CompareOptions.None               : -1
Compare CompareOptions.None (Invariant)   : 1

それぞれの結果の違いに関しては、§.CurrentCultureとInvariantCultureの違いおよび§.CurrentCultureとOrdinalの違いもあわせて参照してください。

CompareOptionsと部分文字列の探索・一致

String.Compareメソッドとは異なり、String.IndexOfなど部分文字列の探索と一致を検証するメソッドではStringComparisonを引数にとるオーバーロードは用意されていますが、CompareOptionsをとるオーバーロードは用意されていません。 CompareOptionsを使ってこれらの処理を行うには、CompareInfoクラスのメソッドを使う必要があります。

部分文字列の探索・一致を行うメソッド
Stringクラスのメソッド 代用できるCompareInfoクラスのメソッド
String.IndexOfメソッド CompareInfo.IndexOfメソッド
String.LastIndexOfメソッド CompareInfo.LastIndexOfメソッド
String.StartsWithメソッド CompareInfo.IsPrefixメソッド
String.EndsWithメソッド CompareInfo.IsSuffixメソッド
String.Equalsメソッド (なし)

なお、これらのメソッドでCompareOptions.StringSortを指定することはできません。 指定した場合はArgumentExceptionがスローされます。

次の例は、CompareInfoクラスの各メソッドを使った部分文字列の探索と一致の例です。

CompareOptionsを指定してIndexOf・LastIndexOf・StartsWith・EndsWithに相当する処理を行う
using System;
using System.Globalization;

class Sample {
  static void Main()
  {
    string s = "かごめかごめ かごのなかのとりは いついつでやる";
    CompareInfo ci = CultureInfo.InvariantCulture.CompareInfo;

    Console.WriteLine(s);

    Console.WriteLine();
    Console.WriteLine("IndexOf(とり、トリ)");
    Console.WriteLine(s.IndexOf("とり"));
    Console.WriteLine(s.IndexOf("トリ"));
    Console.WriteLine(ci.IndexOf(s, "とり"));
    Console.WriteLine(ci.IndexOf(s, "とり", CompareOptions.None));
    Console.WriteLine(ci.IndexOf(s, "トリ"));
    Console.WriteLine(ci.IndexOf(s, "トリ", CompareOptions.IgnoreKanaType));

    Console.WriteLine();
    Console.WriteLine("LastIndexOf(かご、カゴ)");
    Console.WriteLine(s.LastIndexOf("かご"));
    Console.WriteLine(s.LastIndexOf("カゴ"));
    Console.WriteLine(ci.LastIndexOf(s, "かご"));
    Console.WriteLine(ci.LastIndexOf(s, "カゴ"));
    Console.WriteLine(ci.LastIndexOf(s, "カゴ", CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth));

    Console.WriteLine();
    Console.WriteLine("IsPrefix(かごめ、かこめ)");
    Console.WriteLine(s.StartsWith("かごめ"));
    Console.WriteLine(s.StartsWith("かこめ"));
    Console.WriteLine(ci.IsPrefix(s, "かごめ"));
    Console.WriteLine(ci.IsPrefix(s, "かこめ"));
    Console.WriteLine(ci.IsPrefix(s, "かこめ", CompareOptions.IgnoreNonSpace));

    Console.WriteLine();
    Console.WriteLine("IsSuffix(でやる、デヤル)");
    Console.WriteLine(s.EndsWith("でやる"));
    Console.WriteLine(s.EndsWith("デヤル"));
    Console.WriteLine(ci.IsSuffix(s, "でやる"));
    Console.WriteLine(ci.IsSuffix(s, "デヤル"));
    Console.WriteLine(ci.IsSuffix(s, "デヤル", CompareOptions.IgnoreKanaType));
  }
}
CompareOptionsを指定してIndexOf・LastIndexOf・StartsWith・EndsWithに相当する処理を行う
Imports System
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim s As String = "かごめかごめ かごのなかのとりは いついつでやる"
    Dim ci As CompareInfo = CultureInfo.InvariantCulture.CompareInfo

    Console.WriteLine(s)

    Console.WriteLine()
    Console.WriteLine("IndexOf(とり、トリ)")
    Console.WriteLine(s.IndexOf("とり"))
    Console.WriteLine(s.IndexOf("トリ"))
    Console.WriteLine(ci.IndexOf(s, "とり"))
    Console.WriteLine(ci.IndexOf(s, "とり", CompareOptions.None))
    Console.WriteLine(ci.IndexOf(s, "トリ"))
    Console.WriteLine(ci.IndexOf(s, "トリ", CompareOptions.IgnoreKanaType))

    Console.WriteLine()
    Console.WriteLine("LastIndexOf(かご、カゴ)")
    Console.WriteLine(s.LastIndexOf("かご"))
    Console.WriteLine(s.LastIndexOf("カゴ"))
    Console.WriteLine(ci.LastIndexOf(s, "かご"))
    Console.WriteLine(ci.LastIndexOf(s, "カゴ"))
    Console.WriteLine(ci.LastIndexOf(s, "カゴ", CompareOptions.IgnoreKanaType Or CompareOptions.IgnoreWidth))

    Console.WriteLine()
    Console.WriteLine("IsPrefix(かごめ、かこめ)")
    Console.WriteLine(s.StartsWith("かごめ"))
    Console.WriteLine(s.StartsWith("かこめ"))
    Console.WriteLine(ci.IsPrefix(s, "かごめ"))
    Console.WriteLine(ci.IsPrefix(s, "かこめ"))
    Console.WriteLine(ci.IsPrefix(s, "かこめ", CompareOptions.IgnoreNonSpace))

    Console.WriteLine()
    Console.WriteLine("IsSuffix(でやる、デヤル)")
    Console.WriteLine(s.EndsWith("でやる"))
    Console.WriteLine(s.EndsWith("デヤル"))
    Console.WriteLine(ci.IsSuffix(s, "でやる"))
    Console.WriteLine(ci.IsSuffix(s, "デヤル"))
    Console.WriteLine(ci.IsSuffix(s, "デヤル", CompareOptions.IgnoreKanaType))
  End Sub
End Class
実行結果
かごめかごめ かごのなかのとりは いついつでやる

IndexOf(とり、トリ)
13
-1
13
13
-1
13

LastIndexOf(かご、カゴ)
7
-1
7
-1
7

IsPrefix(かごめ、かこめ)
True
False
True
False
True

IsSuffix(でやる、デヤル)
True
False
True
False
True

CompareOptionsに対応するStringComparerの取得

StringComparisonに対応するStringComparerとは異なり、IComparer<string>・IEqualityComparer<string>を実装するCompareOptionsに対応したクラスは直接提供されていませんが、GetStringComparer拡張メソッドを使用することにより、CompareOptionsに対応するStringComparerを取得することができます。 GetStringComparer拡張メソッドは、.NET Frameworkでは4.7.1以降で使用することができます。

Dictionaryのキー比較にCompareOptionsを使用する

GetStringComparer拡張メソッドで取得したStringComparerをDictionaryのコンストラクタに渡すことにより、CompareOptionsを使用したキーの比較を行うことができます。

例として、CompareOptions.IgnoreKanaType/IgnoreWidthを使って、キーのひらがなとカタカナの違い/全角と半角の違いを無視するDictionary<string, string>を作成するには次のようにします。

CompareOptionsを使用してキーのひらがなとカタカナの違い無視するDictionaryを作成する .NET Framework 4.7.1
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  static void Main()
  {
    // ひらがな・カタカナの違いを無視するDictionaryを作成する
    var dict = new Dictionary<string, string>(
      // CultureInfo.InvariantCultureのCompareInfoをベースに、CompareOptions.IgnoreKanaTypeを指定して
      // ひらがな・カタカナの違いを無視するStringComparerを取得する
      CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreKanaType)
    );

    dict["ほげ"] = "hoge";
    dict["ぴよ"] = "piyo";
    dict["ふが"] = "fuga";

    dict["ピヨ"] = "PIYO"; // ひらがな・カタカナの違いは無視されるため、キー"ぴよ"の値が上書きされる

    foreach (var p in dict) {
      Console.WriteLine("{0} => {1}", p.Key, p.Value);
    }

    Console.WriteLine(dict.ContainsKey("ピヨ"));
  }
}
CompareOptionsを使用してキーのひらがなとカタカナの違い無視するDictionaryを作成する .NET Framework 4.7.1
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  Shared Sub Main()
    ' 独自に実装したIEqualityComparer(Of String)を指定してDictionaryを作成する
    ' (CultureInfo.InvariantCultureのCompareInfoをベースに、CompareOptions.IgnoreKanaTypeを指定して
    ' ひらがな・カタカナの違いを無視するStringComparerを取得する)
    Dim dict As New Dictionary(Of String, String)(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreKanaType))

    dict("ほげ") = "hoge"
    dict("ぴよ") = "piyo"
    dict("ふが") = "fuga"

    dict("ピヨ") = "PIYO" ' ひらがな・カタカナの違いは無視されるため、キー"ぴよ"の値が上書きされる

    For Each p As KeyValuePair(Of String, String) In dict
      Console.WriteLine("{0} => {1}", p.Key, p.Value)
    Next

    Console.WriteLine(dict.ContainsKey("ピヨ"))
  End Sub
End Class
実行結果
ほげ => hoge
ぴよ => PIYO
ふが => fuga
True
CompareOptionsを使用してキーの全角半角の違い無視するDictionaryを作成する .NET Framework 4.7.1
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  static void Main()
  {
    // 全角・半角の違いを無視するDictionaryを作成する
    var dict = new Dictionary<string, string>(
      // CultureInfo.InvariantCultureのCompareInfoをベースに、CompareOptions.IgnoreWidthを指定して
      // 全角・半角の違いを無視するStringComparerを取得する
      CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth)
    );

    dict["hoge"] = "ほげ";
    dict["piyo"] = "ぴよ";
    dict["42"] = "よんじゅうに";

    // 全角半角の違いは無視されるため、キー"piyo", "42"の値が上書きされる
    dict["piyo"] = "ピヨ";
    dict["42"] = "よんじゅうに";

    foreach (var p in dict) {
      Console.WriteLine("{0} => {1}", p.Key, p.Value);
    }

    Console.WriteLine(dict.ContainsKey("piyo"));
  }
}
CompareOptionsを使用してキーの全角半角の違い無視するDictionaryを作成する .NET Framework 4.7.1
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  Shared Sub Main()
    ' 独自に実装したIEqualityComparer(Of String)を指定してDictionaryを作成する
    ' (CultureInfo.InvariantCultureのCompareInfoをベースに、CompareOptions.IgnoreWidthを指定して
    ' 全角・半角の違いを無視するStringComparerを取得する)
    Dim dict As New Dictionary(Of String, String)(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth))

    dict("hoge") = "ほげ"
    dict("piyo") = "ぴよ"
    dict("42") = "よんじゅうに"

    ' 全角半角の違いは無視されるため、キー"piyo", "42"の値が上書きされる
    dict("piyo") = "ピヨ"
    dict("42") = "よんじゅうに"

    For Each p As KeyValuePair(Of String, String) In dict
      Console.WriteLine("{0} => {1}", p.Key, p.Value)
    Next

    Console.WriteLine(dict.ContainsKey("piyo"))
  End Sub
End Class
実行結果
hoge => ほげ
piyo => ピヨ
42 => よんじゅうに
True

Dictionaryとキー比較のカスタマイズについてはジェネリックコレクション(2) Dictionary §.キー比較のカスタマイズ(大文字小文字の違いの無視)を参照してください。


4.7以前の.NET FrameworkでCompareOptionsを使用してDictionaryを作成する場合は、CompareInfo.CompareメソッドをラップしてIEqualityComparer<string>を実装するクラスを独自に実装する必要があります。

CompareOptionsを使用してキーのひらがなとカタカナの違い無視するDictionaryを作成する(.NET Framework 4.7以前)
using System;
using System.Collections.Generic;
using System.Globalization;

// ひらがな・カタカナの違いを無視するIEqualityComparer<string>
class IgnoreKanaTypeComparer : EqualityComparer<string> {
  public override bool Equals(string x, string y)
  {
    // CompareOptions.IgnoreKanaTypeを指定して2つの文字列が等しいか否かを判定する
    return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreKanaType) == 0;
  }

  public override int GetHashCode(string s)
  {
    // 実装は省略
    return 0;
  }
}

class Sample {
  static void Main()
  {
    // 独自に実装したIEqualityComparer<string>を指定してDictionaryを作成する
    Dictionary<string, string> dict = new Dictionary<string, string>(new IgnoreKanaTypeComparer());

    dict["ほげ"] = "hoge";
    dict["ぴよ"] = "piyo";
    dict["ふが"] = "fuga";

    dict["ピヨ"] = "PIYO"; // ひらがな・カタカナの違いは無視されるため、キー"ぴよ"の値が上書きされる

    foreach (KeyValuePair<string, string> p in dict) {
      Console.WriteLine("{0} => {1}", p.Key, p.Value);
    }

    Console.WriteLine(dict.ContainsKey("ピヨ"));
  }
}
CompareOptionsを使用してキーのひらがなとカタカナの違い無視するDictionaryを作成する(.NET Framework 4.7以前)
Imports System
Imports System.Collections.Generic
Imports System.Globalization

' ひらがな・カタカナの違いを無視するIEqualityComparer(Of String)
Class IgnoreKanaTypeComparer
  Inherits EqualityComparer(Of String)

  Public Overrides Function Equals(ByVal x As String, ByVal y As String) As Boolean
    ' CompareOptions.IgnoreKanaTypeを指定して2つの文字列が等しいか否かを判定する
    Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreKanaType) = 0
  End Function

  Public Overrides Function GetHashCode(ByVal s As String) As Integer
    ' 実装は省略
    Return 0
  End Function
End Class

Class Sample
  Shared Sub Main()
    ' 独自に実装したIEqualityComparer(Of String)を指定してDictionaryを作成する
    Dim dict As New Dictionary(Of String, String)(New IgnoreKanaTypeComparer())

    dict("ほげ") = "hoge"
    dict("ぴよ") = "piyo"
    dict("ふが") = "fuga"

    dict("ピヨ") = "PIYO" ' ひらがな・カタカナの違いは無視されるため、キー"ぴよ"の値が上書きされる

    For Each p As KeyValuePair(Of String, String) In dict
      Console.WriteLine("{0} => {1}", p.Key, p.Value)
    Next

    Console.WriteLine(dict.ContainsKey("ピヨ"))
  End Sub
End Class
実行結果
ほげ => hoge
ぴよ => PIYO
ふが => fuga
True

この例では、EqualityComparer<string>クラスを継承することでIEqualityComparer<string>インターフェイスを実装しています。 また、GetHashCodeメソッドの実装は省略していますが、オブジェクトのハッシュ値が使用される場合は適切に実装する必要があります。

EqualityComparer<T>についての解説や実装例については等価性の定義と比較 §.EqualityComparer<T>を参照してください。

ソートにCompareOptionsを使用する

List<string>.SortやArray.Sortの場合も同様に、GetStringComparer拡張メソッドで取得したStringComparerをSortメソッドに渡すことにより、CompareOptionsを使用したソートを行うことができます。

例として、CompareOptions.IgnoreKanaType/IgnoreWidthを使って、ひらがなとカタカナの違い/全角と半角の違いを無視してソートするには次のようにします。

.NET Framework 4.7以前の場合は、Comparison<string>デリゲートに適合するメソッドあるいはラムダ式を記述することで、CompareOptionsを使用したソートを行うことができます。

CompareOptionsを使用してひらがなカタカナの違いを無視してListをソートする .NET Framework 4.7
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  static void Main()
  {
    var wordList = new List<string>() {"ア", "あイ", "あい", "い", "あ", "アい", "イ"};

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordList));

    // CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreKanaTypeを指定して
    // ひらがな・カタカナの違いを無視してソートする
    wordList.Sort(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreKanaType));
    // .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    //wordList.Sort((x, y) => String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreKanaType));

    Console.WriteLine("CompareOptions.IgnoreKanaType  : {0}", String.Join(", ", wordList));

    wordList.Sort(StringComparer.InvariantCulture);

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordList));

    wordList.Sort(StringComparer.Ordinal);

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList));
  }
}
CompareOptionsを使用してひらがなカタカナの違いを無視してListをソートする .NET Framework 4.7
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim wordList As New List(Of String) From {"ア", "あイ", "あい", "い", "あ", "アい", "イ"}

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordList))

    ' CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreKanaTypeを指定して
    ' ひらがな・カタカナの違いを無視してソートする
    wordList.Sort(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreKanaType))
    ' .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    'wordList.Sort(Function(x As String, y As String)
    '  Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreKanaType)
    'End Function)

    Console.WriteLine("CompareOptions.IgnoreKanaType  : {0}", String.Join(", ", wordList))

    wordList.Sort(StringComparer.InvariantCulture)

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordList))

    wordList.Sort(StringComparer.Ordinal)

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList))
  End Sub
End Class
実行結果
before sort                    : ア, あイ, あい, い, あ, アい, イ
CompareOptions.IgnoreKanaType  : ア, あ, あイ, あい, アい, い, イ
StringComparer.InvariantCulture: あ, ア, あい, あイ, アい, い, イ
StringComparer.Ordinal         : あ, あい, あイ, い, ア, アい, イ
CompareOptionsを使用して全角半角の違いを無視してListをソートする .NET Framework 4.7
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  static void Main()
  {
    var wordList = new List<string>() {"foo", "Foo", "foo", "FOO", "FOO", "Foo"};

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordList));

    // CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreWidthを指定して
    // 全角半角の違いを無視してソートする
    wordList.Sort(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth));
    // .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    //wordList.Sort((x, y) => String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth));

    Console.WriteLine("CompareOptions.IgnoreWidth     : {0}", String.Join(", ", wordList));

    wordList.Sort(StringComparer.InvariantCulture);

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordList));

    wordList.Sort(StringComparer.Ordinal);

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList));
  }
}
CompareOptionsを使用して全角半角の違いを無視してListをソートする .NET Framework 4.7
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim wordList As New List(Of String) From {"foo", "Foo", "foo", "FOO", "FOO", "Foo"}

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordList))

    ' CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreWidthを指定して
    ' 全角半角の違いを無視してソートする
    wordList.Sort(CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth))
    ' .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    'wordList.Sort(Function(x As String, y As String)
    '  Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth)
    'End Function)

    Console.WriteLine("CompareOptions.IgnoreWidth     : {0}", String.Join(", ", wordList))

    wordList.Sort(StringComparer.InvariantCulture)

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordList))

    wordList.Sort(StringComparer.Ordinal)

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList))
  End Sub
End Class
実行結果
before sort                    : foo, Foo, foo, FOO, FOO, Foo
CompareOptions.IgnoreWidth     : foo, foo, Foo, Foo, FOO, FOO
StringComparer.InvariantCulture: foo, foo, Foo, FOO, Foo, FOO
StringComparer.Ordinal         : FOO, Foo, foo, FOO, Foo, foo
CompareOptionsを使用して全角半角の違いを無視して配列をソートする .NET Framework 4.7
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  static void Main()
  {
    var wordArray = new[] {"foo", "Foo", "foo", "FOO", "FOO", "Foo"};

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordArray));

    // CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreWidthを指定して
    // 全角半角の違いを無視してソートする
    Array.Sort(wordArray, CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth));
    // .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    //Array.Sort(wordArray, (x, y) => String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth));

    Console.WriteLine("CompareOptions.IgnoreWidth     : {0}", String.Join(", ", wordArray));

    Array.Sort(wordArray, StringComparer.InvariantCulture);

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordArray));

    Array.Sort(wordArray, StringComparer.Ordinal);

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordArray));
  }
}
CompareOptionsを使用して全角半角の違いを無視して配列をソートする .NET Framework 4.7
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  Shared Sub Main()
    Dim wordArray As String() = New String() {"foo", "Foo", "foo", "FOO", "FOO", "Foo"}

    Console.WriteLine("before sort                    : {0}", String.Join(", ", wordArray))

    ' CultureInfo.InvariantCultureをベースに、CompareOptions.IgnoreWidthを指定して
    ' 全角半角の違いを無視してソートする
    Array.Sort(wordArray, CultureInfo.InvariantCulture.CompareInfo.GetStringComparer(CompareOptions.IgnoreWidth))
    ' .NET Framework 4.7以前の場合は、String.Comparerメソッドを使用してソート順を定義する必要がある
    'Array.Sort(wordArray, Function(x As String, y As String)
    '  Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth)
    'End Function)

    Console.WriteLine("CompareOptions.IgnoreWidth     : {0}", String.Join(", ", wordArray))

    Array.Sort(wordArray, StringComparer.InvariantCulture)

    Console.WriteLine("StringComparer.InvariantCulture: {0}", String.Join(", ", wordArray))

    Array.Sort(wordArray, StringComparer.Ordinal)

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordArray))
  End Sub
End Class
実行結果
before sort                    : foo, Foo, foo, FOO, FOO, Foo
CompareOptions.IgnoreWidth     : foo, foo, Foo, Foo, FOO, FOO
StringComparer.InvariantCulture: foo, foo, Foo, FOO, Foo, FOO
StringComparer.Ordinal         : FOO, Foo, foo, FOO, Foo, foo

Comparison<T>デリゲートについては大小関係の定義と比較 §.Comparison<T>を参照してください。

ソートについて詳しくは基本型のソートと昇順・降順でのソートを参照してください。