Advanced

C-Sharp Design Patterns Memento

A Memento maintains the internal state of an object so that that object can be restored to that state later.

@ardalis Level: Intermediate


Table of Contents

  1. Course Overview
  2. Introduction to the Memento pattern
  3. Examples of problems solved by Memento
  4. Memento pattern structure
  1. Implementation of Undo/Redo with Memento
  1. Apply Memento to existing code
  2. Demo: Adding Undo support with Memento
  1. Result analysis
  2. Alternative approaches
  1. Related Patterns
  2. Key Takeaways
  3. References and resources

1. Course Overview

This course, presented by Steve Smith (known as Ardalis), is a comprehensive introduction to the Memento pattern in C#. Steve Smith is an experienced .NET developer, architect, and trainer. He can be reached on Twitter, GitHub and YouTube under the handle @ardalis, and publishes short development tips on his podcast weeklydevtips.com.

What this course covers

  • What problem is the Memento pattern designed to solve?
  • What software design principles can be applied with this pattern?
  • How to apply the Memento pattern in different ways in your applications?
  • How to implement undo / redo logic using this pattern?
  • What other design patterns are similar or related to Memento?

Learning Outcome

At the end of this course, you will be able to:

  • Recognize situations where the Memento pattern is appropriate
  • Apply this pattern with confidence in your C# applications

2. Introduction to the Memento pattern

Definition

A Memento maintains the internal state of an object so that that object can be restored to that state later.

The pattern is also sometimes called token pattern. It can be compared to:

  • A save point in a video game
  • A special commit in a source control system (like Git)

Category

The Memento pattern is classified as a behavioral pattern because it can be used to add an undo or replay behavior to an application.

Questions this course answers

QuestionAnswer covered
What is the Memento pattern?Module 2
What problems does it solve?Module 2-3
How is it structured?Module 4
How to apply it in code?Modules 6-7
How to recognize similar patterns?Modules 9-10

3. Examples of problems solved by Memento

3.1 Save state in games

This is one of the most classic applications of the Memento pattern. Video games often need to allow players to save the game state to resume later. Memento is perfectly suited to this scenario because:

  • Full game state can be captured at a specific time
  • This savepoint can be restored on demand

3.2 Support for undo in drawing or editing applications

Memento is an excellent candidate for implementing undo functionality in applications such as:

  • Graphic editors (Adobe Photoshop, vector design)
  • Text editors
  • Data processing applications

Beyond simple undo, it is relatively easy to implement unlimited undo/redo with this pattern.

3.3 Rollback of distributed transactions

In distributed systems, where multiple services each have their own state, Memento can be used to standardize rollback. If one of the systems fails, all must revert to their previous state. Memento achieves this in a standardized way.

3.4 When to use this pattern?

You should consider using the Memento pattern in the following cases:

  • Rollback: you need to be able to go back on the state of one or more objects
  • Undo: your application requires an undo functionality (very common)
  • Encapsulation: you have complex objects whose internal state you do not want to expose for rollback

Note: For simple scenarios involving primitive types like strings, Memento might be overkill. But if you have complex objects, rolling back their state often requires exposing that internal state to other collaborators — which would violate encapsulation and make the code more prone to bugs.

The Memento pattern allows you to:

  • Respect the Single Responsibility Principle (SRP): state management is not a responsibility of the source object, it is delegated to a Caretaker
  • Preserve encapsulation: the internal state of the object remains protected from inappropriate external access

In summary, the Memento pattern describes a way to capture the internal state of objects without violating encapsulation or the Single Responsibility Principle.


4. Structure of the Memento pattern

4.1 The three actors: Originator, Caretaker, Memento

The Memento pattern involves three objects that collaborate together:

The Originator

The Originator is the object whose state is being tracked. It could be:

  • Of a game (Game)
  • From a document (Document)
  • A drawing canvas (DrawingCanvas)
  • Of a business process

The state of this object can be simple or complex, but it must remain private and encapsulated from the rest of the application.

