【C#】拡張メソッドを使用して「列挙値のパターンマッチ」を簡潔にする。

※全ソースコードは記事の最後のほうにあります。

問題点

利き手を表す列挙型DominantHandのプロパティを持つクラスhumansがあるとします。

/// <summary>
/// 人間を表します
/// </summary>
public class Human
{
    /// <summary>
    /// 名前
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 利き手
    /// </summary>
    public DominantHand DominantHand { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="name"></param>
    /// <param name="dominantHand"></param>
    public Human(string name, DominantHand dominantHand)
    {
        this.Name = name;
        this.DominantHand = dominantHand;
    }
}

なお、列挙型DominantHandには左/右/両手の3つの列挙値が定義されているとします。

/// <summary>
///  利き手を表します
/// </summary>
public enum DominantHand
{
    /// <summary>左手</summary>
    Left,
    /// <summary>右手</summary>
    Right,
    /// <summary>両手</summary>
    Both,
}

このようなクラスHumanを要素としてもつリストHumansがあるとします。

var humans = new List<Human>
{
    new Human("佐藤(L)", DominantHand.Left),
    new Human("佐藤(R)", DominantHand.Right),
    new Human("田中(L)", DominantHand.Left),
    new Human("田中(B)", DominantHand.Both),
};

このとき「利き手が左手もしくは右手」の要素(Human)のみを取得したい場合、単純に考えるとLINQのWhere()とOR演算子「||」を使って、つらつらと書いていくことになるでしょう。

// 利き手が[左手]もしくは[右手]の人のみ抽出
var oneSideHumans = humans.Where(x => x.DominantHand == DominantHand.Left || x.DominantHand == DominantHand.Right);

しかしこのような記述では、列挙値が増えた場合にWhere()の中身が長くなりわかりずらくなってしまいます。(「利き手」なので列挙値が100個みたいな大量になることはないと思いますが…。)

対策:拡張メソッドを作成

そこで列挙型に対して拡張メソッドIsMatch()を作成して、Where()の記述を簡潔になるようにします。

/// <summary>
/// 列挙体に対する拡張メソッド
/// </summary>
public static class EnumExtensions
{
    /// <summary>
    /// 列挙した列挙値のいずれかを持つかを判定します。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sorceEnum"></param>
    /// <param name="targetEnum"></param>
    /// <returns></returns>
    public static bool IsMatch<T>(this T sourceEnum, params T[] targetEnum)
        where T : Enum
    {
        return targetEnum.Any(x => EqualityComparer<T>.Default.Equals(x, sourceEnum));

    }
}
// 利き手が[左手]もしくは[右手]の人のみ抽出
var oneSideHumans = humans.Where(x => x.DominantHand.IsMatch(DominantHand.Left, DominantHand.Right));

列挙値の増加によるコードの長文かを抑えることができ、さらに条件となる列挙値がぱっと見でわかりやすくなったと思います。

全ソースコード

public  class Program
{
    static void Main(string[] args)
    {
        var humans = new List<Human>
        {
            new Human("佐藤(L)", DominantHand.Left),
            new Human("佐藤(R)", DominantHand.Right),
            new Human("田中(L)", DominantHand.Left),
            new Human("田中(B)", DominantHand.Both),
        };

        // 利き手が[左手]もしくは[右手]の人のみ抽出
        var oneSideHumans = humans.Where(x => x.DominantHand.IsMatch(DominantHand.Left, DominantHand.Right));

        foreach (var oneSideHuman in oneSideHumans)
        {
            Console.WriteLine(oneSideHuman.GetInfomation());
        }
    }
}

/// <summary>
/// 列挙体に対する拡張メソッド
/// </summary>
public static class EnumExtensions
{
    /// <summary>
    /// 列挙した列挙値のいずれかを持つかを判定します。
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="sorceEnum"></param>
    /// <param name="targetEnum"></param>
    /// <returns></returns>
    public static bool IsMatch<T>(this T sourceEnum, params T[] targetEnum)
        where T : Enum
    {
        return targetEnum.Any(x => EqualityComparer<T>.Default.Equals(x, sourceEnum));

    }
}

/// <summary>
/// 人間を表します
/// </summary>
public class Human
{
    /// <summary>
    /// 名前
    /// </summary>
    public string Name { get; set; }
    /// <summary>
    /// 利き手
    /// </summary>
    public DominantHand DominantHand { get; set; }

    /// <summary>
    /// コンストラクタ
    /// </summary>
    /// <param name="name"></param>
    /// <param name="dominantHand"></param>
    public Human(string name, DominantHand dominantHand)
    {
        this.Name = name;
        this.DominantHand = dominantHand;
    }

    /// <summary>
    /// 情報を表す文字列を取得します
    /// </summary>
    /// <returns></returns>
    public string GetInfomation()
    {
        return $"{this.Name}:{this.DominantHand}";
    }
}

/// <summary>
///  利き手を表します
/// </summary>
public enum DominantHand
{
    /// <summary>左手</summary>
    Left,
    /// <summary>右手</summary>
    Right,
    /// <summary>両手</summary>
    Both,
}

佐藤(L):Left
佐藤(R):Right
田中(L):Left

最後に

今回の例に限らず、拡張メソッドを使用するとコードが簡潔になる場合があります。

拡張メソッドは変更不可能なクラス・列挙型に対して、外部から疑似的にメソッドを追加できるます。そのため拡張メソッドを利用することで、今回のように自分好みの便利なメソッドを作ることができます。

「メソッドが無いから頑張ってゴリゴリ書かないと…」とあきらめるまえに、拡張メソッドを使うという選択肢も考えてみると良いと思いますよ。

コメント