5年前に書いた記事 Swiftで簡単なステートマシンを作って使う のC#版です。
他の言語で実装したらどうなる?と聞かれたので、とりあえずC#でやってみます。
本体
イベント構文は便利ですね。
class StateMachine<TState, TEvent>
{
public StateMachine(TState initial, Func<TState, TEvent, TState?> routing)
{
Current = initial;
this.routing = routing;
}
public TState Current { get; private set; }
Func<TState, TEvent, TState?> routing;
public event Action<StateChangeType, TState>? Changed;
public void Transition(TEvent by)
{
var next = routing(Current, by);
if (next == null)
{
return;
}
Changed?.Invoke(StateChangeType.OnExit, Current);
Current = next;
Changed?.Invoke(StateChangeType.OnEnter, Current);
}
}
enum StateChangeType { OnEnter, OnExit }
定義
レコード型を使ってADTっぽく定義します。
できればシングルトンとかにしたほうが丁寧でいいですね。
abstract record EditState();
sealed record Editing() : EditState;
sealed record Confirming() : EditState;
sealed record Saving() : EditState;
sealed record Saved(SaveResult SaveResult): EditState;
abstract record EditEvent();
sealed record Save() : EditEvent;
sealed record Confirm(bool IsOk) : EditEvent;
sealed record FinishSave(SaveResult SaveResult) : EditEvent;
sealed record ShownResult() : EditEvent;
abstract record SaveResult();
sealed record Success() : SaveResult;
sealed record Failure(SaveError Error) : SaveResult;
使い方
class EditViewController
{
public EditViewController()
{
stateMachine = new StateMachine<EditState, EditEvent>(new Editing(), (state, evt) => (state, evt) switch
{
(Editing, Save) => new Confirming(),
(Confirming, Confirm { IsOk: true }) => new Saving(),
(Confirming, Confirm { IsOk: false }) => new Editing(),
(Saving, FinishSave finishSave) => new Saved(finishSave.SaveResult),
(Saved, ShownResult) => new Editing(),
_ => null,
});
stateMachine.Changed += OnStateChanged;
}
StateMachine<EditState, EditEvent> stateMachine;
void OnStateChanged(StateChangeType type, EditState state)
{
switch (type, state)
{
case (StateChangeType.OnEnter, Confirming):
OnEnterConfirming();
break;
case (StateChangeType.OnEnter, Saving):
OnEnterSaving();
break;
case (StateChangeType.OnEnter, Saved saved):
OnEnterSaved(saved.SaveResult);
break;
}
}
void OnEnterConfirming()
{
// 確認ダイアログを表示する
// ...
// ... OK/NGが選択されたら stateMachine.Transition(new Confirm(isOk)) を呼ぶ
}
void OnEnterSaving()
{
// 保存処理を開始する
// ...
// ... 保存処理が終わったら stateMachine.Transition(new FinishSave(saveResult)) を呼ぶ
}
void OnEnterSaved(SaveResult result)
{
switch (result)
{
case Success:
// 保存に成功したことを表示する
break;
case Failure f:
// 保存に失敗したことを表示する
break;
}
stateMachine.Transition(new ShownResult());
}
}
感想
最近のC#(9.0以降?)だとレコード型とパターンマッチが使えるので、Swiftと近い実装にできますね。
旧いバージョンの場合、この方針だと色々辛そうなので別のアプローチが必要です。
思いついたら書きます。もしいい方法があったら教えてほしいです。