次の方法で共有


.NET で文字列を比較するためのベスト プラクティス

.NET では、ローカライズされたグローバル化されたアプリケーションを開発するための広範なサポートが提供され、文字列の並べ替えや表示などの一般的な操作を実行するときに、現在のカルチャまたは特定のカルチャの規則を簡単に適用できます。 ただし、文字列の並べ替えや比較は、カルチャに依存する操作とは限りません。 たとえば、通常、アプリケーションによって内部的に使用される文字列は、すべてのカルチャで同じように処理する必要があります。 XML タグ、HTML タグ、ユーザー名、ファイル パス、システム オブジェクトの名前など、カルチャに依存しない文字列データがカルチャに依存しているかのように解釈される場合、アプリケーション コードは微妙なバグ、パフォーマンスの低下、場合によってはセキュリティの問題の影響を受ける可能性があります。

この記事では、.NET での文字列の並べ替え、比較、および大文字と小文字の区別のメソッドについて説明し、適切な文字列処理メソッドを選択するための推奨事項を示し、文字列処理メソッドに関する追加情報を提供します。

文字列の使用に関する推奨事項

.NET を使用して開発する場合は、文字列を比較するときに次の推奨事項に従います。

ヒント

さまざまな文字列関連のメソッドが比較を実行します。 例としては、 String.EqualsString.CompareString.IndexOfString.StartsWithなどがあります。

文字列を比較するときは、次のプラクティスを避けてください。

  • 文字列操作の文字列比較規則を明示的または暗黙的に指定しないオーバーロードは使用しないでください。
  • ほとんどの場合、 StringComparison.InvariantCulture に基づく文字列操作は使用しないでください。 いくつかの例外の 1 つは、言語的に意味のあるが文化的に依存しないデータを保持している場合です。
  • String.CompareメソッドまたはCompareTo メソッドのオーバーロードを使用せず、戻り値が 0 かどうかをテストして、2 つの文字列が等しいかどうかを判断してください。

文字列比較を明示的に指定する

.NET の文字列操作メソッドのほとんどはオーバーロードされています。 通常、1 つ以上のオーバーロードは既定の設定を受け入れますが、他のオーバーロードは既定値を受け入れず、代わりに文字列を比較または操作する正確な方法を定義します。 既定値に依存しないほとんどのメソッドには、 StringComparison型のパラメーターが含まれます。これは、カルチャと大文字と小文字による文字列比較の規則を明示的に指定する列挙体です。 次の表では、 StringComparison 列挙メンバーについて説明します。

StringComparison メンバー 説明
CurrentCulture 現在のカルチャを使用して、大文字と小文字を区別する比較を実行します。
CurrentCultureIgnoreCase 現在のカルチャを使用して、大文字と小文字を区別しない比較を実行します。
InvariantCulture インバリアント カルチャを使用して、大文字と小文字を区別する比較を実行します。
InvariantCultureIgnoreCase インバリアント カルチャを使用して、大文字と小文字を区別しない比較を実行します。
Ordinal 序数比較を実行します。
OrdinalIgnoreCase 大文字と小文字を区別しない、序数に基づく比較を実行します。

たとえば、文字または文字列に一致するIndexOf オブジェクト内の部分文字列のインデックスを返すString メソッドには、次の 9 つのオーバーロードがあります。

次の理由から、既定値を使用しないオーバーロードを選択することをお勧めします。

  • 既定のパラメーターを持つオーバーロード (文字列インスタンスで Char を検索するオーバーロード) によっては序数比較を実行するものもあれば、カルチャに依存するオーバーロード (文字列インスタンス内の文字列を検索するオーバーロード) もあります。 どのメソッドがどの既定値を使用しているかを覚えておくのは難しく、オーバーロードを混同するのは簡単です。

  • メソッド呼び出しの既定値に依存するコードの意図は明確ではありません。 既定値に依存する次の例では、開発者が実際に2つの文字列の序数比較または言語的比較を意図しているのか、あるいはurl.Schemeと"HTTPS"の大文字と小文字の違いが原因で等価性のテストがfalseを返すかどうかを判断するのが困難です。

    Uri url = new("https://learn.microsoft.com/");
    
    // Incorrect
    if (string.Equals(url.Scheme, "https"))
    {
        // ...Code to handle HTTPS protocol.
    }
    
    Dim url As New Uri("https://learn.microsoft.com/")
    
    ' Incorrect
    If String.Equals(url.Scheme, "https") Then
        ' ...Code to handle HTTPS protocol.
    End If
    

一般に、コードの意図が明確になるため、既定値に依存しないメソッドを呼び出することをお勧めします。 これにより、コードの読みやすさが高くなり、デバッグと保守が容易になります。 次の例では、前の例に関して発生した質問に対処します。 序数比較を使用することと、大文字と小文字の違いを無視することを指定します。

Uri url = new("https://learn.microsoft.com/");

