モチベーション
「オブジェクトの状態を、ある一定期間変化させ、その後元に戻す」という処理を簡潔に書きたい。
例:WinFormsコントロールのEnabledプロパティを動的に切り替える
例えば、Form1にbutton1ボタンが置かれていて、ボタンをクリックした際、ある特定の処理が終わるまでボタンを押下不能にしたいとします。ベタに書けば、例えば以下のようなコードが考えられます。
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private async void button1_Click(object sender, EventArgs e)
{
button1.SetEnabled(false);
await Task.Delay(5000);
button1.SetEnabled(true);
}
}
ここでSetEnabledメソッドは次のような拡張メソッドです。
static class ControlExtensions
{
public static void SetEnabled(this Control control, bool enabled)
{
if (control.InvokeRequired)
{
control.Invoke(new Action(() => control.Enabled = enabled));
}
else
{
control.Enabled = enabled;
}
}
}
とりあえずはこれで十分なのですが、いちいちEnabledプロパティをfalseにしたりtrueに戻したりといった記述が面倒くさい。こういった処理を少しでも簡潔に書けないかと思い、次のような方法を考えてみました。
まず、コンストラクタ内でEnabledをfalseに設定し、Disposeメソッドでtrueに戻す処理を記述した、IDisposableを実装したクラスを用意します。
class ControlNotEnabled : IDisposable
{
private Control _control;
public ControlNotEnabled(Control control)
{
_control = control;
_control.SetEnabled(false);
}
#region IDisposable
private bool disposed = false;
public void Dispose()
{
if (!disposed)
{
_control.SetEnabled(true);
disposed = true;
}
}
#endregion
}
次に、上記のControlNotEnabledクラスのインスタンスを生成する拡張メソッドを用意します。
public static ControlNotEnabled NotEnabled(this Control control) => new(control);
これで準備が整いました。Form1クラスのbutton1_Clickを次のように書き換えます。
private async void button1_Click(object sender, EventArgs e)
{
using (button1.NotEnabled())
{
await Task.Delay(5000);
}
}
このコードは、usingステートメントのコードブロックに入る直前に、ControlNotEnabledクラスのコンストラクタによってbutton1.SetEnabled(false)が呼び出され、ブロックを抜けた直後に同クラスのDisposeメソッドによってbutton1.SetEnabled(true)が呼び出されます。これによって、usingコードブロック内でのみbutton1.Enabledがfalseになるという処理を記述することができました。
汎用化する
上記の方法は汎用化できそうです(あくまで使用者が使い方を知っていることが前提ですが)。汎用化のためには、まず以下のような抽象クラスを用意します。
public abstract class ObjectState<T> : IDisposable where T : class
{
private T _value;
protected ObjectState(T value)
{
_value = value;
Set(_value);
}
protected abstract void Set(T value);
protected abstract void Reset(T value);
#region IDisposable
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!disposed)
{
if (disposing)
{
Reset(_value);
}
disposed = true;
}
}
public void Dispose() => Dispose(true);
#endregion
}
ネーミングが安直ですね。いい名前が思いつきませんでした。それはともかくとして、これを使えば、先程のControlNotEnabledクラスは以下のように書き換えることができます。
class ControlNotEnabled : ObjectState<Control>
{
public ControlNotEnabled(Control control) : base(control) { }
protected override void Set(Control value)
{
value.SetEnabled(false);
}
protected override void Reset(Control value)
{
value.SetEnabled(true);
}
}
ObjectState<T>クラスは、ただのクラスなので、usingステートメントなど使わなくてもいろいろな場面で役に立ちそうな気がしました。とりあえず個人的には、本記事のようなusingステートメント前提にした使用を想定しています。
まとめ
usingステートメントは、Dispose()メソッドによる「リソースの破棄」というところに目が行きがちですが、考え方しだいでいろいろな使い方ができるな、と思いました。
↧