ざこノート
2018-05-22 [C# ]

[C#] よく使う文字列の比較判定を高速で安全に

高速な完全比較。大文字/小文字、半角/全角、ひらがな/カタカナの比較方法。

環境

  • Windows 10 Pro 64bit 1709
  • Visual Studio Community 2017 15.5.7
  • .NET Framework 4.6.1
  • C# 7.0

日本語環境を前提とします。

// "ja-JP"
Console.WriteLine(System.Globalization.CultureInfo.CurrentCulture.Name);

結果

拡張メソッドを実装した使用例です。

// 完全比較 (高速で確実(環境非依存)なバイナリ比較)
s1 == s2;                  // 通常のやり方で良い
s1.Contains(s2);           // 通常のやり方で良い
s1.CompareToOptions(s2);
s1.IsPrefixOptions(s2);    // s1.StartsWith(s2, StringComparison.Ordinal) と同じ
s1.IsSuffixOptions(s2);    // s1.EndsWith(s2, StringComparison.Ordinal) と同じ
s1.IndexOfOptions(s2);     // s1.IndexOf(s2, StringComparison.Ordinal) と同じ
s1.LastIndexOfOptions(s2); // s1.LastIndexOf(s2, StringComparison.Ordinal) と同じ

// 大文字/小文字を無視 (少し高速で確実(環境非依存)なバイナリ比較)
s1.Equals(s2, StringComparison.OrdinalIgnoreCase);           // 通常のメソッドにオプション指定
s1.ContainsOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.IndexOf(s2, StringComparison.OrdinalIgnoreCase) >= 0 と同じ
s1.CompareToOptions(s2, CompareOptions.OrdinalIgnoreCase);
s1.IsPrefixOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.StartsWith(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.IsSuffixOptions(s2, CompareOptions.OrdinalIgnoreCase);    // s1.EndsWith(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.IndexOfOptions(s2, CompareOptions.OrdinalIgnoreCase);     // s1.IndexOf(s2, StringComparison.OrdinalIgnoreCase) と同じ
s1.LastIndexOfOptions(s2, CompareOptions.OrdinalIgnoreCase); // s1.LastIndexOf(s2, StringComparison.OrdinalIgnoreCase) と同じ

// ひらがな/カタカナを無視
s1.EqualsOptions(s2, CompareOptions.IgnoreKanaType);
s1.ContainsOptions(s2, CompareOptions.IgnoreKanaType);
s1.CompareToOptions(s2, CompareOptions.IgnoreKanaType);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreKanaType);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreKanaType);
s1.IndexOfOptions(s2, CompareOptions.IgnoreKanaType);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreKanaType);

// 全角/半角を無視
s1.EqualsOptions(s2, CompareOptions.IgnoreWidth);
s1.ContainsOptions(s2, CompareOptions.IgnoreWidth);
s1.CompareToOptions(s2, CompareOptions.IgnoreWidth);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreWidth);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreWidth);
s1.IndexOfOptions(s2, CompareOptions.IgnoreWidth);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreWidth);

// ひらがな/カタカナ、全角/半角、大文字/小文字を無視
s1.EqualsOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.ContainsOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.CompareToOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IsPrefixOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IsSuffixOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.IndexOfOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);
s1.LastIndexOfOptions(s2, CompareOptions.IgnoreKanaType | CompareOptions.IgnoreWidth | CompareOptions.IgnoreCase);

以下は完全一致ではなく(言語環境に依存するし)高速でもないため、通常は使用しなくて良いと思います。

s1.CompareTo(s2);
s1.IndexOf(s2);
s1.LastIndexOf(s2);
s1.StartsWith(s2);
s1.EndsWith(s2);

CompareOptions 列挙型 (System.Globalization)