// Correct
if (string.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase))
{
    // ...Code to handle HTTPS protocol.
}
Dim url As New Uri("https://learn.microsoft.com/")

' Incorrect
If String.Equals(url.Scheme, "https", StringComparison.OrdinalIgnoreCase) Then
    ' ...Code to handle HTTPS protocol.
End If

文字列比較の詳細

文字列比較は、特に並べ替えと等価性のテストなど、文字列関連の多くの操作の中心です。 文字列は、決められた順序で並べ替えられます。文字列の並べ替えられたリストで "string" の前に "my" が表示される場合、"my" は "string" 以下を比較する必要があります。 さらに、比較によって等価性が暗黙的に定義されます。 比較操作は、等しいと見なされる文字列に対して 0 を返します。 適切な解釈は、どちらの文字列も他の文字列よりも小さいという点です。 文字列に関連する最も意味のある操作には、別の文字列との比較と、明確に定義された並べ替え操作の実行のいずれかまたは両方のプロシージャが含まれます。

並べ替えの重みテーブル、Windows オペレーティング システムの並べ替えおよび比較操作で使用される文字の重みに関する情報を含むテキスト ファイルのセット、およびLinuxとmacOS用の既定のUnicode照合順序要素テーブル、つまり並べ替え重みテーブルの最新バージョンをダウンロードできます。 Linux および macOS の並べ替え重みテーブルの特定のバージョンは、システムにインストールされている Unicode ライブラリ用の International Components のバージョンによって異なります。 ICU のバージョンと実装されている Unicode バージョンについては、「 ICU のダウンロード」を参照してください。

ただし、2 つの文字列の等値または並べ替え順序を評価しても、1 つの正しい結果は得られません。結果は、文字列の比較に使用される条件によって異なります。 特に、序数に基づく文字列比較や、現在のカルチャまたはインバリアント カルチャ (英語をベースとする、ロケールに依存しないカルチャ) の大文字と小文字の規則や並べ替えの規則に基づく文字列比較では、さまざまな結果が返される可能性があります。

さらに、異なるバージョンの .NET を使用する文字列比較や、異なるオペレーティング システムまたはオペレーティング システムのバージョンで .NET を使用すると、異なる結果が返される場合があります。 詳細については、「 文字列と Unicode 標準」を参照してください。

現在のカルチャを基準に文字列を比較する

1 つの条件では、文字列を比較するときに現在のカルチャの規則を使用する必要があります。 現在のカルチャに基づく比較では、スレッドの現在のカルチャまたはロケールが使用されます。 カルチャがユーザーによって設定されていない場合は、オペレーティング システムの設定が既定値になります。 データが言語的に関連している場合、およびカルチャに依存するユーザー操作を反映する場合は、常に現在のカルチャに基づく比較を使用する必要があります。

しかし、.NET の比較や大文字と小文字の区別の動作は、カルチャによって変わります。 これは、アプリケーションが開発されたコンピューターとは異なるカルチャを持つコンピューターでアプリケーションを実行するとき、または実行中のスレッドがそのカルチャを変更したときに発生します。 この動作は意図的なものですが、多くの開発者には明らかではありません。 次の例は、米国英語 ("en-US") とスウェーデン語 ("sv-SE") カルチャの並べ替え順序の違いを示しています。 並べ替えられた文字列配列では、"ångström"、"Windows"、および "Visual Studio" という単語が異なる位置に表示されることに注意してください。

using System.Globalization;

// Words to sort
string[] values= { "able", "ångström", "apple", "Æble",
                    "Windows", "Visual Studio" };

// Current culture
Array.Sort(values);
DisplayArray(values);

// Change culture to Swedish (Sweden)
string originalCulture = CultureInfo.CurrentCulture.Name;
Thread.CurrentThread.CurrentCulture = new CultureInfo("sv-SE");
Array.Sort(values);
DisplayArray(values);

// Restore the original culture
Thread.CurrentThread.CurrentCulture = new CultureInfo(originalCulture);

static void DisplayArray(string[] values)
{
    Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:");
    
    foreach (string value in values)
        Console.WriteLine($"   {value}");

    Console.WriteLine();
}

// The example displays the following output:
//     Sorting using the en-US culture:
//        able
//        Æble
//        ångström
//        apple
//        Visual Studio
//        Windows
//
//     Sorting using the sv-SE culture:
//        able
//        apple
//        Visual Studio
//        Windows
//        ångström
//        Æble
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        ' Words to sort
        Dim values As String() = {"able", "ångström", "apple", "Æble",
                                  "Windows", "Visual Studio"}

        ' Current culture
        Array.Sort(values)
        DisplayArray(values)

        ' Change culture to Swedish (Sweden)
        Dim originalCulture As String = CultureInfo.CurrentCulture.Name
        Thread.CurrentThread.CurrentCulture = New CultureInfo("sv-SE")
        Array.Sort(values)
        DisplayArray(values)

        ' Restore the original culture
        Thread.CurrentThread.CurrentCulture = New CultureInfo(originalCulture)
    End Sub

    Sub DisplayArray(values As String())
        Console.WriteLine($"Sorting using the {CultureInfo.CurrentCulture.Name} culture:")

        For Each value As String In values
            Console.WriteLine($"   {value}")
        Next

        Console.WriteLine()
    End Sub
