2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

C#で簡単なステートマシンを作って使う

Last updated at Posted at 2023-08-02

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と近い実装にできますね。

旧いバージョンの場合、この方針だと色々辛そうなので別のアプローチが必要です。
思いついたら書きます。もしいい方法があったら教えてほしいです。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?