BrandGhost
Memento Design Pattern in C#: Complete Guide with Examples

Memento Design Pattern in C#: Complete Guide with Examples

Memento Design Pattern in C#: Complete Guide with Examples

When you need to capture and restore an object's internal state without breaking encapsulation, the memento design pattern in C# is the behavioral pattern built for exactly that job. It lets you take snapshots of an object at specific points in time and roll back to any of those snapshots later -- all without exposing the object's private fields or internal structure to the outside world. If you've ever needed undo functionality, checkpoint saving, or transactional rollback, the memento pattern provides a clean, structured way to get there.

In this complete guide, we'll walk through everything you need to know about the memento pattern -- from the three core roles (Originator, Memento, and Caretaker) and a basic implementation to undo/redo systems, serialization-based approaches, encapsulation techniques, and practical considerations for production use. By the end, you'll have working C# code examples and a clear understanding of when this pattern is the right choice for your application.

What Is the Memento Design Pattern?

The memento design pattern is a behavioral pattern from the Gang of Four (GoF) catalog that captures an object's internal state so it can be restored later without violating encapsulation. You externalize an object's state into a separate memento object, store that memento somewhere safe, and use it later to return the object to its previous condition.

Think of a video game save system. When you save your progress, the game captures your character's health, inventory, position, and quest status into a save file. That save file is the memento -- it holds a snapshot of the game state at a specific moment. You can keep playing, and if things go wrong, you load the save file to restore everything to exactly how it was.

The memento pattern resolves a fundamental tension in object-oriented design. You want objects to encapsulate their data behind private fields, but you also need the ability to save and restore that data. The memento pattern lets the object itself produce and consume its own snapshots, keeping the internal structure hidden from everything else.

The Three Roles of the Memento Pattern

The memento pattern involves three distinct participants, each with a clearly defined responsibility. Getting these boundaries right is what keeps the pattern clean and maintainable.

Originator

The Originator is the object whose state you want to capture and restore. It knows its own internal structure and is the only participant that can create a memento from its current state or restore its state from a memento.

In practical terms, the originator is your domain object -- a document, a game character, a form, or any object with complex internal state. It exposes two key operations: one to create a snapshot (often called Save) and one to restore from a snapshot (often called Restore).

Memento

The Memento is the snapshot object that stores the originator's internal state. It acts as an opaque token -- the caretaker can hold it and pass it around, but it cannot inspect or modify the stored data. This opacity is what preserves encapsulation.

In C#, the memento is typically implemented as a class with read-only properties or private fields. You might use a nested class, an interface, or a sealed record. The important thing is that the caretaker never pokes into the memento's internals.

Caretaker

The Caretaker is responsible for storing mementos and deciding when to capture or restore state. It requests mementos from the originator, holds onto them, and hands them back when a rollback is needed. The caretaker never examines or modifies the memento's contents.

In an undo system, the caretaker maintains a stack of mementos. In a checkpoint system, it might store mementos in a dictionary keyed by name. The caretaker knows when to save and restore but has no idea what is being saved.

When to Use the Memento Pattern

The memento pattern fits naturally into scenarios where you need to preserve and restore object state. Here are the most common use cases:

  • Undo/redo functionality. Text editors, drawing applications, and form editors all need the ability to reverse user actions. The memento pattern captures state before each change so you can roll back. The command design pattern often works alongside the memento pattern here -- commands trigger actions while mementos preserve state snapshots.
  • Transactional rollback. If an operation fails partway through, you can restore the object to its pre-operation state using a memento rather than writing complex compensating logic.
  • Checkpoint/save systems. Games, long-running workflows, and wizards benefit from periodic state snapshots that users can return to.
  • State history browsing. Applications that let users navigate through previous versions of an object -- like version history in a document editor -- use mementos to represent each historical state.

Avoid the memento pattern when state snapshots are large and frequent, when the originator's state is trivial (a single field doesn't justify the pattern), or when you need fine-grained change tracking rather than full snapshots. In those cases, simpler approaches like cloning or the observer design pattern for change notification may serve you better.

Basic Memento Implementation in C#

Let's start with a straightforward implementation using a text editor as our domain. The editor holds content and cursor position, and we want the ability to save and restore its state.

The Memento Class

The memento stores a snapshot of the editor's state. It exposes its data through internal access so that the originator can read it, but external code treats it as an opaque container:

public sealed class EditorMemento
{
    public string Content { get; }
    public int CursorPosition { get; }