End Module

' The example displays the following output:
'     Sorting using the en-US culture:
'        able
'        Æble
'        ångström
'        apple
'        Visual Studio
'        Windows
'
'     Sorting using the sv-SE culture:
'        able
'        apple
'        Visual Studio
'        Windows
'        ångström
'        Æble

現在のカルチャを使用する、大文字と小文字を区別しない比較は、スレッドの現在のカルチャの大文字と小文字の区別の規則が無視される以外は、カルチャに依存した比較と同じです。 この動作は、並べ替え順序でも現れる場合があります。

現在のカルチャ セマンティクスを使用する比較は、次のメソッドの既定値です。

いずれの場合も、 StringComparison パラメーターを持つオーバーロードを呼び出して、メソッド呼び出しの意図を明確にすることをお勧めします。

非言語的な文字列データが言語的に解釈される場合や、特定のカルチャの文字列データが別のカルチャの規則を使用して解釈される場合は、微妙でさりげないバグが発生する可能性があります。 標準的な例は、Turkish-I の問題です。

米国英語を含むほぼすべてのラテンアルファベットの場合、文字 "i" (\u0069) は文字 "I" (\u0049) の小文字のバージョンです。 この大文字と小文字の規則は、このようなカルチャでプログラミングを行う人にとってはすぐに当たり前のことになります。 しかし、トルコ語 ("tr-TR") のアルファベットには、"i" の大文字版である "ドット付きの I" ("İ" (\u0130)) があります。 トルコ語には、小文字の "i without a dot" 文字 "ı" (\u0131) も含まれています。これは "I" を大文字にします。 この動作は、アゼルバイジャン語 ("az") カルチャでも発生します。

したがって、"i" または "I" の文字が大文字や小文字で書かれることに関する前提は、すべての文化で通用するわけではありません。 文字列比較ルーチンに既定のオーバーロードを使用すると、カルチャ間の差異が発生します。 比較するデータが言語的でない場合、既定のオーバーロードを使用すると望ましくない結果が生じる可能性があります。次に示すのは、文字列 "bill" と "BILL" の大文字と小文字を区別しない比較を実行しようとする場合です。

using System.Globalization;

string name = "Bill";

Thread.CurrentThread.CurrentCulture = new CultureInfo("en-US");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");
Console.WriteLine();

Thread.CurrentThread.CurrentCulture = new CultureInfo("tr-TR");
Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}");
Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}");
Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", true, null)}");

//' The example displays the following output:
//'
//'     Culture = English (United States)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? True
//'     
//'     Culture = Turkish (Türkiye)
//'        Is 'Bill' the same as 'BILL'? True
//'        Does 'Bill' start with 'BILL'? False
Imports System.Globalization
Imports System.Threading

Module Program
    Sub Main()
        Dim name As String = "Bill"

        Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
        Console.WriteLine()

        Thread.CurrentThread.CurrentCulture = New CultureInfo("tr-TR")
        Console.WriteLine($"Culture = {Thread.CurrentThread.CurrentCulture.DisplayName}")
        Console.WriteLine($"   Is 'Bill' the same as 'BILL'? {name.Equals("BILL", StringComparison.OrdinalIgnoreCase)}")
        Console.WriteLine($"   Does 'Bill' start with 'BILL'? {name.StartsWith("BILL", True, Nothing)}")
    End Sub

End Module

' The example displays the following output:
'
'     Culture = English (United States)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? True
'     
'     Culture = Turkish (Türkiye)
'        Is 'Bill' the same as 'BILL'? True
'        Does 'Bill' start with 'BILL'? False

この比較により、次の例のように、セキュリティに依存する設定でカルチャが誤って使用される場合、重大な問題が発生する可能性があります。 IsFileURI("file:")などのメソッド呼び出しは、現在のカルチャが米国英語の場合はtrueを返しますが、現在のカルチャがトルコ語の場合はfalse。 したがって、トルコのシステムでは、"FILE:" で始まる大文字と小文字を区別しない URI へのアクセスをブロックするセキュリティ対策を回避する可能性があります。

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", true, null);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", True, Nothing)
End Function

この場合、"file:" は非言語的でカルチャに依存しない識別子として解釈されるため、代わりに次の例に示すようにコードを記述する必要があります。

public static bool IsFileURI(string path) =>
    path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase);
Public Shared Function IsFileURI(path As String) As Boolean
    Return path.StartsWith("FILE:", StringComparison.OrdinalIgnoreCase)
