画面処理でExpression Builderを使ってみる

まずは、ベースになるWindowsアプリケーションの画面処理です。よくあるコードで、4桁までの数値を入力検証します。このコードを改善していきます。

textBox1.MaxLength = 4;
textBox1.ImeMode = ImeMode.Off;
textBox1.Validating +=new CancelEventHandler(textBox1_Validating);

private void textBox1_Validating(object sender, CancelEventArgs e)
{
    string errMessage = "";
    if (this.textBox1.Text.Length == 0)
    {
        errMessage = "入力してください";
    }
    else
    {
        int val;
        if (!int.TryParse(this.textBox1.Text, out val))
        {
            errMessage = "整数で入力してください";
        }
    }
    this.errorProvider1.SetError(this.textBox1, errMessage);
}

まずは最初の改善はValidatingイベントの処理を共通化のためにサブルーチン化します。

public void CheckIntegerData(TextBox textControl)
{
    string errMessage = "";
    if (textControl.Text.Length == 0)
    {
        errMessage = "入力してください";
    }
    else
    {
        int val;
        if (!int.TryParse(textControl.Text, out val))
        {
            errMessage = "整数で入力してください";
        }
    }
    this.errorProvider1.SetError(textControl, errMessage);
}

private void textBox1_Validating(object sender, CancelEventArgs e)
{
    CheckIntegerData(this.textBox1);
}

さらにイベント処理を匿名メソッドに置き換えることでイベントを直接ハンドリングせずに検証処理を記述できます。これによって検証処理を宣言的に処理を記述できるようになります。

this.SetIntegerDataValidator(textBox1);

public void SetIntegerDataValidator(TextBox textControl)
{
    textControl.Validating += delegate
    {
        string errMessage = "";
        if (textControl.Text.Length == 0)
        {
            errMessage = "入力してください";
        }
        else
        {
            int val;
            if (!int.TryParse(textControl.Text, out val))
            {
                errMessage = "整数で入力してください";
            }
        }
        this.errorProvider1.SetError(textControl, errMessage);
    };
}

この宣言的に処理を記述することができるということは処理を構造的に表すことができるということなので、Expression Builderがここで登場します。

public class ControlBuilder
{
    private Control target;
    private ErrorProvider errors;

    public ControlBuilder(Control target, ErrorProvider errors)
    {
        this.target = target;
        this.errors = errors;
    }

    public ControlBuilder SetIntegerDataValidator()
    {
        target.Validating += delegate
        {
            string errMessage = "";
            if (target.Text.Length == 0)
            {
                errMessage = "入力してください";
            }
            else
            {
                int val;
                if (!int.TryParse(target.Text, out val))
                {
                    errMessage = "整数で入力してください";
                }
            }
            errors.SetError(target, errMessage);
        };
        return this;
    }

    public ControlBuilder SetImeModeOff()
    {
        target.ImeMode = ImeMode.Off;
        return this;
    }

    public ControlBuilder SetMaxLength(int length)
    {
        TextBoxBase text = target as TextBoxBase;
        if (text != null) text.MaxLength = length;
        return this;
    }
}

あとはビルダーを使ってコントロールの処理構造を宣言的に記述できるようになります。

new ControlBuilder(textBox1, errorProvider1)
    .SetMaxLength(4)
    .SetImeModeOff()
    .SetIntegerDataValidator();

コントロールの機能拡張や共通化は継承を利用する方法がありますが、このExpression Builderは継承せずに機能拡張できる大きなメリットがあります。また複数のコントロールにたいして一度に機能拡張できるメリットもあり便利な方式です。
これ以外にもExpression Builderは使用用途がいくつもあり、今後はより広い範囲で利用されていくのではないかと予想しています。