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

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

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

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

StringComparison列挙型とStringComparerクラス

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

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

大文字と小文字の違いを無視した文字列比較の例
using System;

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

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

    // 大文字小文字の違いを無視して文字列を比較する
    // (s1とs2は等しいと判断される)
    Console.WriteLine(String.Equals(s1, s2, StringComparison.InvariantCultureIgnoreCase));
  }
}
大文字と小文字の違いを無視した文字列比較の例
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を作成したい場合は次のようにします。

Dictionaryでのキー比較規則を指定する例
using System;
using System.Collections.Generic;

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

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

    // キー"FOO"の値を参照
    Console.WriteLine(dict["FOO"]); // KeyNotFoundExceptionはスローされない
  }
}
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を指定した場合と同じ動作になります。

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

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

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

using System;

class Sample {
  static void Main()
  {
    string s1 = "foo";
    string 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));
  }
}
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とInvariantCultureは、ともに特定のカルチャでの規則に基づいた比較を行う点では同じですが、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でもの方がよりも小さいというどちらも同じ結果となります。 (参考: 固有カルチャのデータの比較と並べ替え)

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

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

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

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(IgnoreCase)、特定の言語や文化圏に依存しない規則に基づいて比較を行いたい場合はInvariantCulture(IgnoreCase)を使用する必要があります。

CurrentCultureとOrdinalの違い

CurrentCultureとInvariantCultureは特定のカルチャでの規則に基づいて比較を行いますが、一方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とInvariantCultureの場合と結果が似ているので分かりにくいですが、CurrentCultureの場合は(あ)の方が(い)より小さい(前に並ぶ)、つまり"<"とされます。

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

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

using System;

class Sample {
  static void Main()
  {
    string[] p1 = new string[] {"coop", "co-op"};
    string[] 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));
  }
}
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である点に注目するとよりわかりやすいかもしれません)

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

StringComparisonの使い分け

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

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

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

一方、InvariantCultureやOrdinalはカルチャによらず一定の結果となります。 したがって、実行環境によって結果が変わることが望ましくない部分、例えば内部のソートや比較処理など、ロジック部分での文字列操作が想定される主な適用範囲となります。

CurrentCultureやInvariantCultureでは、各文字の持つ意味的な側面(句読点や記号の扱い、アクセント記号の有無など)を考慮した比較となるため、あいまいな比較(あるいは自然な比較)でも構わない場合はこちらを使うことができます。 一方、バイト単位での比較のように、各文字の持つ意味的な側面を排除して、すべての文字を単なる数値として等しく扱いたい場合は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を使用することが出来ます。

using System;
using System.Collections.Generic;

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

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

    words.Sort(StringComparer.CurrentCulture);

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

    words.Sort(StringComparer.Ordinal);

    Console.WriteLine(String.Join(", ", words.ToArray()));
  }
}
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)

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

    words.Sort(StringComparer.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

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

using System;
using System.Collections.Generic;

class Sample {
  static void Main()
  {
    Dictionary<string, string> dict1 = new Dictionary<string, string>(StringComparer.InvariantCulture);
    Dictionary<string, string> 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);
    }
  }
}
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でアラビア数字・漢数字・囲み文字を並べ替えたときの違いを出力する例です。

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を指定して文字列の比較を行う例です。

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));
  }
}
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とソートで追って解説します。

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

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);
  }
}
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

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

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

CompareOptions 列挙体

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

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

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);
  }
}
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でも実行結果は異なるようです。

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);
  }
}
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でも実行結果は異なるようです。

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);
  }
}
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で指定した場合の例です。 記述は異なりますが、結果は同じです。

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));
  }
}
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クラスの各メソッドを使った部分文字列の探索と一致の例です。

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));
  }
}
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とソート

StringComparisonとは異なり、CompareOptionsに対応したIComparer<string>、IEqualityComparer<string>を提供するクラスはないため、自前で実装する必要があります。 例えば、キーのひらがなとカタカナの違いを無視するDictionary<string, string>を作成するには次のようにします。

Dictionaryのキー比較にCompareOptionsを使用する例
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("ピヨ"));
  }
}
Dictionaryのキー比較にCompareOptionsを使用する例
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>を参照してください。

また、List<string>.SortやArray.Sortの場合は、IComparerを用意しなくてもComparison<string>デリゲートに適合するメソッドがあれば良いので、次のようにできます。

List<string>.SortでCompareOptionsを使用する例
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  // 文字列の比較を行うメソッド
  static int CompareIgnoreWidth(string x, string y)
  {
    return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth);
  }

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

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

    wordList.Sort(CompareIgnoreWidth);

    Console.WriteLine("CompareIgnoreWidth             : {0}", String.Join(", ", wordList.ToArray()));

    wordList.Sort(StringComparer.InvariantCulture);

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

    wordList.Sort(StringComparer.Ordinal);

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList.ToArray()));
  }
}
List<string>.SortでCompareOptionsを使用する例
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  ' 文字列の比較を行うメソッド
  Shared Function CompareIgnoreWidth(ByVal x As String, ByVal y As String) As Integer
    Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth)
  End Function

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

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

    wordList.Sort(AddressOf CompareIgnoreWidth)

    Console.WriteLine("CompareIgnoreWidth             : {0}", String.Join(", ", wordList.ToArray()))

    wordList.Sort(StringComparer.InvariantCulture)

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

    wordList.Sort(StringComparer.Ordinal)

    Console.WriteLine("StringComparer.Ordinal         : {0}", String.Join(", ", wordList.ToArray()))
  End Sub
End Class
実行結果
before sort                    : foo, Foo, foo, FOO, FOO, Foo
CompareIgnoreWidth             : foo, foo, Foo, Foo, FOO, FOO
StringComparer.InvariantCulture: foo, foo, Foo, FOO, Foo, FOO
StringComparer.Ordinal         : FOO, Foo, foo, FOO, Foo, foo
Array.SortでCompareOptionsを使用する例
using System;
using System.Collections.Generic;
using System.Globalization;

class Sample {
  // 文字列の比較を行うメソッド
  static int CompareIgnoreWidth(string x, string y)
  {
    return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth);
  }

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

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

    Array.Sort(wordArray, CompareIgnoreWidth);

    Console.WriteLine("CompareIgnoreWidth             : {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));
  }
}
Array.SortでCompareOptionsを使用する例
Imports System
Imports System.Collections.Generic
Imports System.Globalization

Class Sample
  ' 文字列の比較を行うメソッド
  Shared Function CompareIgnoreWidth(ByVal x As String, ByVal y As String) As Integer
    Return String.Compare(x, y, CultureInfo.InvariantCulture, CompareOptions.IgnoreWidth)
  End Function

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

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

    Array.Sort(wordArray, AddressOf CompareIgnoreWidth)

    Console.WriteLine("CompareIgnoreWidth             : {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
CompareIgnoreWidth             : 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>を参照してください。