C#で文字列にSQLのIN句のようなメソッドを追加する

SQLにあるIN句をC#の文字列に適用し、リストに格納された文字列がある文字列に一致するかどうかを判定する処理をstringに追加したいと思います。

例えば"ABC123"という文字列の中に"AB", "12"という文字が含まれているかという処理は以下のように書けば判定できます。

string str = "ABC123";

// ★判定方法(1)
if(str == "AB" || str == "12")
{
    // 見つかった
}
else
{
    // 見つからなかった
}

// ★判定方法(2)
List<string> keys = new List<string>()
{
    "AB", "12"
};
foreach(string item in keys)
{
    if(str == item)
    {
        // みつかった
    }
}

(1)の場合、対象が個数が増えていくと重複したコードが増えるし(2)だと見つからないときに何かしら追加が必要です。

どうせなら1行でかけたらいいのに…という実装アイデアです。

コード例

早速ですがコード例です。

stringクラスにInメソッドを拡張メソッドとして定義します。

IN句なので完全一致判定となります。

// StringExtension.cs

/// <summary>
/// <see cref="string"/>  クラスの拡張メソッドを定義します。
/// </summary>
public static class StringExtension
{
    // ★パターン(1)
    // 指定した文字列中に複数の要素の中から1つでも一致するものが含まれているかどうかを確認する
    public static bool In(this string str, params string[] items)
    {
        return items.Contains(str); // 逆にすれば Linq で判定できる (完全一致判定)
    }
    public static bool In(this string str, bool ignoreCase, params string[] items)
    {
        return items.Contains(str, comparer); // 逆にする + 大文字小文字を区別しない (完全一致反転)
    }
    
    // ★パターン(2)
    // 指定した文字列中に複数の要素の中から1つでも一致するものが含まれているかどうかを確認する
    public static bool In(this string str, IEnumerable<string> items)
    {
        return items.Contains(str);
    }
    public static bool In(this string str, bool ignoreCase, IEnumerable<string> items)
    {
        return ignoreCase ? items.Contains(str, comparer) : In(str, items);
    }

    // 大文字/小文字を区別しない実装
    private static readonly Exp comparer = new Exp();
    private class Exp : IEqualityComparer<string>
    {
        public bool Equals(string x, string y)
        {
            return string.Compare(x, y, true) == 0;
        }
        public int GetHashCode(string obj)
        {
            return obj.GetHashCode();
        }
    }
}

使い方

上記クラスの使い方は以下の通りです。

リストと配列、IEnumerableが全て指定できます。Spanでも実装できますが…

大文字と小文字を区別しない場合、第1引数はtrueにします。

static void Main(string[] args)
{
    string str = "ABC123";

    // パターン(1)でチェック その1
    bool contains = str.In(false, "XX", "99", "AB", "12");
    if (contains)
    {
        // 見つかった
    }
    else
    {
        // 見つからない
    }

    // パターン(1)でチェック その2
    string[] items = new string[] { "XX", "99", "AB", "12" };
    contains = str.In(false, items);
    if (contains)
    {
        // 見つかった
    }
    else
    {
        // 見つからない
    }


    // パターン(2)でチェック
    IList<string> list = new List<string>() { "XX", "99", "AB", "12" };
    contains = str.In(false, list);
    if (contains)
    {
        // 見つかった
    }
    else
    {
        // 見つからない
    }

    // パター(2)でチェック
    IEnumerable<string> f()
    {
        yield return "XX";
        yield return "99";
        yield return "AB";
        yield return "12";
    }

    contains = str.In(false, f());
    if (contains)
    {
        // 見つかった
    }
    else
    {
        // 見つからない
    }
}

これで複数の候補の中から完全一致するものがあるかどうかをstringクラスで判定できます。

Containsを拡張する

余談ですが、string.Contains を複数の文字列の中から部分一致するものがあるか判定する処理も記載しておきます。

public static class StringExtension
{
    public static bool Contains(this string str, params string[] keywords)
    {
        foreach (var word in keywords)
        {
            if (str.Contains(word)) return true;
        }
        return false;
    }
}

こうしておくと指定したキーワードの中に部分一致するものがあれば true を返すようになります。こっちのほうがよく使うかもしれません。