To support the Memento pattern, the Originator must interact with Mementos in two ways:

  1. Create a Memento from its current state: CreateMemento() method (or SaveSetPoint(), CreateBackup(), etc.)
  2. Restore its state from a provided Memento: method SetMemento(memento) (or ResumeFrom(), RestoreFrom(), etc.)

The Caretaker

The Caretaker is the external object that interacts with the Originator. It could be:

  • A user interface (UI)
  • From a web page element
  • From a console application
  • From the main Program

Its responsibility is twofold:

  • Perform operations on the Originator
  • Manage Originator Mementos (storage, organization, undo/redo invocation)

The Caretaker can keep Mementos in memory or in a persistent store if serialization is supported.

The Memento

The Memento is the object that contains the internal state of the Originator. It represents:

  • A game save
  • A saved version of a document or drawing

It must contain the complete state of the Originator, so that it can be fully restored from the Memento.

4.2 Class diagram

+-------------------+          +-------------------+
|    Originator     |          |     Caretaker     |
+-------------------+          +-------------------+
| - state           |<-------->| - mementos        |
+-------------------+          +-------------------+
| + CreateMemento() |          | + SaveState()     |
| + SetMemento(m)   |          | + Undo()          |
+-------------------+          | + Redo()          |
         |                     +-------------------+
         | crée/lit
         v
+-------------------+
|      Memento      |
+-------------------+
| ~ state           |
+-------------------+
| ~ GetState()      |
| ~ SetState()      |
+-------------------+

Interaction flow:

  1. User requests to save state
  2. The Caretaker calls CreateMemento() on the Originator
  3. The Originator creates a new Memento instance, copies its state to it and returns it to the Caretaker
  4. Later, user requests a restore
  5. The Caretaker calls SetMemento(memento) on the Originator with the chosen Memento
  6. The Originator retrieves the state of the Memento via GetState() and updates its current state

4.3 Key design points

RuleExplanation
The Memento must be simpleIt’s a value object with very little logic: mostly internal state and property accessors. May also have serialization attributes if necessary.
Methods on the OriginatorAdd CreateMemento() and SetMemento() (or equivalent names)
Separate liabilityThe storage and management of Mementos belongs to the Caretaker, generally associated with the UI
Preserve encapsulationAvoid giving the Caretaker or any other object (except the Originator) direct access to the internal state of the Memento
Accessibility in C#C# does not have a friend level of protection like C++. To restrict access, use internal and place the Originator and Memento in the same project (assembly)

5. Implementing Undo/Redo with Memento

5.1 Undo only — using a stack

The most common way to manage Mementos in Caretaker is to maintain a Stack of previous states to support multiple undo operations.

Viewing the undo stack

Let’s imagine that the application has gone through a series of updates:

États successifs :
  État 1 → État 2 → État 3 → État 4 (courant)

Pile Undo (du bas vers le haut) :
  [ État 1 | État 2 | État 3 ]
  État courant = État 4

Undo operation:

Avant :
  Pile Undo : [ État 1 | État 2 | État 3 ]
  État courant = État 4

Après Undo :
  Pile Undo : [ État 1 | État 2 ]
  État courant = État 3  ← (popped de la pile)
  État 4 est PERDU (dans un undo sans redo)

In an application that only supports undo, there is no way to recover State 4 once undo; it is simply replaced by the previous state.

The user can continue to undo: State 3 → State 2 → State 1, or continue forward by changing the state (which pushes State 3 onto the stack and replaces the current state with a new State 4).

5.2 Undo AND Redo — two stacks

To support redo, the only real difference is that during an undo, instead of simply overwriting the current state with the previous one, we push the current state onto the redo stack before overwriting it.

Visualization with undo stack + redo stack

État initial :
  Pile Undo : [ État 1 | État 2 | État 3 ]
  Pile Redo : [ ]
  État courant = État 4