    public EditorMemento(
        string content,
        int cursorPosition)
    {
        Content = content;
        CursorPosition = cursorPosition;
    }
}

The Originator

The TextEditor class is the originator. It manages its own state and provides methods to create and restore from mementos:

using System;

public sealed class TextEditor
{
    public string Content { get; private set; }
    public int CursorPosition { get; private set; }

    public TextEditor()
    {
        Content = string.Empty;
        CursorPosition = 0;
    }

    public void Type(string text)
    {
        Content = Content.Insert(CursorPosition, text);
        CursorPosition += text.Length;
    }

    public void MoveCursor(int position)
    {
        if (position < 0 || position > Content.Length)
        {
            throw new ArgumentOutOfRangeException(
                nameof(position));
        }

        CursorPosition = position;
    }

    public EditorMemento Save()
    {
        return new EditorMemento(Content, CursorPosition);
    }

    public void Restore(EditorMemento memento)
    {
        Content = memento.Content;
        CursorPosition = memento.CursorPosition;
    }
}

Notice that the originator controls both the creation and consumption of mementos. No external code needs to know which fields make up the editor's state -- that's entirely the originator's responsibility.

The Caretaker

The caretaker stores mementos and coordinates save/restore operations:

using System;
using System.Collections.Generic;

public sealed class EditorHistory
{
    private readonly Stack<EditorMemento> _history = new();

    public void Save(TextEditor editor)
    {
        _history.Push(editor.Save());
    }

    public void Undo(TextEditor editor)
    {
        if (_history.Count == 0)
        {
            Console.WriteLine("Nothing to undo.");
            return;
        }

        EditorMemento memento = _history.Pop();
        editor.Restore(memento);
    }

    public bool HasHistory => _history.Count > 0;
}

Putting It Together

Here's how the three participants interact:

TextEditor editor = new();
EditorHistory history = new();

history.Save(editor);
editor.Type("Hello, ");
Console.WriteLine($"Content: '{editor.Content}'");

history.Save(editor);
editor.Type("World!");
Console.WriteLine($"Content: '{editor.Content}'");

history.Undo(editor);
Console.WriteLine($"After undo: '{editor.Content}'");

history.Undo(editor);
Console.WriteLine($"After second undo: '{editor.Content}'");

This outputs:

Content: 'Hello, '
Content: 'Hello, World!'
After undo: 'Hello, '
After second undo: ''

The caretaker manages the history, the editor creates and consumes its own snapshots, and the memento carries the state between them. Each participant stays within its lane.

Implementing Undo/Redo with the Memento Pattern

The basic caretaker above only supports undo. For full undo/redo, you need two stacks -- one for undo history and one for redo history. When you undo an action, you push the current state onto the redo stack before restoring the previous state. When you redo, you reverse the process.

using System;
using System.Collections.Generic;

public sealed class UndoRedoManager
{
    private readonly Stack<EditorMemento> _undoStack = new();
    private readonly Stack<EditorMemento> _redoStack = new();

    public void SaveState(TextEditor editor)
    {
        _undoStack.Push(editor.Save());
        _redoStack.Clear();
    }

    public void Undo(TextEditor editor)
    {
        if (_undoStack.Count == 0)
        {
            Console.WriteLine("Nothing to undo.");
            return;
        }

        _redoStack.Push(editor.Save());
        EditorMemento memento = _undoStack.Pop();
        editor.Restore(memento);
    }

    public void Redo(TextEditor editor)
    {
        if (_redoStack.Count == 0)
        {
            Console.WriteLine("Nothing to redo.");
            return;
        }

        _undoStack.Push(editor.Save());
        EditorMemento memento = _redoStack.Pop();
        editor.Restore(memento);
    }

    public bool CanUndo => _undoStack.Count > 0;
    public bool CanRedo => _redoStack.Count > 0;
}

The key detail is that SaveState clears the redo stack. Once the user makes a new change after undoing, the "future" states are no longer valid. This matches the undo/redo behavior users expect from every major application. Here's how it works in practice:

TextEditor editor = new();
UndoRedoManager manager = new();

manager.SaveState(editor);
editor.Type("First ");

manager.SaveState(editor);
editor.Type("Second ");

manager.SaveState(editor);
editor.Type("Third");

Console.WriteLine($"Content: '{editor.Content}'");

manager.Undo(editor);
Console.WriteLine($"After undo: '{editor.Content}'");

manager.Undo(editor);
Console.WriteLine($"After second undo: '{editor.Content}'");

manager.Redo(editor);
Console.WriteLine($"After redo: '{editor.Content}'");