End Function

序数に基づく文字列操作

メソッド呼び出しで StringComparison.Ordinal 値または StringComparison.OrdinalIgnoreCase 値を指定することは、自然言語の機能が無視される非言語的比較を意味します。 これらの StringComparison 値を使用して呼び出されたメソッドでは、文字列操作の判断が、大文字と小文字の指定、またはカルチャでパラメーター化される同等の表ではなく、単純なバイト比較に基づいて行われます。 ほとんどの場合、このアプローチは、コードをより高速かつ信頼性の高いものにしながら、文字列の意図された解釈に最適です。

序数比較は、各文字列の各バイトが言語的な解釈なしで比較される文字列比較です。たとえば、"windows" は "Windows" と一致しません。 これは基本的に、C ランタイム strcmp 関数の呼び出しです。 この比較は、文字列を厳密に照合するか、保守的な照合ポリシーを要求する場合に、コンテキストによって指示される場合に使用します。 さらに、序数比較は、結果を決定するときに言語規則を適用しないため、最も高速な比較操作です。

.NET の文字列には、埋め込み null 文字 (およびその他の印刷以外の文字) を含めることができます。 序数とカルチャに依存する比較 (インバリアント カルチャを使用する比較を含む) の最も明確な違いの 1 つは、文字列内の埋め込み null 文字の処理に関係します。 カルチャに依存する比較 (インバリアント カルチャを使用する比較を含む) を実行するために String.Compare メソッドと String.Equals メソッドを使用する場合、これらの文字は無視されます。 その結果、埋め込み null 文字を含む文字列は、含まれない文字列と等しいと見なすことができます。 埋め込まれた印刷以外の文字は、 String.StartsWithなどの文字列比較メソッドの目的でスキップされる場合があります。

Von Bedeutung

文字列比較メソッドでは、埋め込まれた null 文字は無視されますが、 String.ContainsString.EndsWithString.IndexOfString.LastIndexOfString.StartsWith などの文字列検索メソッドは無視されます。

次の例では、文字列 "Aa" と、"A" と "a" の間に複数の埋め込み null 文字を含む類似の文字列のカルチャに敏感な比較を実行し、どのようにして 2 つの文字列が等しいと見なされるかを示します。

string str1 = "Aa";
string str2 = "A" + new string('\u0000', 3) + "a";