Après Undo :
  Pile Undo : [ État 1 | État 2 ]
  Pile Redo : [ État 4 ]  ← État 4 poussé sur redo
  État courant = État 3

Après second Undo :
  Pile Undo : [ État 1 ]
  Pile Redo : [ État 4 | État 3 ]
  État courant = État 2

Après Redo :
  Pile Undo : [ État 1 | État 2 ]  ← État 2 retourné sur undo
  Pile Redo : [ État 4 ]
  État courant = État 3  ← popped de la pile redo

5.3 Detailed implementation steps

To implement undo/redo logic in your application:

1. Initial setup:

  • The Caretaker is responsible for the undo stack and (optionally) the redo stack

2. At each change of state:

  • The Caretaker retrieves the state before the change (via CreateMemento())
  • He pushes this Memento onto the undo pile

3. Operation Undo:

  • Pop previous Memento from stack undo
  • Call SetMemento() on the Originator with this Memento
  • If redo is supported, push current state to redo stack before restoring

4. Operation Redo:

  • Push current state to stack undo (like any other action)
  • Pop the Memento from the redo stack and call SetMemento() to restore

Important reminder: Mementos must be immutable value objects with state but no behavior. Only the Originator who created the Memento should be able to assign or retrieve its state.

Pseudo-code of undo/redo logic

// Dans le Caretaker
Stack<IMemento> _undoStack = new Stack<IMemento>();
Stack<IMemento> _redoStack = new Stack<IMemento>();

// Avant chaque action
void BeforeAction()
{
    _undoStack.Push(_originator.CreateMemento());
    _redoStack.Clear(); // Effacer le redo quand une nouvelle action est faite
}

// Opération Undo
void Undo()
{
    if (_undoStack.Count == 0) return;
    _redoStack.Push(_originator.CreateMemento()); // Sauvegarder pour redo
    _originator.SetMemento(_undoStack.Pop());
}

// Opération Redo
void Redo()
{
    if (_redoStack.Count == 0) return;
    _undoStack.Push(_originator.CreateMemento()); // Sauvegarder pour undo
    _originator.SetMemento(_redoStack.Pop());
}

6. Apply Memento to existing code

If you have existing code that could benefit from the Memento pattern (for example because you need savepoints or undo/redo behavior), here are the steps to refactor it.

Refactoring steps

Step 1: Refactor carefully If you have already implemented the state management logic directly in the class that owns the state (the Originator), start by refactoring its design according to good refactoring practices, so as not to introduce bugs.

Step 2: Define a Memento type Define a Memento type to store the state of the Originator. Keep it as simple as possible. Try to ensure that its accessors are only available to the Originator to preserve encapsulation.

Step 3: Add methods to the Originator Add methods on the Originator to:

  • Get a Memento: typically called SaveMemento(), CreateMemento(), CreateSetPoint(), CreateBackup()
  • Restore from a Memento: typically called SetMemento(), Restore(), ResumeFrom()

Step 4: Manage Mementos in Caretaker Identify the class that will be responsible for managing Mementos (the Caretaker). Create the data structures necessary to store one or more Mementos, such as stacks for undo and, optionally, redo.

Summary in checklist form

