カテゴリ: C# 更新日: 2026/07/22

C#のコマンドパターンで操作履歴を管理!Undo機能の実装方法を完全解説

C#のコマンドパターンで処理の履歴を管理する方法
C#のコマンドパターンで処理の履歴を管理する方法

先生と生徒の会話形式で理解しよう

生徒

「C#でアプリを作っているのですが、間違えて操作したときに元に戻すUndo機能を作るのが難しくて困っています。」

先生

「なるほど。そんなときはコマンドパターンという設計手法を使うのが一番の近道ですよ。」

生徒

「コマンドパターンですか?初めて聞く言葉ですが、どういう仕組みなんですか?」

先生

「操作を一つ一つの部品として扱うことで、履歴を保存したり、取り消したりするのがとても簡単になるんですよ。」

1. デザインパターンとコマンドパターンの基礎知識

1. デザインパターンとコマンドパターンの基礎知識
1. デザインパターンとコマンドパターンの基礎知識

プログラミングの世界には、多くの先輩たちが積み上げてきた便利な設計の型があります。これをデザインパターンと呼びます。その中でもコマンドパターンは、命令そのものを一つのオブジェクト(データのかたまり)として扱う手法です。

通常、プログラムは「このボタンが押されたら、この関数を動かす」といった直接的な命令になりがちですが、コマンドパターンを使うと「ボタンが押された」という事実を「コマンド」という箱に入れて管理します。これにより、後からその箱を調べたり、逆の処理を実行したりすることが可能になります。初心者の方がプログラムを学ぶ上で、この「命令をデータ化する」という考え方は非常に強力な武器になります。

2. なぜコマンドパターンで操作履歴が管理できるのか

2. なぜコマンドパターンで操作履歴が管理できるのか
2. なぜコマンドパターンで操作履歴が管理できるのか

操作履歴を管理するとは、言い換えれば「過去に行った命令を順番に記録しておく」ということです。コマンドパターンを使わない場合、何が行われたかを追いかけるのはとても大変です。しかし、コマンドパターンで「実行ボタンを押した」「テキストを入力した」といった操作をすべて独立したコマンドとして作っておくと、それらをリストという入れ物に順番に入れておくだけで履歴管理が完了します。

これがなぜ便利かというと、履歴リストの中に保存されたコマンドを取り出して、反対の動作をさせるだけで、簡単に「一つ前に戻る」といった操作を実現できるからです。プログラミング未経験の方にとって、まずは「操作を箱に入れる」というイメージを持つことが、複雑なシステムを理解する第一歩になります。

3. インターフェースを使って操作を共通化する

3. インターフェースを使って操作を共通化する
3. インターフェースを使って操作を共通化する

コマンドを扱うために重要なのがインターフェースという仕組みです。インターフェースは「必ずこの機能を持たせます」という決まりごとを定義するものです。例えば「実行する」ためのメソッドと「取り消す」ためのメソッドを持つインターフェースを作ることで、どんな操作でも同じ手順で扱えるようになります。


public interface ICommand
{
    void Execute();
    void Undo();
}

このICommandというインターフェースがあれば、色々な操作を同じ枠組みで管理できます。インターフェースという用語は難しく感じるかもしれませんが、要は「誰が作っても同じ操作ができるようにするための約束事」だと考えてください。

4. 具体的なコマンドクラスを作成してみよう

4. 具体的なコマンドクラスを作成してみよう
4. 具体的なコマンドクラスを作成してみよう

次に、実際に文字を書き換えるという操作をコマンドとして作ってみます。このクラスには、本来の値と新しい値を保存しておくことで、取り消しが可能になります。


public class ChangeTextCommand : ICommand
{
    private string _previousText;
    private string _newText;

    public ChangeTextCommand(string previous, string next)
    {
        _previousText = previous;
        _newText = next;
    }

    public void Execute()
    {
        Console.WriteLine("新しい文字を設定します: " + _newText);
    }

    public void Undo()
    {
        Console.WriteLine("前の文字に戻します: " + _previousText);
    }
}