Thread.CurrentThread.CurrentCulture = System.Globalization.CultureInfo.GetCultureInfo("en-us");

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Current Culture: {string.Compare(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Compare(str1, str2, StringComparison.InvariantCulture)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Current Culture: {string.Equals(str1, str2, StringComparison.CurrentCulture)}");
Console.WriteLine($"      Invariant Culture: {string.Equals(str1, str2, StringComparison.InvariantCulture)}");

string ShowBytes(string value)
{
   string hexString = string.Empty;
   for (int index = 0; index < value.Length; index++)
   {
      string result = Convert.ToInt32(value[index]).ToString("X4");
      result = string.Concat(" ", result.Substring(0,2), " ", result.Substring(2, 2));
      hexString += result;
   }
   return hexString.Trim();
}

// The example displays the following output:
//     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
//        With String.Compare:
//           Current Culture: 0
//           Invariant Culture: 0
//        With String.Equals:
//           Current Culture: True
//           Invariant Culture: True

Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Current Culture: {String.Compare(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Compare(str1, str2, StringComparison.InvariantCulture)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Current Culture: {String.Equals(str1, str2, StringComparison.CurrentCulture)}")
        Console.WriteLine($"      Invariant Culture: {String.Equals(str1, str2, StringComparison.InvariantCulture)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '     Comparing 'Aa' (00 41 00 61) and 'Aa' (00 41 00 00 00 00 00 00 00 61):
    '        With String.Compare:
    '           Current Culture: 0
    '           Invariant Culture: 0
    '        With String.Equals:
    '           Current Culture: True
    '           Invariant Culture: True
End Module

ただし、次の例に示すように、序数比較を使用する場合、文字列は等しいとは見なされません。

string str1 = "Aa";
string str2 = "A" + new String('\u0000', 3) + "a";

Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):");
Console.WriteLine("   With String.Compare:");
Console.WriteLine($"      Ordinal: {string.Compare(str1, str2, StringComparison.Ordinal)}");
Console.WriteLine("   With String.Equals:");
Console.WriteLine($"      Ordinal: {string.Equals(str1, str2, StringComparison.Ordinal)}");

string ShowBytes(string str)
{
    string hexString = string.Empty;
    for (int ctr = 0; ctr < str.Length; ctr++)
    {
        string result = Convert.ToInt32(str[ctr]).ToString("X4");
        result = " " + result.Substring(0, 2) + " " + result.Substring(2, 2);
        hexString += result;
    }
    return hexString.Trim();
}

// The example displays the following output:
//    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
//       With String.Compare:
//          Ordinal: 97
//       With String.Equals:
//          Ordinal: False
Module Program
    Sub Main()
        Dim str1 As String = "Aa"
        Dim str2 As String = "A" & New String(Convert.ToChar(0), 3) & "a"

        Console.WriteLine($"Comparing '{str1}' ({ShowBytes(str1)}) and '{str2}' ({ShowBytes(str2)}):")
        Console.WriteLine("   With String.Compare:")
        Console.WriteLine($"      Ordinal: {String.Compare(str1, str2, StringComparison.Ordinal)}")
        Console.WriteLine("   With String.Equals:")
        Console.WriteLine($"      Ordinal: {String.Equals(str1, str2, StringComparison.Ordinal)}")
    End Sub

    Function ShowBytes(str As String) As String
        Dim hexString As String = String.Empty

        For ctr As Integer = 0 To str.Length - 1
            Dim result As String = Convert.ToInt32(str.Chars(ctr)).ToString("X4")
            result = String.Concat(" ", result.Substring(0, 2), " ", result.Substring(2, 2))
            hexString &= result
        Next

        Return hexString.Trim()
    End Function

    ' The example displays the following output:
    '    Comparing 'Aa' (00 41 00 61) and 'A   a' (00 41 00 00 00 00 00 00 00 61):
    '       With String.Compare:
    '          Ordinal: 97
    '       With String.Equals:
    '          Ordinal: False
End Module

その次に慎重な方法は、大文字と小文字を区別しない序数に基づく比較です。 この比較では、大文字と小文字の区別のほとんどが無視されます (たとえば、"windows" と "Windows" は一致します)。 ASCII 文字を扱う場合、このポリシーは通常の ASCII 大文字と小文字の区別を無視する点を除き、 StringComparison.Ordinalと同じです。 したがって、[A, Z] (\u0041-\u005A) 内の任意の文字は、[a,z] (\u0061-\007A) の対応する文字と一致します。 ASCII の範囲外の大文字と小文字の区別には、インバリアント カルチャのテーブルが使用されます。 そのため、次の比較を行います。

string.Compare(strA, strB, StringComparison.OrdinalIgnoreCase);
String.Compare(strA, strB, StringComparison.OrdinalIgnoreCase)

は、この比較と同じです (ただし、より高速です)。

string.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal);
String.Compare(strA.ToUpperInvariant(), strB.ToUpperInvariant(), StringComparison.Ordinal)

とはいえ、これらの比較はどちらも非常に高速です。

StringComparison.OrdinalStringComparison.OrdinalIgnoreCaseの両方でバイナリ値を直接使用し、照合に最適です。 比較設定が不明な場合は、次の 2 つの値のいずれかを使用します。 ただし、バイト単位の比較を実行するため、言語的な並べ替え順序 (英語の辞書など) ではなく、バイナリの並べ替え順序で並べ替えられます。 ユーザーに表示される場合、ほとんどのコンテキストで結果が奇数に見える場合があります。

序数セマンティクスは、String.Equals引数 (等値演算子を含む) を含まないStringComparisonオーバーロードの既定値です。 いずれの場合も、 StringComparison パラメーターを持つオーバーロードを呼び出することをお勧めします。

インバリアント カルチャを使用する文字列操作

インバリアント カルチャとの比較では、静的CompareInfo プロパティによって返されるCultureInfo.InvariantCulture プロパティが使用されます。 この動作はすべてのシステムで同じです。それは、その範囲外の文字を同等の不変文字であると信じるものに変換します。 このポリシーは、カルチャ間で 1 つの文字列の動作セットを維持する場合に役立ちますが、多くの場合、予期しない結果が得られます。

インバリアント カルチャを使用する、大文字と小文字を区別しない比較でも、静的 CompareInfo プロパティから返される静的 CultureInfo.InvariantCulture プロパティが比較情報として使用されます。 変換後の文字の大文字と小文字の違いは無視されます。

StringComparison.InvariantCultureStringComparison.Ordinalを使用する比較は、ASCII 文字列でも同じように機能します。 ただし、 StringComparison.InvariantCulture は、一連のバイトとして解釈する必要がある文字列には適さない言語的な決定を行います。 CultureInfo.InvariantCulture.CompareInfo オブジェクトを使用すると、Compareメソッドは特定の文字セットを等価として解釈します。 たとえば、インバリアント カルチャでは、次の等価性が有効です。

インバリアント カルチャ: a + ̊ = å

LATIN SMALL LETTER A 文字 "a" (\u0061) は、COMBINING RING ABOVE 文字 "+ " ̊" (\u030a) の横にある場合、LATIN SMALL LETTER A WITH RING ABOVE 文字 "å" (\u00e5) として解釈されます。 次の例に示すように、この動作は序数比較とは異なります。

string separated = "\u0061\u030a";
string combined = "\u00e5";

Console.WriteLine($"Equal sort weight of {separated} and {combined} using InvariantCulture: {string.Compare(separated, combined, StringComparison.InvariantCulture) == 0}");

Console.WriteLine($"Equal sort weight of {separated} and {combined} using Ordinal: {string.Compare(separated, combined, StringComparison.Ordinal) == 0}");

// The example displays the following output:
//     Equal sort weight of a° and å using InvariantCulture: True
//     Equal sort weight of a° and å using Ordinal: False
Module Program
    Sub Main()
        Dim separated As String = ChrW(&H61) & ChrW(&H30A)
        Dim combined As String = ChrW(&HE5)

        Console.WriteLine("Equal sort weight of {0} and {1} using InvariantCulture: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.InvariantCulture) = 0)

        Console.WriteLine("Equal sort weight of {0} and {1} using Ordinal: {2}",
                          separated, combined,
                          String.Compare(separated, combined, StringComparison.Ordinal) = 0)

        ' The example displays the following output:
        '     Equal sort weight of a° and å using InvariantCulture: True
        '     Equal sort weight of a° and å using Ordinal: False
    End Sub
End Module

ファイル名、Cookie、または "å" などの組み合わせが表示されるその他の何かを解釈する場合でも、序数の比較では最も透過的で適切な動作が提供されます。

バランスの取れたインバリアント カルチャには、比較に役立つプロパティはほとんどありません。 言語的に関連する方法で比較を行います。これにより、完全なシンボリック等価性が保証されるのを防ぐことができますが、どのカルチャでも表示する選択ではありません。 比較に StringComparison.InvariantCulture を使用するいくつかの理由の 1 つは、異文化同一の表示に対して順序付けされたデータを保持することです。 たとえば、表示用の並べ替えられた識別子のリストを含む大きなデータ ファイルがアプリケーションに付属している場合、このリストに追加するには、インバリアント スタイルの並べ替えを使用して挿入する必要があります。

メソッド呼び出しの StringComparison メンバーの選択

次の表は、セマンティック文字列コンテキストから StringComparison 列挙メンバーへのマッピングの概要を示しています。

データ 行動 対応する System.StringComparison

価値
ケースに依存する内部識別子。

XML や HTTP などの標準の、大文字と小文字が区別される識別子。

大文字と小文字が区別されるセキュリティ関連の設定。
非言語識別子。バイトが正確に一致します。 Ordinal
大文字と小文字が区別されない内部識別子。

XML や HTTP などの標準では、大文字と小文字を区別しない識別子。

ファイル パス。

レジストリ キーと値。

環境変数。

リソース識別子 (ハンドル名など)。

ケースを区別しないセキュリティ関連の設定。
大文字と小文字の区別に関係ない非言語的識別子。 OrdinalIgnoreCase
永続化される、言語的な意味を持つデータの一部。

固定の並べ替え順序を必要とする言語データの表示。
言語的に関連性がある、文化的に依存しないデータ。 InvariantCulture

-又は-

InvariantCultureIgnoreCase
ユーザーに表示されるデータ。

ほとんどのユーザー入力。
現地の言語習慣を必要とするデータ。 CurrentCulture

-又は-

CurrentCultureIgnoreCase

.NET の一般的な文字列比較メソッド

次のセクションでは、文字列比較に最も一般的に使用されるメソッドについて説明します。

String.Compare

既定の解釈: StringComparison.CurrentCulture

文字列の解釈の最も中心となる操作として、これらのメソッド呼び出しのすべてのインスタンスを調べて、文字列を現在のカルチャに従って解釈するか、カルチャから関連付けを解除するか (シンボリックに) 判断する必要があります。 通常は後者であり、代わりに StringComparison.Ordinal 比較を使用する必要があります。

System.Globalization.CompareInfo プロパティによって返される CultureInfo.CompareInfo クラスには、Compare フラグ列挙によって多数の一致オプション (序数、空白の無視、かな型の無視など) を提供するCompareOptions メソッドも含まれています。

String.CompareTo

既定の解釈: StringComparison.CurrentCulture

このメソッドは現在、 StringComparison 型を指定するオーバーロードを提供していません。 通常、このメソッドを推奨される String.Compare(String, String, StringComparison) 形式に変換できます。

IComparableインターフェイスとIComparable<T> インターフェイスを実装する型は、このメソッドを実装します。 StringComparison パラメーターのオプションは提供されないため、型を実装すると、多くの場合、ユーザーはコンストラクターでStringComparerを指定できます。 次の例では、クラス コンストラクターに FileName パラメーターが含まれるStringComparer クラスを定義します。 この StringComparer オブジェクトは、 FileName.CompareTo メソッドで使用されます。

class FileName : IComparable
{
    private readonly StringComparer _comparer;

    public string Name { get; }

    public FileName(string name, StringComparer? comparer)
    {
        if (string.IsNullOrEmpty(name)) throw new ArgumentNullException(nameof(name));

        Name = name;

        if (comparer != null)
            _comparer = comparer;
        else
            _comparer = StringComparer.OrdinalIgnoreCase;
    }

    public int CompareTo(object? obj)
    {
        if (obj == null) return 1;

        if (obj is not FileName)
            return _comparer.Compare(Name, obj.ToString());
        else
            return _comparer.Compare(Name, ((FileName)obj).Name);
    }
}
Class FileName
    Implements IComparable

    Private ReadOnly _comparer As StringComparer

    Public ReadOnly Property Name As String

    Public Sub New(name As String, comparer As StringComparer)
        If (String.IsNullOrEmpty(name)) Then Throw New ArgumentNullException(NameOf(name))

        Me.Name = name

        If comparer IsNot Nothing Then
            _comparer = comparer
        Else
            _comparer = StringComparer.OrdinalIgnoreCase
        End If
    End Sub

    Public Function CompareTo(obj As Object) As Integer Implements IComparable.CompareTo
        If obj Is Nothing Then Return 1

        If TypeOf obj IsNot FileName Then
            Return _comparer.Compare(Name, obj.ToString())
        Else
            Return _comparer.Compare(Name, DirectCast(obj, FileName).Name)
        End If
    End Function
End Class

String.Equals

既定の解釈: StringComparison.Ordinal

String クラスを使用すると、静的またはインスタンスEqualsメソッドのオーバーロードを呼び出すか、静的等値演算子を使用して等価性をテストできます。 オーバーロードと演算子は、既定で序数比較を使用します。 ただし、序数比較を実行する場合でも、 StringComparison 型を明示的に指定するオーバーロードを呼び出することをお勧めします。これにより、特定の文字列解釈のコードを簡単に検索できます。

String.ToUpper と String.ToLower

既定の解釈: StringComparison.CurrentCulture

String.ToUpper()メソッドとString.ToLower()メソッドを使用する場合は注意してください。大文字または小文字に文字列を強制することは、大文字と小文字に関係なく文字列を比較するための小さな正規化としてよく使用されるためです。 その場合は、大文字と小文字を区別しない比較を使用することを検討してください。

String.ToUpperInvariantメソッドとString.ToLowerInvariant メソッドも使用できます。 ToUpperInvariant は、ケースを正規化する標準的な方法です。 StringComparison.OrdinalIgnoreCaseを使用して行われた比較は、動作上、両方の文字列引数でToUpperInvariantを呼び出し、StringComparison.Ordinalを使用して比較を行うという 2 つの呼び出しの構成です。

オーバーロードは、そのカルチャを表す CultureInfo オブジェクトをメソッドに渡すことによって、特定のカルチャで大文字と小文字に変換することもできます。

Char.ToUpper と Char.ToLower

既定の解釈: StringComparison.CurrentCulture

Char.ToUpper(Char)メソッドとChar.ToLower(Char)メソッドは、前のセクションで説明したString.ToUpper()メソッドとString.ToLower()メソッドと同様に機能します。

String.StartsWith と String.EndsWith

既定の解釈: StringComparison.CurrentCulture

既定では、これらのメソッドの両方でカルチャに依存する比較が実行されます。 特に、印刷されない文字は無視される場合があります。

String.IndexOf と String.LastIndexOf

既定の解釈: StringComparison.CurrentCulture

これらのメソッドの既定のオーバーロードが比較を実行する方法に一貫性がありません。 String.IndexOf パラメーターを含むすべてのString.LastIndexOfメソッドとChar メソッドは序数比較を実行しますが、String.IndexOf パラメーターを含む既定のString.LastIndexOfメソッドとString メソッドはカルチャに依存する比較を実行します。

String.IndexOf(String)またはString.LastIndexOf(String)メソッドを呼び出し、現在のインスタンス内で検索する文字列を渡す場合は、StringComparison型を明示的に指定するオーバーロードを呼び出することをお勧めします。 Char引数を含むオーバーロードでは、StringComparison型を指定できません。

文字列比較を間接的に実行するメソッド

一元的な操作として文字列比較を行う一部の文字列以外のメソッドでは、 StringComparer 型が使用されます。 StringComparer クラスには、StringComparer メソッドが次の種類の文字列比較を実行StringComparer.Compareインスタンスを返す 6 つの静的プロパティが含まれています。

Array.Sort と Array.BinarySearch

既定の解釈: StringComparison.CurrentCulture

コレクションにデータを格納したり、ファイルまたはデータベースから永続化されたデータをコレクションに読み取ったりすると、現在のカルチャを切り替えると、コレクション内のインバリアントが無効になることがあります。 Array.BinarySearchメソッドは、検索対象の配列内の要素が既に並べ替えられていることを前提としています。 配列内の任意の文字列要素を並べ替えるために、 Array.Sort メソッドは String.Compare メソッドを呼び出して個々の要素を並べ替えます。 カルチャに依存する比較子を使用すると、配列の並べ替えと内容の検索の間でカルチャが変化すると、危険な場合があります。 たとえば、次のコードでは、ストレージと取得は、 Thread.CurrentThread.CurrentCulture プロパティによって暗黙的に提供される比較子に対して動作します。 StoreNamesDoesNameExistの呼び出しの間でカルチャが変更される可能性がある場合、特に配列の内容が 2 つのメソッド呼び出しの間のどこかに保持されている場合、バイナリ検索が失敗する可能性があります。

// Incorrect
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name) >= 0; // Line B
' Incorrect
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name) >= 0 ' Line B
End Function

次の例では、配列の並べ替えと検索の両方に同じ序数 (カルチャを区別しない) 比較メソッドを使用する、推奨されるバリエーションが示されています。 変更コードは、2 つの例で Line ALine B というラベルが付いた行に反映されます。

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.Ordinal); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.Ordinal) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.Ordinal) >= 0 ' Line B
End Function