This undo/redo system with the memento pattern works cleanly because each snapshot captures the full state. You don't need to compute deltas or track individual field changes. The tradeoff is memory -- each memento stores a complete copy. For objects with large state, limit the history depth or use the serialization-based memento pattern approach shown next.

This is a classic memento pattern pairing with undo/redo. You'll often see it alongside the command pattern, where commands encapsulate the actions and mementos capture state checkpoints.

Serialization-Based Memento Approach

For objects with complex or deeply nested state, you can use serialization to create mementos. Instead of manually copying each field, you serialize the originator's state to JSON (or another format) and deserialize it on restore. This approach scales well when the state structure changes frequently or involves collections and nested objects.

using System;
using System.Collections.Generic;
using System.Text.Json;

public sealed class GameCharacter
{
    public string Name { get; set; } = string.Empty;
    public int Health { get; set; }
    public int Level { get; set; }
    public List<string> Inventory { get; set; } = new();

    public string SaveToMemento()
    {
        var state = new CharacterState
        {
            Name = Name,
            Health = Health,
            Level = Level,
            Inventory = new List<string>(Inventory)
        };

        return JsonSerializer.Serialize(state);
    }

    public void RestoreFromMemento(string memento)
    {
        CharacterState? state =
            JsonSerializer.Deserialize<CharacterState>(
                memento);

        if (state is null)
        {
            throw new InvalidOperationException(
                "Failed to deserialize memento.");
        }

        Name = state.Name;
        Health = state.Health;
        Level = state.Level;
        Inventory = new List<string>(state.Inventory);
    }

    private sealed class CharacterState
    {
        public string Name { get; set; } = string.Empty;
        public int Health { get; set; }
        public int Level { get; set; }
        public List<string> Inventory { get; set; } = new();
    }
}

The memento here is a JSON string rather than a typed object. The caretaker stores strings:

using System;
using System.Collections.Generic;

public sealed class GameSaveManager
{
    private readonly Dictionary<string, string> _saves = new();

    public void SaveCheckpoint(
        string name,
        GameCharacter character)
    {
        _saves[name] = character.SaveToMemento();
        Console.WriteLine(
            $"Checkpoint '{name}' saved.");
    }

    public void LoadCheckpoint(
        string name,
        GameCharacter character)
    {
        if (!_saves.TryGetValue(name, out string? memento))
        {
            Console.WriteLine(
                $"Checkpoint '{name}' not found.");
            return;
        }

        character.RestoreFromMemento(memento);
        Console.WriteLine(
            $"Checkpoint '{name}' loaded.");
    }

    public IReadOnlyCollection<string> ListCheckpoints()
    {
        return _saves.Keys;
    }
}

This is how you'd use the serialization-based approach:

GameCharacter hero = new()
{
    Name = "Warrior",
    Health = 100,
    Level = 5,
    Inventory = new List<string> { "Sword", "Shield" }
};

GameSaveManager saveManager = new();

saveManager.SaveCheckpoint("before-boss", hero);

hero.Health = 20;
hero.Inventory.Add("Potion");
hero.Level = 6;

Console.WriteLine(
    $"After battle: HP={hero.Health}, " +
    $"Level={hero.Level}, " +
    $"Items={string.Join(", ", hero.Inventory)}");

saveManager.LoadCheckpoint("before-boss", hero);

Console.WriteLine(
    $"After restore: HP={hero.Health}, " +
    $"Level={hero.Level}, " +
    $"Items={string.Join(", ", hero.Inventory)}");

The serialization-based memento pattern approach has clear advantages: it handles nested objects and collections automatically, the memento format is human-readable for debugging, and adding new fields doesn't require changes to the memento class. The downside is performance -- serialization is slower than direct field copying. For high-frequency snapshots in the memento pattern, profile before committing to this approach.

Memento with Encapsulation Using Nested Classes

The basic implementation above exposes the memento's properties publicly. Any code with a reference to the memento can read the stored state, which weakens encapsulation. For stricter encapsulation, you can use C#'s nested class feature to make the memento's internals accessible only to the originator.

using System;

public sealed class Document
{
    private string _title;
    private string _body;
    private DateTime _lastModified;

    public Document(string title, string body)
    {
        _title = title;
        _body = body;
        _lastModified = DateTime.UtcNow;
    }

    public void UpdateTitle(string title)
    {
        _title = title;
        _lastModified = DateTime.UtcNow;
    }

    public void UpdateBody(string body)
    {
        _body = body;
        _lastModified = DateTime.UtcNow;
    }

