How to Implement Memento Pattern in C#: Step-by-Step Guide
Capturing and restoring an object's internal state without breaking encapsulation is one of the most practical things you can do in software design. The memento pattern is a behavioral design pattern that externalizes an object's state into a snapshot object, letting you roll back to a previous version whenever you need to. If you want to implement memento pattern in C#, this guide walks you through every step -- from defining the memento interface to building undo/redo support and writing unit tests. By the end, you'll have complete, compilable code ready to adapt for your own projects.
We'll build progressively. First, we define the memento contract. Then we create the originator that produces and consumes snapshots. After that, we build a caretaker that manages snapshot history, wire up undo functionality, add redo with dual stacks, and finish with a unit testing approach. Each step includes full C# code so you can follow along in your IDE.
Prerequisites
Before diving in, make sure you're comfortable with these fundamentals:
- C# interfaces and classes: You'll define a memento interface and implement concrete classes. Understanding how interfaces enforce contracts is essential.
- Encapsulation: The memento pattern is fundamentally about preserving encapsulation while still enabling state capture. You need to understand access modifiers and why exposing internal state directly is a problem.
- Stack data structures: The undo/redo mechanism relies on two stacks. Familiarity with
Stack<T>in C# will help. - Basic unit testing with xUnit: The final step covers testing. Familiarity with
[Fact]and assertion methods is helpful but not strictly required. - .NET 8 or later: The code examples use modern C# syntax. Any recent .NET SDK works.
Step 1: Define the IMemento Interface
The first step to implement memento pattern in C# is creating the memento interface. This interface represents a snapshot of state. It should expose just enough information for the caretaker to manage snapshots without revealing the originator's internal details.
public interface IMemento
{
string Description { get; }
DateTime CreatedAt { get; }
}
The interface is deliberately minimal. Description gives the caretaker a human-readable label for display or logging purposes. CreatedAt records when the snapshot was taken -- useful for ordering or pruning old snapshots. Neither property exposes the actual state data. That's the whole point of the memento pattern -- the caretaker manages mementos without knowing what's inside them.
This separation aligns with the principle of inversion of control. The caretaker depends on the IMemento abstraction, never on the concrete memento class or the originator's internal structure. That decoupling means you can change the originator's state shape without touching the caretaker at all.
Why not just use a dictionary or a JSON string? You could, but you'd sacrifice type safety and encapsulation. A typed memento object lets the compiler catch mistakes, and the interface boundary prevents the caretaker from accidentally modifying captured state.
Step 2: Create the Originator Class
The originator is the object whose state you want to capture and restore. When you implement memento pattern in C#, the originator is responsible for two things: creating mementos that contain its state, and restoring its state from a memento. No other object should create or read the memento's internal data.
For our example, we'll build a text editor that tracks content and cursor position:
public sealed class TextEditor
{
public string Content { get; private set; } = string.Empty;
public int CursorPosition { get; private set; }
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),
"Cursor position is out of range.");
}
CursorPosition = position;
}
public IMemento Save()
{
return new EditorMemento(Content, CursorPosition);
}
public void Restore(IMemento memento)
{
if (memento is not EditorMemento editorMemento)
{
throw new ArgumentException(
"Invalid memento type.",
nameof(memento));
}
Content = editorMemento.SavedContent;
CursorPosition = editorMemento.SavedCursorPosition;
}
private sealed class EditorMemento : IMemento
{
public string SavedContent { get; }
public int SavedCursorPosition { get; }
public string Description =>
$"Content length: {SavedContent.Length}, " +
$"Cursor: {SavedCursorPosition}";
public DateTime CreatedAt { get; } = DateTime.UtcNow;
public EditorMemento(
string content,
int cursorPosition)
{
SavedContent = content;
SavedCursorPosition = cursorPosition;
}
}
}
A few things to notice about this originator:
- The
EditorMementoclass is a private nested class insideTextEditor. This is the classic way to implement memento pattern in C# while preserving encapsulation. No external code can accessSavedContentorSavedCursorPosition-- only the originator can read them through the concrete type. - The
Savemethod returnsIMemento, notEditorMemento. The caretaker only sees the interface. - The
Restoremethod performs a type check with pattern matching. If someone passes a memento from a different originator, it throws immediately rather than corrupting state. - The memento is immutable. Once created, its state never changes. This prevents accidental mutation after snapshot creation.
This approach is analogous to how the state design pattern manages internal state transitions. The key difference is intent -- the state pattern models transitions between distinct behavioral states, while the memento pattern captures and restores data snapshots without changing behavior.
Step 3: Build the Caretaker
The caretaker manages the collection of mementos. It asks the originator to save snapshots and holds onto them, but it never inspects or modifies the memento's contents. When you implement memento pattern in C#, the caretaker is where the history management logic lives.
public sealed class EditorHistory
{
private readonly List<IMemento> _snapshots = new();
public IReadOnlyList<IMemento> Snapshots => _snapshots;
public void Push(IMemento memento)
{
if (memento is null)
{
throw new ArgumentNullException(
nameof(memento));
}
_snapshots.Add(memento);
}
public IMemento? Pop()
{
if (_snapshots.Count == 0)
{
return null;
}
int lastIndex = _snapshots.Count - 1;
IMemento memento = _snapshots[lastIndex];
_snapshots.RemoveAt(lastIndex);
return memento;
}
public void Clear()
{
_snapshots.Clear();
}
}
The caretaker is simple by design. It pushes and pops mementos without knowing what they contain. This is a deliberate architectural choice -- the caretaker acts as a facade over the snapshot storage mechanism. If you later need to limit the number of stored snapshots, persist them to disk, or compress them, all changes happen inside the caretaker without affecting the originator.
Here's a quick example showing the caretaker and originator working together:
var editor = new TextEditor();
var history = new EditorHistory();
editor.Type("Hello");
history.Push(editor.Save());
editor.Type(", World!");
history.Push(editor.Save());
Console.WriteLine(editor.Content);
// Output: Hello, World!
IMemento? snapshot = history.Pop();
if (snapshot is not null)
{
editor.Restore(snapshot);
}
Console.WriteLine(editor.Content);
// Output: Hello
Each call to Save captures the editor's full state. Each call to Restore rolls it back. The caretaker manages the stack of snapshots without ever looking inside them.
Step 4: Implement Undo Functionality
With the originator and caretaker in place, implementing undo means coordinating snapshot creation and restoration. The idea is straightforward -- before every state-changing operation, save a snapshot. When the user undoes, pop the most recent snapshot and restore it.
Let's create a coordinator class that ties everything together:
public sealed class UndoableEditor
{
private readonly TextEditor _editor;
private readonly Stack<IMemento> _undoStack = new();
public string Content => _editor.Content;
public int CursorPosition => _editor.CursorPosition;
public int UndoCount => _undoStack.Count;
public UndoableEditor(TextEditor editor)
{
_editor = editor
?? throw new ArgumentNullException(
nameof(editor));
}
public void Type(string text)
{
_undoStack.Push(_editor.Save());
_editor.Type(text);
}
public void MoveCursor(int position)
{
_undoStack.Push(_editor.Save());
_editor.MoveCursor(position);
}
public bool Undo()
{
if (_undoStack.Count == 0)
{
return false;
}
IMemento snapshot = _undoStack.Pop();
_editor.Restore(snapshot);
return true;
}
}
Notice the pattern in Type and MoveCursor -- save before making the change. This ensures the snapshot captures the state immediately before the modification so that undoing restores the correct previous state.
The Undo method returns a bool to indicate whether an undo was performed. This gives calling code a clean way to check without inspecting stack counts. If you've worked with the command design pattern, you'll notice a similarity -- both patterns support undo by storing enough information to reverse operations. The difference is that the command pattern stores operations, while the memento pattern stores state snapshots.
Here's the undo workflow in action:
var editor = new TextEditor();
var undoable = new UndoableEditor(editor);
undoable.Type("First ");
undoable.Type("Second ");
undoable.Type("Third");
Console.WriteLine(undoable.Content);
// Output: First Second Third
undoable.Undo(); // Removes "Third"
Console.WriteLine(undoable.Content);
// Output: First Second
undoable.Undo(); // Removes "Second "
Console.WriteLine(undoable.Content);
// Output: First
Each undo operation restores the complete state from before the corresponding Type call. The memento pattern captures everything -- content and cursor position -- so the rollback is comprehensive.
Step 5: Add Redo Support with Dual Stacks
Adding redo requires a second stack. When you undo, push the pre-undo state onto the redo stack. When you redo, push the pre-redo state onto the undo stack. When a new operation occurs, clear the redo stack -- redoing after a new action would create a confusing branching history.
Here's the updated UndoableEditor with full redo support:
public sealed class UndoRedoEditor
{
private readonly TextEditor _editor;
private readonly Stack<IMemento> _undoStack = new();
private readonly Stack<IMemento> _redoStack = new();
public string Content => _editor.Content;
public int CursorPosition => _editor.CursorPosition;
public int UndoCount => _undoStack.Count;
public int RedoCount => _redoStack.Count;
public UndoRedoEditor(TextEditor editor)
{
_editor = editor
?? throw new ArgumentNullException(
nameof(editor));
}
public void Type(string text)
{
SaveForUndo();
_editor.Type(text);
_redoStack.Clear();
}
public void MoveCursor(int position)
{
SaveForUndo();
_editor.MoveCursor(position);
_redoStack.Clear();
}
public bool Undo()
{
if (_undoStack.Count == 0)
{
return false;
}
_redoStack.Push(_editor.Save());
IMemento snapshot = _undoStack.Pop();
_editor.Restore(snapshot);
return true;
}
public bool Redo()
{
if (_redoStack.Count == 0)
{
return false;
}
_undoStack.Push(_editor.Save());
IMemento snapshot = _redoStack.Pop();
_editor.Restore(snapshot);
return true;
}
private void SaveForUndo()
{
_undoStack.Push(_editor.Save());
}
}
The undo/redo dance works like this:
- Normal operation: Save state to undo stack, perform the action, clear redo stack.
- Undo: Save state to redo stack, pop from undo stack, restore.
- Redo: Save state to undo stack, pop from redo stack, restore.
This dual-stack approach is the standard technique when you implement memento pattern with redo support. Each stack direction mirrors the other, and clearing the redo stack on new operations prevents stale state from being replayed.
Here's a complete example demonstrating the full workflow:
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("Alpha ");
undoRedo.Type("Beta ");
undoRedo.Type("Gamma");
Console.WriteLine(undoRedo.Content);
// Output: Alpha Beta Gamma
undoRedo.Undo();
Console.WriteLine(undoRedo.Content);
// Output: Alpha Beta
undoRedo.Undo();
Console.WriteLine(undoRedo.Content);
// Output: Alpha
undoRedo.Redo();
Console.WriteLine(undoRedo.Content);
// Output: Alpha Beta
undoRedo.Type("Delta");
Console.WriteLine(undoRedo.Content);
// Output: Alpha Beta Delta
// Redo stack is now empty because we typed after undoing
Console.WriteLine(undoRedo.RedoCount);
// Output: 0
After typing "Delta", the redo stack clears. The undone "Gamma" is gone permanently. This is the expected behavior that users of text editors and design tools rely on.
If you need more complex state coordination between multiple objects, the mediator design pattern can help broker communication without tight coupling.
Step 6: Unit Testing Approach
Testing the memento pattern implementation requires verifying that state is captured and restored correctly. Since the memento's internal state is hidden from the caretaker, you test through the originator's public properties.
Here are the key test scenarios using xUnit:
public class UndoRedoEditorTests
{
[Fact]
public void Type_AppendsTextToContent()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("Hello");
Assert.Equal("Hello", undoRedo.Content);
}
[Fact]
public void Undo_RestoresPreviousState()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("Hello");
undoRedo.Type(" World");
undoRedo.Undo();
Assert.Equal("Hello", undoRedo.Content);
}
[Fact]
public void Undo_ReturnsFalse_WhenNothingToUndo()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
bool result = undoRedo.Undo();
Assert.False(result);
}
[Fact]
public void Redo_RestoresUndoneState()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("Hello");
undoRedo.Type(" World");
undoRedo.Undo();
undoRedo.Redo();
Assert.Equal("Hello World", undoRedo.Content);
}
[Fact]
public void Redo_ReturnsFalse_WhenNothingToRedo()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("Hello");
bool result = undoRedo.Redo();
Assert.False(result);
}
[Fact]
public void NewAction_ClearsRedoStack()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("First");
undoRedo.Type(" Second");
undoRedo.Undo();
undoRedo.Type(" Third");
Assert.Equal(0, undoRedo.RedoCount);
Assert.Equal("First Third", undoRedo.Content);
}
[Fact]
public void MultipleUndos_RestoreToInitialState()
{
var editor = new TextEditor();
var undoRedo = new UndoRedoEditor(editor);
undoRedo.Type("A");
undoRedo.Type("B");
undoRedo.Type("C");
undoRedo.Undo();
undoRedo.Undo();
undoRedo.Undo();
Assert.Equal(string.Empty, undoRedo.Content);
}
}
A few testing principles to keep in mind:
- Test through public state: You can't directly inspect the memento's internal data (it's private), so assert against the originator's public properties after restore. This is exactly right -- testing through the public API verifies the behavior you care about.
- Cover edge cases: Empty undo/redo stacks, multiple sequential undos, and redo after new actions are all important scenarios.
- Each test is independent: Every test creates fresh instances. No shared state between tests. This prevents order-dependent failures.
If you register your editor components with IServiceCollection in a production application, consider writing integration tests that resolve dependencies from the container to verify the wiring is correct.
Common Mistakes to Avoid
Even experienced developers make these mistakes when they first implement memento pattern in C#.
Exposing memento internals to the caretaker: If the caretaker can read or modify the snapshot's data, you've broken encapsulation. The private nested class approach prevents this at the compiler level. If you move the memento to a separate file, use internal access and assembly-level restrictions.
Capturing references instead of values: If your originator contains mutable reference types -- lists, dictionaries, or custom objects -- copying the reference into the memento isn't enough. You need deep copies. Otherwise, changes to the originator's state after the snapshot will also change the memento, making undo useless.
Unbounded snapshot history: Every snapshot consumes memory. For a text editor with large documents, storing hundreds of full-content snapshots gets expensive fast. Consider limiting the undo stack size, using incremental diffs instead of full snapshots, or compressing older mementos.
Forgetting to clear the redo stack: When a new action occurs after an undo, the redo stack must be cleared. Skipping this step lets users redo into states that no longer make sense, leading to corrupted or confusing results.
Using the memento pattern when the command pattern fits better: If your operations are discrete and invertible -- like "add line" or "delete character" -- the command pattern may be more memory-efficient. The memento pattern shines when state is complex and operations are hard to reverse individually. Choose based on your specific constraints.
Frequently Asked Questions
What is the memento pattern and why does it matter for C# developers?
The memento pattern is a behavioral design pattern from the Gang of Four catalog. It lets you capture an object's internal state as a snapshot and restore it later without exposing the object's internal structure. For C# developers, this matters because it provides a clean, encapsulation-preserving way to implement undo/redo, checkpointing, and state rollback. Instead of exposing private fields or relying on serialization hacks, the memento pattern gives you a type-safe, compiler-enforced mechanism for state management.
How does the memento pattern differ from the command pattern for undo/redo?
Both patterns support undo/redo, but they approach it differently. The command pattern stores operations and their inverses -- each command knows how to execute and undo itself. The memento pattern stores complete state snapshots. Commands are more memory-efficient when operations are easily reversible. Mementos are simpler when state is complex and individual operations are hard to invert. In practice, many systems combine both -- using commands for operation logging and mementos for state checkpoints.
Can I use the memento pattern with immutable objects in C#?
Yes, and it actually simplifies things. If your originator produces new immutable state objects on every change, each state object is effectively a memento already. You just store references to previous states. No deep copying required because immutable objects can't change after creation. This functional approach works well with C# records and with expressions, making the memento pattern nearly zero-cost.
How do I limit memory usage when storing many mementos?
There are several strategies to implement memento pattern in C# without consuming excessive memory. First, cap the undo stack at a fixed size and discard the oldest snapshots when the limit is reached. Second, store incremental diffs instead of full state copies -- each memento records only what changed since the previous snapshot. Third, compress older mementos using serialization and decompress them on demand. The right approach depends on your state size and how far back users need to undo.
What role does encapsulation play in the memento pattern?
Encapsulation is the core concern. The originator's internal state should never be visible to the caretaker or any other external class. In C#, the private nested class technique enforces this at compile time -- the memento's data properties are inaccessible outside the originator. This means the caretaker can store, sort, and prune snapshots without any risk of accidentally corrupting the originator's data. The IMemento interface acts as a narrow contract that reveals metadata but hides state.
How does the memento pattern relate to the strategy pattern?
The strategy pattern and the memento pattern are both behavioral patterns, but they solve different problems. The strategy pattern lets you swap algorithms at runtime behind a common interface. The memento pattern captures and restores object state. Where they overlap is in design philosophy -- both use interfaces to decouple components. You could combine them by using the strategy pattern to choose how mementos are stored (in-memory, on-disk, compressed) while the memento pattern handles what gets stored.
Can I implement memento pattern in C# with dependency injection?
Absolutely. Register the originator and caretaker as services using IServiceCollection. The originator is typically scoped or singleton depending on your application's lifetime requirements. The caretaker is often the same scope as the originator since it manages that specific originator's history. Mementos themselves are not registered -- they're transient value objects created by the originator's Save method and managed by the caretaker. This follows inversion of control naturally -- consuming code depends on abstractions, not on concrete snapshot implementations.