このデータが保持され、カルチャ間で移動され、並べ替えを使用してこのデータをユーザーに表示する場合は、 StringComparison.InvariantCultureを使用することを検討してください。これは、ユーザー出力を向上させるために言語的に動作しますが、カルチャの変更の影響を受けません。 次の例では、配列の並べ替えと検索にインバリアント カルチャを使用するように、前の 2 つの例を変更します。

// Correct
string[] _storedNames;

public void StoreNames(string[] names)
{
    _storedNames = new string[names.Length];

    // Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length);

    Array.Sort(_storedNames, StringComparer.InvariantCulture); // Line A
}

public bool DoesNameExist(string name) =>
    Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0; // Line B
' Correct
Dim _storedNames As String()

Sub StoreNames(names As String())
    ReDim _storedNames(names.Length - 1)

    ' Copy the array contents into a new array
    Array.Copy(names, _storedNames, names.Length)

    Array.Sort(_storedNames, StringComparer.InvariantCulture) ' Line A
End Sub

Function DoesNameExist(name As String) As Boolean
    Return Array.BinarySearch(_storedNames, name, StringComparer.InvariantCulture) >= 0 ' Line B
End Function

コレクションの例: Hashtable コンストラクター

ハッシュ文字列は、文字列の比較方法の影響を受ける操作の 2 番目の例を提供します。