✅ Identifier l'Originator (l'objet dont l'état doit être suivi)
✅ Créer la classe Memento (simple, avec état mais sans logique)
✅ Restreindre l'accès au Memento (internal en C#, même assembly)
✅ Ajouter CreateMemento() à l'Originator
✅ Ajouter SetMemento() à l'Originator
✅ Identifier le Caretaker (souvent l'UI ou le programme principal)
✅ Ajouter Stack<Memento> undoStack au Caretaker
✅ (Optionnel) Ajouter Stack<Memento> redoStack pour le redo
✅ Sauvegarder l'état avant chaque action dans le Caretaker
✅ Implémenter la logique Undo() dans le Caretaker
✅ (Optionnel) Implémenter la logique Redo() dans le Caretaker

7. Demo: Adding Undo support with Memento

This demonstration illustrates how to implement the Memento pattern in a simple C# console game: the Hangman game.

The Hangman Game — Rules reminder

In the Hangman game:

  • There is a secret word to guess
  • Player must guess the word letter by letter
  • He has a limited number of bad attempts before losing
  • If the player guesses all the letters before exhausting his attempts, he wins

7.1 The base game: HangmanGame (without Memento)

Here is the structure of the HangmanGame class as described in the demonstration:

// HangmanGame.cs
// ORIGINATOR (version de base, sans Memento)
public class HangmanGame
{
    private readonly string _secretWord;
    private readonly int _initialGuesses;
    protected List<char> _guesses = new List<char>();
    protected int _guessesRemaining;

    public bool IsOver => Won || Lost;
    public bool Won => MaskedWord == _secretWord;
    public bool Lost => _guessesRemaining <= 0;
    public int GuessesRemaining => _guessesRemaining;
    public IEnumerable<char> PreviousGuesses => _guesses;

    public HangmanGame(string secretWord, int initialGuesses = 5)
    {
        _secretWord = secretWord;
        _initialGuesses = initialGuesses;
        _guessesRemaining = initialGuesses;
    }

    /// <summary>
    /// Affiche le mot avec des underscores pour les lettres non encore devinées
    /// </summary>
    public string MaskedWord
    {
        get
        {
            var masked = new System.Text.StringBuilder();
            foreach (char c in _secretWord)
            {
                masked.Append(_guesses.Contains(c) ? c : '_');
            }
            return masked.ToString();
        }
    }

    /// <summary>
    /// Logique principale : tenter de deviner une lettre
    /// </summary>
    public GuessResult Guess(char letter)
    {
        // Validation : doit être une lettre A-Z
        if (!char.IsLetter(letter))
            return GuessResult.InvalidGuess;

        letter = char.ToLower(letter);

        // Vérification des doublons
        if (_guesses.Contains(letter))
            return GuessResult.DuplicateGuess;

        // Ajouter à la liste des tentatives
        _guesses.Add(letter);

        // Vérifier si la lettre est dans le mot secret
        if (_secretWord.Contains(letter))
        {
            // Vérifier si le mot est entièrement deviné (victoire)
            if (MaskedWord == _secretWord)
                return GuessResult.Won;
            return GuessResult.CorrectGuess;
        }

        // La lettre n'est pas dans le mot : décrémenter le compteur
        _guessesRemaining--;

        if (_guessesRemaining <= 0)
            return GuessResult.Lost;

        return GuessResult.WrongGuess;
    }
}

public enum GuessResult
{
    InvalidGuess,
    DuplicateGuess,
    CorrectGuess,
    WrongGuess,
    Won,
    Lost
}

The main game loop (before Memento)

// Program.cs (version sans Memento — Caretaker basique)
class Program
{
    static void Main(string[] args)
    {
        var game = new HangmanGame("secret");

        while (!game.IsOver)
        {
            Console.WriteLine("Welcome to Hangman!");
            Console.WriteLine($"Previous guesses ({game.PreviousGuesses.Count()}): " +
                              $"{string.Join(", ", game.PreviousGuesses)}");
            Console.WriteLine($"Word: {game.MaskedWord}");
            Console.WriteLine($"Guesses remaining: {game.GuessesRemaining}");
            Console.Write("Make a guess (A-Z): ");

            var input = Console.ReadLine();
            if (string.IsNullOrEmpty(input)) continue;

            var result = game.Guess(input[0]);

            if (result == GuessResult.WrongGuess)
            {
                Console.WriteLine("Wrong guess!");
            }
        }

        if (game.Won)
            Console.WriteLine($"You won! The word was: {game.MaskedWord}");
        if (game.Lost)
            Console.WriteLine("You lost!");
    }
}

Example of game session (without undo):

Welcome to Hangman!
Word: ______   (6 lettres)
Guesses remaining: 5
Make a guess: A → Wrong
Make a guess: B → Wrong  (4 restant)
Make a guess: C → Correct! (___c_t)
Make a guess: D → Wrong  (2 restant)
Make a guess: E → Correct! (__e__t) → 2 E's
Make a guess: F → Wrong  (1 restant)
Make a guess: G → Wrong  → LOST!

This is where we would like to be able to cancel certain bad attempts…

7.2 Added Memento: HangmanMemento

The Memento must be as simple as possible. It only contains the internal state needed to restore the game, without any business logic.

// HangmanMemento.cs
// MEMENTO — value object simple, état encapsulé
public class HangmanMemento
{
    // Marqué 'internal' pour restreindre l'accès au même assembly
    // Ainsi, le Caretaker (dans un autre projet) ne peut PAS accéder à Guesses
    internal char[] Guesses { get; }

    // Le constructeur peut aussi être internal si on ne veut pas
    // que le Caretaker puisse créer des Mementos directement
    public HangmanMemento(char[] guesses)
    {
        Guesses = guesses;
    }
}

Important Notes:

  • The Guesses property is marked internal: it is only accessible from the same assembly (project)
  • The Caretaker (which is in a different project) can create and keep Mementos, but cannot access their contents
  • This restriction reinforces encapsulation: the internal state of the Memento can only be read by the Originator who created it

7.3 The Originator enriched: HangmanGameWithUndo

To keep the changes clearly visible, HangmanGameWithUndo inherits from HangmanGame and adds only the two methods needed to support Memento.

// HangmanGameWithUndo.cs
// ORIGINATOR avec support Memento — hérite de HangmanGame
public class HangmanGameWithUndo : HangmanGame
{
    public HangmanGameWithUndo(string secretWord, int initialGuesses = 5)
        : base(secretWord, initialGuesses)
    {
    }

    /// <summary>
    /// CreateMemento — Crée un SetPoint (point de sauvegarde) représentant
    /// l'état courant du jeu.
    /// Convertit la liste des tentatives en tableau pour en faire une COPIE.
    /// </summary>
    public HangmanMemento CreateSetPoint()
    {
        // Important : convertir en tableau pour avoir une copie indépendante
        // Si on passait la référence à _guesses, le Memento changerait avec le jeu!
        return new HangmanMemento(_guesses.ToArray());
    }

    /// <summary>
    /// SetMemento — Restaure l'état du jeu à partir d'un SetPoint.
    /// Remplace les tentatives actuelles par celles contenues dans le Memento.
    /// </summary>
    public void ResumeFrom(HangmanMemento memento)
    {
        _guesses = memento.Guesses.ToList();
    }
}

Why _guesses.ToArray()?

It is crucial to create a copy of the list when creating the Memento. If we simply passed the reference to _guesses, the Memento would point to the same object as the game, and its contents would change with each new attempt — which would render the Memento useless.

7.4 The Caretaker: the application console

The Caretaker is the Program (the console application). He is responsible for:

  1. Create and store Mementos in a Stack<HangmanMemento>
  2. Save state before each attempt
  3. Handle the '-' command to trigger undo
// Program.cs (version avec Memento — CARETAKER)
class Program
{
    static void Main(string[] args)
    {
        // Originator : utiliser HangmanGameWithUndo au lieu de HangmanGame
        var game = new HangmanGameWithUndo("secret");

        // Caretaker : pile pour l'historique du jeu (undo stack)
        var gameHistory = new Stack<HangmanMemento>();

        // IMPORTANT : Sauvegarder l'état initial AVANT de démarrer la boucle
        // Ainsi on peut undo jusqu'au tout début
        gameHistory.Push(game.CreateSetPoint());

        while (!game.IsOver)
        {
            Console.WriteLine("Welcome to Hangman!");
            Console.WriteLine($"Previous guesses ({game.PreviousGuesses.Count()}): " +
                              $"{string.Join(", ", game.PreviousGuesses)}");
            Console.WriteLine($"Word: {game.MaskedWord}");
            Console.WriteLine($"Guesses remaining: {game.GuessesRemaining}");

            // Nouvelle option : '-' pour undo
            Console.Write("Make a guess (A-Z) or '-' to undo: ");

            var input = Console.ReadLine();
            if (string.IsNullOrEmpty(input)) continue;

            var entry = input[0];

            // Gestion du undo — UNIQUEMENT dans le Caretaker
            // L'Originator (HangmanGame) ne sait RIEN de l'undo
            if (entry == '-')
            {
                if (gameHistory.Count > 0)
                {
                    // Retirer le dernier état sauvegardé de la pile
                    // et restaurer le jeu à cet état
                    game.ResumeFrom(gameHistory.Pop());
                }
                // Continuer la boucle sans passer '-' au jeu
                continue;
            }

            // Tentative normale : sauvegarder l'état APRÈS la tentative
            var result = game.Guess(entry);

            // Sauvegarder le nouvel état après chaque tentative valide
            // (le Caretaker pousse sur la pile après chaque action)
            gameHistory.Push(game.CreateSetPoint());

            if (result == GuessResult.WrongGuess)
            {
                Console.WriteLine("Wrong guess!");
            }
        }

        if (game.Won)
            Console.WriteLine($"You won! The word was: {game.MaskedWord}");
        if (game.Lost)
            Console.WriteLine("You lost!");
    }
}

Caretaker Important Points:

ItemDescription
Only the Caretaker manages the stackThe Originator has no knowledge of the stack or the concept of undo
Initial backupThe first Push takes place before the game even starts
Undo invisible to the OriginatorThe '-' command is processed entirely in the Caretaker, it is never transmitted to the game
Save after each actionAfter each attempt, the state is pushed onto the stack

Game session with Memento (full demo)

Welcome to Hangman!
Word: ______   (6 lettres)
Guesses remaining: 5
Make a guess (A-Z) or '-' to undo: A → Wrong (4 restant)
Make a guess: B → Wrong  (3 restant)
Make a guess: C → Correct! (__e__t)
Make a guess: D → Wrong  (2 restant)
Make a guess: E → Correct! (__ece_)
Make a guess: F → Wrong  (1 restant)
→ On est en danger! Utilisons undo...
Make a guess: - → UNDO! (retour à 2 restant)
Make a guess: - → UNDO! (retour à 3 restant)
Make a guess: R → Correct! (__ecr_t)
Make a guess: - → UNDO de E (on avait un E, on l'a annulé par erreur)
Make a guess: E → Correct! (__ecre_)
Make a guess: S → Correct! (s_ecret)
Make a guess: T → Correct! (secret)
→ YOU WON! Le mot secret était "secret"!

Thanks to undo, the player can “cheat” by canceling bad attempts and ultimately win the game.

7.5 Encapsulation demonstration

One of the major advantages of the internal approach is that it is impossible for the Caretaker to access the internal state of the Memento.

// Dans Program.cs (le Caretaker) — Tentative d'accès illégal
var firstMemento = gameHistory.First();

// ❌ ERREUR DE COMPILATION :
// "The property 'HangmanMemento.Guesses' is inaccessible due to its protection level"
var guesses = firstMemento.Guesses; // Interdit !

// ✅ Le Caretaker peut seulement :
// - Créer des Mementos via game.CreateSetPoint()
// - Les conserver dans sa pile
// - Les passer à game.ResumeFrom() pour restaurer l'état
// Il NE PEUT PAS lire ou modifier le contenu du Memento directement

This restriction ensures that:

  • The Caretaker cannot access the internal state of the Memento, just as it cannot access the internal state of the game itself
  • Encapsulation is fully respected
  • Only the Originator can read and write to the Memento

8. Analysis of the result

After implementing the Memento pattern in the Hangman game example, here are the important observations:

Role of the Caretaker

  • The Caretaker is the application console / user interface
  • Only UI needs to keep Mementos
  • There are no collections or internal fields with a Memento in the Originator (the HangmanGame)

Pattern limitations

It is worth remembering that the Memento pattern may not be appropriate if the state is very large. In the example, we only store individual characters, but if we stored:

  • The state of a large game world
  • A very detailed document
  • A video

…we might not be able to use this pattern (too much memory required for each Memento).

Encapsulation in C# vs other languages

Different languages ​​offer different capabilities when it comes to object protection:

LanguageMechanism
C++Keyword friend — selective access between classes
C#No friend — use internal + separate projects
JavaPackage-private — classes in the same package

Recommended approach in C#:

  1. Place the Originator and the Memento in the same project (same assembly)
  2. Mark Memento accessors as internal
  3. Ensure that the Caretaker belongs to a different project (another assembly)
Solution Architecture:
├── HangmanGame.csproj          (Assembly A)
│   ├── HangmanGame.cs          → Originator
│   └── HangmanMemento.cs       → Memento (internal Guesses)
│
└── HangmanConsole.csproj       (Assembly B — référence A)
    └── Program.cs              → Caretaker (ne peut pas accéder à Guesses)

9. Alternative approaches

9.1 Store inverse operations (pattern Command)

An alternative approach to the Memento pattern is to store inverse operations rather than the entire state of the system. This approach works very well with the Command pattern, because each command can have a known opposing command capable of reversing its effects.

Principle:

  • Instead of storing the state, we store the operations themselves
  • Undo is accomplished by applying the reverse operations for each command

Example: a simple calculator (favorable case)

Départ   : 30
+ 10     : 40
× 2      : 80

Undo × 2 (opération inverse : ÷ 2) → 40
Undo + 10 (opération inverse : - 10) → 30  ✅ Correct !

This works perfectly because the operations are fully reversible.

Example: non-reversible operation (unfavorable case)

Départ         : 10
- 20           : -10
² (au carré)   : 100

Undo ² (inverse : √)
  → En mathématiques : ±10
  → En logiciel      : +10 seulement ❌ (pas de ±)

Undo - 20 (inverse : + 20)
  → 10 + 20 = 30 ❌ (résultat attendu : 10)

Conclusion: This approach only works for operations that have inverses that always return the original result.

Example: translation (non-reversible operation)

Anglais  : "Pluralsight delivers quality training to developers worldwide."
          ↓ Traduire en Allemand
Allemand : "Pluralsight bietet Entwicklern weltweit qualitativ hochwertige Schulungen."
          ↓ Traduire en Russe
Russe    : [texte en cyrillique]
          ↓ Undo : re-traduire en Allemand
Allemand : [résultat approximatif]
          ↓ Undo : re-traduire en Anglais
Anglais  : "Pluralsight offers high quality developer training courses worldwide."

The final result is close to the original but clearly different — translation is not a perfectly reversible operation.

Memento vs Command comparison for undo

CriterionMemento (complete state)Command (reverse operations)
ReliabilityAlways accurateOnly if operations are reversible
MemoryHigher (full state)Lower (just the controls)
ComplexitySimpleRequires defining inverses
ApplicabilityUniversalLimited to reversible operations

9.2 Store differences (diffs)

When storing operations and reversing them doesn’t work, another alternative to the Memento pattern that requires less space is to store only the differences rather than the full state.

This is exactly how version control systems like Git work.

Benefits of diffs

  • We typically do not store as much information as for complete reports
  • For small changes and short history this works very well

Disadvantages of diffs

  1. Computational resources for creation: Creating a diff may require more resources than simply making a copy of the current state
  2. Computational resources for restoration: Restoring a particular state may involve applying many diffs successively, which can be resource intensive

Comparison of the three approaches

ApproachMemory spaceComplexityReliability
Memento (complete state)HighLowHigh (always accurate)
Command (reverse operations)LowAverageConditional
Diff (differences)MediumHighHigh (but restoration cost)

Two patterns are directly linked to Memento:

10.1 Pattern Command

The pattern Command can be used as an alternative to storing full states. Instead of the Caretaker managing states, it manages a series of commands. When the user wants to return to a previous state, reverse commands are applied.

When to choose Command over Memento?

  • When all operations are easily reversible
  • When the state is too large to copy completely
  • When you already have an order-based model

When to choose Memento over Command?

  • When certain operations are not reversible
  • When restoration reliability is critical
  • When the state is relatively compact

10.2 Pattern Iterator

The Iterator pattern may sometimes need to maintain state between iterations. Iterators can leverage the Memento pattern as a way to do this, keeping this logic outside the iterator itself.

This is an example of patterns that complement each other rather than replacing each other:

  • The Iterator focuses on traversing a collection
  • Memento keeps iteration state
  • Combining the two keeps the Iterator simple and state is managed cleanly

11. Key Takeaways

Pattern summary

AppearanceDetail
NameMemento (also called Token)
CategoryBehavioral patterns
ObjectiveStore the state of an Originator object to allow its later restoration
Delegated responsibilityState management is transferred from the Originator to the Caretaker
Common usesGame save points, basic undo, full undo/redo

SOLID principles applied

1. Single Responsibility Principle (SRP)

The pattern helps enforce the SRP by removing state management as a responsibility of the Originator. The Originator focuses on its business logic; The Caretaker focuses on state history management.

2. Encapsulation

Memento avoids violating encapsulation by keeping the internal state of the Originator protected from inappropriate access. Neither the Caretaker nor any other object should have access to the internal state of the Memento or Originator.

What you learned in this course

  • ✅ How to recognize problems that the Memento pattern can solve
  • ✅ The structure of the pattern with its three actors (Originator, Caretaker, Memento)
  • ✅ How to refactor an existing system to apply the pattern
  • ✅ How to implement undo and undo/redo with stacks
  • Alternative approaches: storing commands or diffs instead of complete states
  • ✅ Related patterns: Command and Iterator

Golden rules

  1. The Memento should be simple — just a state container, no logic
  2. Only the Originator must be able to read/write the internal state of the Memento
  3. Only the Caretaker must manage the Mementos collection
  4. Restrict access in C# with internal and separate projects
  5. Think about memory — if the state is very large, consider an alternative
  6. Mementos are immutable — once created, they should not change

12. References and resources

Source code

Trainer

CoursesLink with Memento
C# Design Patterns: CommandAlternative to Memento for undo via reverse operations
C# Design Patterns: IteratorCan use Memento to maintain state
SOLID Principles of OO DevelopmentSRP and encapsulation applied in Memento
Refactoring FundamentalsUseful for applying Memento to existing code

Glossary

TermDefinition
MementoObject that stores the internal state of an Originator at a specific time
OriginatorObject whose state is tracked; creates and consumes Mementos
CaretakerObject responsible for the conservation and management of Mementos
Token patternAnother name for the Memento pattern
Undo stackStack of Mementos used to implement undo
Redo stackStack of Mementos used to implement repetition
SetPointAlternative name for a Memento in the context of a game
PRSSingle Responsibility Principle — each class has only one reason to change
EncapsulationOO principle that protects the internal state of an object
internalC# access modifier that restricts access to the same assembly
StackLIFO (Last In, First Out) Data Structure in C#
Value objectObject defined by its values ​​rather than its identity; ideally immutable
Behavioral patternCategory of design patterns that deal with communication and responsibilities between objects
SOLIDAcronym for five OO design principles (Single responsibility, Open-closed, Liskov substitution, Interface segregation, Dependency inversion)


Search Terms

c-sharp · design · patterns · memento · testing · architecture · c# · .net · development · undo · pattern · caretaker · game · redo · command · diffs · originator · stack · approaches · case · comparison · encapsulation · non-reversible · operation

Interested in this course?

Contact us to book it or get a custom training plan for your team.