【C#】カルチャに依存しない文字列比較:StringComparison.Ordinal の重要性

目次

文字列比較の落とし穴:「カルチャ」依存

C#で2つの文字列を比較する際、== 演算子や引数なしの Equals() メソッドを使うのが最も簡単です。

string input = "windows";
string constant = "WINDOWS";

bool isEqual = (input == constant); // false
bool isEqualEquals = input.Equals(constant); // false

これらの比較は、デフォルトで大文字と小文字を厳密に区別します。では、大文字・小文字を区別せずに比較したい場合はどうでしょうか。ToLowerInvariant()などで小文字に統一する方法もありますが、C#にはより適切な「比較ルール」を指定する方法が用意されています。

ここで重要になるのが、「カルチャ(地域設定)」の問題です。


なぜカルチャを意識する必要があるのか?

string.Equals(stringA, stringB, StringComparison.CurrentCultureIgnoreCase) のように、CurrentCulture(現在のカルチャ)を指定すると、プログラムが実行されているOSの地域設定(例: “ja-JP” や “en-US”)に基づいて比較が行われます。

これは、ユーザーに表示する文字列をソート(並べ替え)する際には正しい動作です。

しかし、**内部的なデータ(ID、ファイル名、APIキー、プロトコル名)**を比較する際にカルチャに依存すると、深刻なバグを引き起こす可能性があります。最も有名な例が「トルコ語の i」です。

  • 英語(Invariant Culture): i の大文字は I
  • トルコ語(tr-TR): i の大文字は İ (ドット付き)、I の小文字は ı (ドットなし)

もし、プログラムがトルコ語環境で実行された場合、"file" という文字列と "FILE" という文字列を CurrentCultureIgnoreCase で比較すると、iI のマッピングが異なるため、比較が false になる可能性があります。


解決策:StringComparison の指定

このようなカルチャ依存のバグを避け、予測可能で一貫性のある比較を行うために、string.Equalsstring.Compare メソッドには比較ルール(StringComparison 列挙型)を指定するオーバーロードが用意されています。

内部的なデータ比較や、セキュリティに関わる比較で推奨されるのは Ordinal です。

StringComparison.Ordinal (序数比較)

  • 動作: 文字列を単純なバイト列(Unicodeの序数値)として比較します。
  • カルチャ: 完全に無視します(最速)。
  • 大文字/小文字: 区別します
  • 用途: ファイルパス、APIキー、JSONプロパティ名、ミューテックス名など、プログラム内部で扱うデータや、機械が解釈する文字列の厳密な一致判定。

StringComparison.OrdinalIgnoreCase (序数比較・大文字小文字無視)

  • 動作: バイト列として比較しますが、大文字と小文字の区別を無視します
  • カルチャ: 完全に無視します(高速)。
  • 大文字/小文字: 無視します
  • 用途: ユーザー名(adminADMINを同一視)、HTTPヘッダー、環境変数名など、大文字・小文字を区別しないが、カルチャには依存させたくない場合の比較。

コード例:OrdinalOrdinalIgnoreCase

"WINDOWS""windows" という2つの文字列を、カルチャに依存しない方法で比較します。

using System;
using System.Globalization;

public class OrdinalComparisonExample
{
    public static void Main()
    {
        string id1 = "WINDOWS-KEY-001";
        string id2 = "windows-key-001";

        Console.WriteLine($"文字列A: {id1}");
        Console.WriteLine($"文字列B: {id2}");
        Console.WriteLine("---");

        // --- 1. Ordinal (厳密なバイト比較) ---
        // 大文字と小文字は区別されるため、false になる
        bool isEqualOrdinal = id1.Equals(id2, StringComparison.Ordinal);
        Console.WriteLine($"Ordinal (区別する): {isEqualOrdinal}");

        // --- 2. OrdinalIgnoreCase (バイト比較・大文字小文字無視) ---
        // カルチャに依存せず、大文字・小文字を無視するため、true になる
        // ユーザー名やキーワードの比較に最適
        bool isEqualOrdinalIgnoreCase = id1.Equals(id2, StringComparison.OrdinalIgnoreCase);
        Console.WriteLine($"OrdinalIgnoreCase (無視する): {isEqualOrdinalIgnoreCase}");
        
        // --- 3. StartsWith や EndsWith でも同様 ---
        string prefix = "windows";
        
        // "WINDOWS-KEY..." が "windows" (小文字) で始まるか?
        bool startsWithIgnoreCase = id1.StartsWith(prefix, StringComparison.OrdinalIgnoreCase);
        Console.WriteLine($"StartsWith (無視する): {startsWithIgnoreCase}");
    }
}

出力結果:

文字列A: WINDOWS-KEY-001
文字列B: windows-key-001
---
Ordinal (区別する): False
OrdinalIgnoreCase (無視する): True
StartsWith (無視する): True

まとめ

C#で文字列を比較(Equals, Compare, StartsWith, EndsWithなど)する際は、常に比較ルールを意識する必要があります。

  • 内部データ、ID、キー、ファイルパスなど:
    • 大文字・小文字を区別する場合: StringComparison.Ordinal
    • 大文字・小文字を無視する場合: StringComparison.OrdinalIgnoreCase
  • ユーザーに表示する文字列の並べ替え:
    • StringComparison.CurrentCulture

セキュリティや意図しない動作(バグ)を防ぐため、== 演算子や引数なしの Equals() の使用は避け、StringComparison を明示的に指定することが強く推奨されます。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

私が勉強したこと、実践したこと、してることを書いているブログです。
主に資産運用について書いていたのですが、
最近はプログラミングに興味があるので、今はそればっかりです。

目次