    public override string ToString()
    {
        return $"[{_lastModified:u}] {_title}: {_body}";
    }

    public IDocumentMemento Save()
    {
        return new DocumentMemento(
            _title, _body, _lastModified);
    }

    public void Restore(IDocumentMemento memento)
    {
        if (memento is not DocumentMemento dm)
        {
            throw new ArgumentException(
                "Invalid memento type.",
                nameof(memento));
        }

        _title = dm.Title;
        _body = dm.Body;
        _lastModified = dm.LastModified;
    }

    private sealed class DocumentMemento : IDocumentMemento
    {
        public string Title { get; }
        public string Body { get; }
        public DateTime LastModified { get; }

        public DocumentMemento(
            string title,
            string body,
            DateTime lastModified)
        {
            Title = title;
            Body = body;
            LastModified = lastModified;
        }
    }
}

public interface IDocumentMemento
{
    // Intentionally empty -- opaque token
}

The IDocumentMemento interface has no members. External code can hold references to mementos and pass them around, but it cannot read or modify the stored state. Only the Document class can cast the memento to its private DocumentMemento type and access the properties. This is the closest C# gets to the strict encapsulation described in the original GoF specification.

The caretaker works with the interface:

using System.Collections.Generic;

public sealed class DocumentHistory
{
    private readonly Stack<IDocumentMemento> _snapshots = new();

    public void Save(Document document)
    {
        _snapshots.Push(document.Save());
    }

    public void Undo(Document document)
    {
        if (_snapshots.Count == 0)
        {
            return;
        }

        IDocumentMemento memento = _snapshots.Pop();
        document.Restore(memento);
    }
}

This approach trades a small amount of complexity -- the nested class and interface -- for strong encapsulation guarantees. The caretaker cannot peek inside mementos, and no external code can construct fake mementos with arbitrary state. This is the memento pattern approach to reach for when your originator holds sensitive data or when strict separation of concerns matters. You might see similar encapsulation strategies used in the facade pattern, which also focuses on hiding internal complexity behind a simplified interface.

Benefits and Drawbacks of the Memento Pattern

Like any design pattern, the memento pattern involves tradeoffs. Understanding both sides helps you decide when to use it and when a simpler approach is more appropriate.

Benefits

  • Preserves encapsulation. The originator's internal state stays private. External code interacts with opaque memento objects and never sees the fields being saved or restored.
  • Simplifies the originator. The originator doesn't need to manage its own history. It only needs to know how to create a snapshot and restore from one. History management is the caretaker's job.
  • Clean separation of concerns. Each participant has one responsibility: the originator manages state, the memento carries state, and the caretaker manages history. This aligns with inversion of control principles by delegating history management away from the originator.
  • Flexible history management. Because mementos are standalone objects, you can store them in stacks, lists, dictionaries, or even persist them to disk. The caretaker can implement any history strategy without changes to the originator.

Drawbacks

  • Memory consumption. Each memento is a full snapshot of the originator's state. If the state is large and snapshots are frequent, memory usage can grow quickly. Limiting history depth or using incremental snapshots can help mitigate this.
  • Performance overhead. Creating and restoring mementos involves copying data. For objects with complex state, this can be expensive -- especially if deep copies of collections or nested objects are needed.
  • Maintenance burden. When the originator's state changes -- adding or removing fields -- the memento class must be updated to match. This coupling between the originator and its memento is a maintenance cost that grows with the number of state fields.
  • Caretaker lifecycle management. The caretaker must decide how many mementos to keep and when to discard old ones. Without a retention policy, the history can grow unbounded and consume excessive resources.

Memento Pattern vs. Other Behavioral Patterns

The memento pattern is one of several behavioral patterns that deal with object state. Understanding how it compares to related patterns helps you pick the right tool.

The command pattern encapsulates operations as objects and is often paired with the memento pattern. Commands handle what happened, mementos handle what the state was. Together, they provide robust undo/redo -- commands trigger actions, and mementos capture snapshots before those actions run.

The state pattern changes an object's behavior based on its current condition. It focuses on dynamic behavior rather than state preservation. Use the state pattern when an object needs to act differently based on its mode, and the memento pattern when you need to snapshot and roll back.

The iterator pattern provides sequential access to collection elements. If you store mementos in a collection, an iterator gives you a clean way to traverse history. The strategy pattern swaps algorithms at runtime -- it changes behavior while the memento pattern preserves data.

Frequently Asked Questions

What is the memento design pattern in C#?