このように、一つの操作に対して「やる(Execute)」と「戻す(Undo)」の両方をセットにしておきます。これにより、プログラムは「今何が起きたか」だけでなく「どうすれば元に戻せるか」という情報もセットで保持できるようになります。

5. 操作履歴リストを作成して管理する方法

5. 操作履歴リストを作成して管理する方法
5. 操作履歴リストを作成して管理する方法

コマンドの準備ができたら、それを蓄積するための履歴リストを作りましょう。C#のStack(スタック)というデータ構造を使うと、最新の履歴から順に取り出すことができます。スタックは「積み上げた皿」のようなもので、一番最後に置いたものから先に取り出す仕組みです。これがまさに「操作の取り消し」の動きにぴったりです。


using System.Collections.Generic;

public class CommandManager
{
    private Stack<ICommand> _history = new Stack<ICommand>();

    public void AddAndExecute(ICommand command)
    {
        command.Execute();
        _history.Push(command);
    }

    public void UndoLastCommand()
    {
        if (_history.Count > 0)
        {
            ICommand lastCommand = _history.Pop();
            lastCommand.Undo();
        }
    }
}

このCommandManagerを使えば、難しいことは一切考えずにコマンドを実行するだけで、裏側で勝手に履歴が管理されます。Pop(ポップ)という操作で履歴からコマンドを取り出し、即座にUndoを呼び出すことで元通りになります。

6. 実際に動かして動作を確認する

6. 実際に動かして動作を確認する
6. 実際に動かして動作を確認する

最後に、これまで作った仕組みを実際に動かしてみましょう。メインの処理では、コマンドを作成し、マネージャーに渡すだけで処理が完了します。


class Program
{
    static void Main()
    {
        CommandManager manager = new CommandManager();
        
        ICommand cmd1 = new ChangeTextCommand("初期値", "設定A");
        manager.AddAndExecute(cmd1);
        
        manager.UndoLastCommand();
    }
}

このプログラムを実行すると、以下のように動作します。


新しい文字を設定します: 設定A
前の文字に戻します: 初期値

このように、コマンドパターンを使うことで、操作を部品化して管理することが可能になります。最初は難しく感じるかもしれませんが、一度「操作を箱に入れる」という感覚が身につけば、どんな複雑なアプリでもUndo機能を実装できるようになります。ぜひこの設計パターンをマスターして、より便利で使いやすいプログラム作りに挑戦してみてください。

カテゴリの一覧へ
新着記事
New1
C#
C#のDictionary(辞書)の使い方を初心者向けに完全解説!キーと値でデータ管理を学ぼう
New2
C#
C#の数値型をマスターしよう!int・double・decimalの違いと使い方
New3
C#
C#で始めるIoT開発!音声認識とスピーカー制御の仕組みを解説
New4
Azure
Azure Reposの使い方を徹底解説!Gitによるソースコード管理とブランチ戦略(Gitflow)
人気記事
No.1
Java&Spring記事人気No1
COBOL
COBOLの数値データ型「PIC 9」の使い方と注意点をやさしく解説!
No.2
Java&Spring記事人気No2
C#
C#のpartialクラスとは?初心者でも理解できるクラス分割の基本
No.3
Java&Spring記事人気No3
C#
C#のWPFとは?XAMLでGUI開発を基礎から完全解説!初心者向けの入門ガイド
No.4
Java&Spring記事人気No4
Azure
Azureストレージの冗長性比較!LRS/GRS/ZRSの選び方とコストの最適解を完全解説
No.5
Java&Spring記事人気No5
C#
C#のログ出力入門!SerilogとNLogの使い方を徹底解説
No.6
Java&Spring記事人気No6
C#
C#のrefとoutキーワードとは?引数の参照渡しを理解しよう
No.7
Java&Spring記事人気No7
C#
C#のforeachループを完全ガイド!初心者でもわかるコレクション操作の基本
No.8
Java&Spring記事人気No8
COBOL
COBOLの小数点の扱いと符号付きデータ型(S, V)の使い方を初心者向けに解説!