C#の三項演算子をリファクタリングする

C#で三項演算子って使ってますか?

タイトルの件ですが、条件判定で、三項演算子を使った以下のようなコート見たことありませんか?

public void SelectString(string text)
{
    // text の中身が "animal" だったら0を返す。そうでい場合1を返す
    int index = text == "animal" ? 0 : 1;
}

次に上記メソッドに text の中身が "plant" だったら2を返す処理を追加する修正を行うとします。

この時一番やってはいけないのは三項演算子の偽の項に更に三項演算子を追加する方法です。

public void SelectString(string text)
{
    // あかんやつ
    int index = text == "animal" ? 0 : text == "plant" ? 2 : 1;
}

エクセルの関数風となり、可読性が一気に低下します。

三項演算子の利点は1行で書けることですが、構文自体に拡張性がほぼ無いため変更が発生しそうな "柔らかい" 場所には不向きと言えます。

で、上記のような例をどうしたらいいかという話ですが

  • 素直にメソッド化する
  • テーブルにする
  • ラムダ式を使う

の3つがあります。それぞれ紹介したいと思います。

素直にメソッド化する

素直にメソッド化する場合、新しく文字列を整数に変換するメソッドを作成します。よくある一般的な方法です。

public void SelectString(string text)
{
    int index = ToIndex(text); // メソッドを呼び出すように修正
}

// 変換するメソッドを追加
public static int ToIndex(string text)
{
    if(text == "animal") 
    {
        return 0;
    }
    else if(text == "plant")
    {
        return 2;
    }
    
    return 1;
}

メソッドは static を追加しています。

「ある文字列を数値に変換する処理」はこのオブジェクトとは性質が関係ない見る事もできるため、「変換する知識クラス」として外に出しても良いかもしれません。

テーブルを使用する場合

テーブルはC#では、Dictionary クラスを使って表現できるので文字列と整数の組み合わせをあらかじめ定義組み合わせを宣言しておきます。

// あらかじめテーブルを定義する
private static readonly IDcitionary<string, int> indexTable = new Dcitionary<string, int>()
{
    { "animal", 0 },
    { "plant", 2 },
};

public void SelectString(string text)
{
    // 見つからなかった場合を最初に入れておく
    int index = 1;
    
    // テーブルにあればそこから取り出す
    if(indexTable.ContainsKey(text))
    {
        index = indexTable[text];
    }
}

既存の三項演算子を大切する記述もできます。あまりおススメはできませんが。

// 三項演算子を置き換える
int index = indexTable.ContainsKey(text) ? indexTable[text] : 1;

ラムダ式を使う

ラムダ式を使うパターンですが、もし万が一、クラスにメソッドを追加してはいけない等という規則があれば使うことになります。

public void SelectString(string text)
{
    // ラムダ式で処理を記述する
    Func<string, int> toIndex = _txt =>
    {
        if(text == "animal") 
        {
            return 0;
        }
        else if(text == "plant")
        {
            return 2;
        }
        
        return 1;
    };
    
    int index = toIndex(text);
}

簡単ですが以上です。