The memento design pattern in C# is a behavioral design pattern that captures an object's internal state as a separate memento object so that the state can be restored later without exposing the object's private fields. It involves three participants: the Originator (the object being snapshotted), the Memento (the snapshot itself), and the Caretaker (the component that stores and manages mementos). In C#, you typically implement it with a memento class containing read-only properties, an originator with Save and Restore methods, and a caretaker that maintains a collection of mementos.

When should I use the memento pattern instead of simple cloning?

Use the memento pattern when you need structured history management, encapsulation of saved state, or when the originator's full clone includes behavior and references you don't want in the snapshot. Simple cloning (via ICloneable or copy constructors) creates a full copy of the object, which is fine for one-off backups. The memento pattern adds structure -- it separates the snapshot from the originator, provides a clean interface for history management, and gives you control over exactly which state is captured. If you're building undo/redo or checkpoint systems, the memento pattern's structure pays off quickly.

How does the memento pattern preserve encapsulation?

The memento pattern preserves encapsulation by keeping the memento's internal data accessible only to the originator. In the strictest implementation, the memento is a nested private class inside the originator, and external code (including the caretaker) interacts with it through a marker interface that has no members. This means the caretaker can store and retrieve mementos but cannot read or modify their contents. The originator is the only participant that knows the memento's structure, so the object's internal state remains hidden from the rest of the system.

What is the difference between the memento pattern and the command pattern for undo?

The command pattern achieves undo by storing inverse operations -- each command knows how to reverse itself. The memento pattern achieves undo by storing state snapshots -- you restore the object to a previous condition. Commands are more memory-efficient when the state is large because they only store the delta (what changed). Mementos are simpler to implement because you don't need to write reverse logic for every operation. In practice, the two patterns are often combined: the command handles action execution while the memento captures state before the command runs. This gives you both the action history from commands and the reliable rollback from mementos.

How do I limit memory usage with the memento pattern?

There are several strategies for controlling memory. First, cap the history size by removing the oldest mementos when the collection exceeds a threshold -- a bounded stack or circular buffer works well for this. Second, use incremental mementos that store only the differences from the previous snapshot rather than the full state. Third, compress or serialize mementos to reduce their in-memory footprint. Fourth, implement a tiered retention policy where recent mementos are kept in full detail while older ones are consolidated or discarded. The right approach depends on your application's specific memory and performance constraints.

Can I persist mementos to disk or a database?

Yes. Serialization-based mementos are designed for exactly this use case. You serialize the originator's state to JSON, XML, or a binary format and store the result in a file or database. When you need to restore, you deserialize the data and pass it back to the originator. This is how game save systems, document auto-save features, and workflow checkpoint mechanisms work. Be mindful of versioning -- if the originator's state structure changes between saves and loads, you'll need a migration strategy to handle older memento formats.

How does the memento pattern work with dependency injection?

The memento pattern integrates naturally with dependency injection. You can register the caretaker as a scoped or singleton service depending on whether history should be per-request or application-wide. The originator is typically a domain object created by your application logic rather than resolved from the container. If the caretaker needs to persist mementos, you can inject a repository or storage service into it. The memento objects themselves are plain data carriers and don't require DI registration.

Wrapping Up the Memento Design Pattern in C#

The memento design pattern in C# is a focused behavioral pattern that gives you structured state preservation without breaking encapsulation. The originator creates and consumes snapshots, the memento carries the state, and the caretaker manages the history. Three roles, clear boundaries, and a straightforward implementation.

Start by identifying places in your codebase where you need to save and restore object state -- look for manual field-by-field backup code, ad-hoc cloning, or undo logic scattered across your application. If you find yourself copying state into temporary variables before risky operations, the memento pattern gives you a clean structure to formalize that approach. Pair it with the command pattern for robust undo/redo pipelines, or with the mediator pattern to coordinate state snapshots across multiple objects. You can find the memento pattern alongside other behavioral and structural patterns in the complete list of design patterns.

The Memento Pattern in C# - How to Achieve Effortless State Restoration

Looking to implement the Memento Pattern in C#? Learn about its origin and principles, and the different components & steps to implementation!

Prototype Design Pattern in C#: Complete Guide with Examples

Prototype design pattern in C#: complete guide with code examples, implementation, and best practices for object cloning and creational design patterns.

Strategy Design Pattern in C#: Complete Guide with Examples

Strategy design pattern in C#: complete guide with code examples, implementation, and best practices for flexible algorithm selection.

An error has occurred. This application may no longer respond until reloaded. Reload