次の例では、Hashtable プロパティによって返されるStringComparer オブジェクトを渡すことによって、StringComparer.OrdinalIgnoreCase オブジェクトをインスタンス化します。 StringComparerから派生したクラスStringComparerIEqualityComparer インターフェイスを実装するため、GetHashCode メソッドを使用してハッシュ テーブル内の文字列のハッシュ コードを計算します。

using System.IO;
using System.Collections;

const int InitialCapacity = 100;

Hashtable creationTimeByFile = new(InitialCapacity, StringComparer.OrdinalIgnoreCase);
string directoryToProcess = Directory.GetCurrentDirectory();

// Fill the hash table
PopulateFileTable(directoryToProcess);

// Get some of the files and try to find them with upper cased names
foreach (var file in Directory.GetFiles(directoryToProcess))
    PrintCreationTime(file.ToUpper());


void PopulateFileTable(string directory)
{
    foreach (string file in Directory.GetFiles(directory))
        creationTimeByFile.Add(file, File.GetCreationTime(file));
}

void PrintCreationTime(string targetFile)
{
    object? dt = creationTimeByFile[targetFile];

    if (dt is DateTime value)
        Console.WriteLine($"File {targetFile} was created at time {value}.");
    else
        Console.WriteLine($"File {targetFile} does not exist.");
}
Imports System.IO

Module Program
    Const InitialCapacity As Integer = 100

    Private ReadOnly s_creationTimeByFile As New Hashtable(InitialCapacity, StringComparer.OrdinalIgnoreCase)
    Private ReadOnly s_directoryToProcess As String = Directory.GetCurrentDirectory()

    Sub Main()
        ' Fill the hash table
        PopulateFileTable(s_directoryToProcess)

        ' Get some of the files and try to find them with upper cased names
        For Each File As String In Directory.GetFiles(s_directoryToProcess)
            PrintCreationTime(File.ToUpper())
        Next
    End Sub

    Sub PopulateFileTable(directoryPath As String)
        For Each file As String In Directory.GetFiles(directoryPath)
            s_creationTimeByFile.Add(file, IO.File.GetCreationTime(file))
        Next
    End Sub

    Sub PrintCreationTime(targetFile As String)
        Dim dt As Object = s_creationTimeByFile(targetFile)

        If TypeOf dt Is Date Then
            Console.WriteLine($"File {targetFile} was created at time {DirectCast(dt, Date)}.")
        Else
            Console.WriteLine($"File {targetFile} does not exist.")
        End If
    End Sub
End Module

こちらも参照ください