Memento — Capture and Restore State
The Memento pattern in C#: save and restore object state without violating encapsulation. Build undo/redo for documents, configuration snapshots, and game save points.
Memento — Capture and Restore State
The Memento pattern captures and externalises an object's internal state so it can be restored later — without violating encapsulation. The object being saved doesn't expose its internals; it produces an opaque snapshot.
Core Implementation
// Memento — the snapshot (opaque to everyone except Originator)
public class DocumentMemento
{
internal string Content { get; }
internal int CursorPos { get; }
internal DateTime SavedAt { get; }
internal DocumentMemento(string content, int cursorPos)
{
Content = content;
CursorPos = cursorPos;
SavedAt = DateTime.UtcNow;
}
public override string ToString()
=> $"Snapshot at {SavedAt:HH:mm:ss} ({Content.Length} chars)";
}
// Originator — the object whose state we save/restore
public class TextDocument
{
private string _content = "";
private int _cursor = 0;
public void Type(string text)
{
_content = _content.Insert(_cursor, text);
_cursor += text.Length;
}
public void MoveCursor(int position)
=> _cursor = Math.Clamp(position, 0, _content.Length);
public string GetContent() => _content;
// Save — produces memento (internal constructor keeps fields hidden)
public DocumentMemento Save()
=> new(_content, _cursor);
// Restore — applies memento back (internal access)
public void Restore(DocumentMemento memento)
{
_content = memento.Content;
_cursor = memento.CursorPos;
}
public override string ToString()
=> $"[cursor:{_cursor}] {_content}";
}
// Caretaker — stores mementos without knowing their contents
public class UndoManager
{
private readonly Stack<DocumentMemento> _history = new();
private readonly Stack<DocumentMemento> _redoStack = new();
public void SaveState(TextDocument doc)
{
_history.Push(doc.Save());
_redoStack.Clear(); // new action invalidates redo history
}
public void Undo(TextDocument doc)
{
if (!_history.TryPop(out var memento)) return;
_redoStack.Push(doc.Save());
doc.Restore(memento);
}
public void Redo(TextDocument doc)
{
if (!_redoStack.TryPop(out var memento)) return;
_history.Push(doc.Save());
doc.Restore(memento);
}
}
// Usage
var doc = new TextDocument();
var undo = new UndoManager();
undo.SaveState(doc);
doc.Type("Hello");
undo.SaveState(doc);
doc.Type(", World");
undo.SaveState(doc);
doc.Type("!");
Console.WriteLine(doc); // [cursor:14] Hello, World!
undo.Undo(doc);
Console.WriteLine(doc); // [cursor:12] Hello, World
undo.Undo(doc);
Console.WriteLine(doc); // [cursor:5] Hello
undo.Redo(doc);
Console.WriteLine(doc); // [cursor:12] Hello, WorldConfiguration Snapshot Pattern
// Useful for "try and revert" scenarios
public class DatabaseConfiguration
{
public string ConnectionString { get; set; } = "";
public int MaxPoolSize { get; set; } = 100;
public int TimeoutSeconds { get; set; } = 30;
public ConfigSnapshot CreateSnapshot()
=> new(ConnectionString, MaxPoolSize, TimeoutSeconds);
public void RestoreFrom(ConfigSnapshot snapshot)
{
ConnectionString = snapshot.ConnectionString;
MaxPoolSize = snapshot.MaxPoolSize;
TimeoutSeconds = snapshot.TimeoutSeconds;
}
}
public record ConfigSnapshot(string ConnectionString, int MaxPoolSize, int TimeoutSeconds);
// Test new config, revert if it fails
var config = new DatabaseConfiguration { MaxPoolSize = 100 };
var snapshot = config.CreateSnapshot();
config.MaxPoolSize = 500; // try new value
if (!await TestConnectionAsync(config))
config.RestoreFrom(snapshot); // revert on failureModern C# with Records
// Immutable records make memento trivial — the record IS the memento
public record GameState(
int Level,
int Score,
int Lives,
(int X, int Y) PlayerPosition
);
public class Game
{
public GameState State { get; private set; } = new(1, 0, 3, (0, 0));
private readonly Stack<GameState> _saves = new();
public void Save() => _saves.Push(State);
public void Load() { if (_saves.TryPop(out var s)) State = s; }
public void Move(int dx, int dy)
{
var (x, y) = State.PlayerPosition;
State = State with { PlayerPosition = (x + dx, y + dy) };
}
}Interview Answer
"Memento captures an object's internal state into an opaque snapshot without exposing private fields — the Originator creates the memento (has access to internals), the Caretaker stores it (doesn't know what's inside), and the Originator restores from it. Classic uses: undo/redo in text editors, configuration rollback (save before applying changes, restore on failure), and game save points. In modern C#, immutable records simplify Memento significantly — a record instance IS the snapshot; create a new record with
with { }for mutations and keep the old record for undo. The pattern's key benefit over directly copying state externally is that the Originator controls what's saved and how — callers cannot read or manipulate the saved state."
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.