項目 カルチャ 内容 速度
Ordinal 非依存 完全一致、バイナリ比較 高速
OrdinalIgnoreCase 非依存 大文字と小文字を無視、バイナリ比較 少し高速
None 依存 遅い
IgnoreCase 依存 大文字と小文字を無視 遅い
IgnoreKanaType 依存 ひらがなとカタカナを無視 遅い
IgnoreWidth 依存 全角と半角を無視 遅い
IgnoreNonSpace 依存 濁点、発音記号、①の○を無視 遅い
IgnoreSymbols 依存 空白、句読点、通貨記号、数学記号、%や&などの記号を無視 遅い
StringSort 依存 ハイフンやアポストロフィが英数字や他記号よりも前になる比較 遅い

StringComparison 列挙型 (System)

実装

public static class StringExtension
{
    private const CompareOptions Ordinal = CompareOptions.Ordinal;

    public static int CompareToOptions(this string string1, string string2, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, string2, options);

    public static int CompareToOptions(this string string1, int offset1, string string2, int offset2,
        CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, string2, offset2, options);

    public static int CompareToOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, options);

    public static bool ContainsOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options) >= 0;

    public static bool ContainsOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options) >= 0;

    public static bool ContainsOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options) >= 0;

    public static bool ContainsOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options) >= 0;

    public static bool ContainsOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options) >= 0;

    public static bool ContainsOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options) >= 0;

    public static bool EqualsOptions(this string string1, string string2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(string2, options) == 0;

    public static bool EqualsOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(offset1, string2, offset2, options) == 0;

    public static bool EqualsOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal) =>
        string1.CompareToOptions(offset1, length1, string2, offset2, length2, options) == 0;

    public static bool IsPrefixOptions(this string source, string prefix, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IsPrefix(source, prefix, options);

    public static bool IsSuffixOptions(this string source, string suffix, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IsSuffix(source, suffix, options);

    private static int IndexOfOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);

    public static int IndexOfOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);

    public static int IndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);

    public static int IndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);

    public static int IndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int IndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int LastIndexOfOptions(this string source, char value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);

    public static int LastIndexOfOptions(this string source, string value, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);

    public static int LastIndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);

    public static int LastIndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);

    public static int LastIndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);

    public static int LastIndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal) =>
        CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, count, options);
}

C# 6.0 より前のコードです。

using System.Globalization;

public static class StringExtension
{
    private const CompareOptions Ordinal = CompareOptions.Ordinal;

    public static int CompareToOptions(this string string1, string string2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, string2, options);
    }

    public static int CompareToOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, string2, offset2, options);
    }

    public static int CompareToOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.Compare(string1, offset1, length1, string2, offset2, length2, options);
    }

    public static bool EqualsOptions(this string string1, string string2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(string2, options) == 0;
    }

    public static bool EqualsOptions(this string string1, int offset1, string string2, int offset2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(offset1, string2, offset2, options) == 0;
    }

    public static bool EqualsOptions(this string string1, int offset1, int length1, string string2, int offset2, int length2, CompareOptions options = Ordinal)
    {
        return string1.CompareToOptions(offset1, length1, string2, offset2, length2, options) == 0;
    }

    public static bool IsPrefixOptions(this string source, string prefix, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IsPrefix(source, prefix, options);
    }

    public static bool IsSuffixOptions(this string source, string suffix, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IsSuffix(source, suffix, options);
    }

    public static int IndexOfOptions(this string source, char value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);
    }

    public static int IndexOfOptions(this string source, string value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, options);
    }

    public static int IndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);
    }

    public static int IndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, options);
    }

    public static int IndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int IndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int LastIndexOfOptions(this string source, char value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);
    }

    public static int LastIndexOfOptions(this string source, string value, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, options);
    }

    public static int LastIndexOfOptions(this string source, char value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);
    }

    public static int LastIndexOfOptions(this string source, string value, int startIndex, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, options);
    }

    public static int LastIndexOfOptions(this string source, char value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.IndexOf(source, value, startIndex, count, options);
    }

    public static int LastIndexOfOptions(this string source, string value, int startIndex, int count, CompareOptions options = Ordinal)
    {
        return CultureInfo.CurrentCulture.CompareInfo.LastIndexOf(source, value, startIndex, count, options);
    }
}

感謝