Basic Design
The most essential pieces of undo/redo logic are implemented in a single class (CircularUndoRedoStack) and further features are added by creating "wrapper" classes around this core class.
Structure of CircularUndoRedoStack
The functionality is based on the following assumptions:
- Undo redo should save up to a fixed number of undoable/redoable actions that is known when an instance of the class is constructured. This guards against saving huge numbers of changes that could potentially grow without limit.
- Undoing actions and then adding a new undoable action to the stack drops all previously undone actions. For example typing 'a', 'b', 'c' then undoing 'c' and 'b' followed by typing 'd' will cause the information for 'c' and 'd' to be removed from the undo/redo stack. This means that hitting undo will undo 'd', then another undo will undo 'a', and then there will be nothing left to undo. This is in contrast to allowing undo to "redo" the undos that were done to 'b' and 'c'. This assumption simplifies the implementation greatly and the functionality to retain this extra undo/redo information seems unlikely to provide much if any value in most scenarios.
SingleTargetUndoRedo (Wrapper for CircularUndoRedoStack)
Meant to server as an undo redo stack for a single target (where a target is an arbitrary .NET object). A good example for a target would be a view model in an MVVM design. The stack retains a WeakReference to its target to discourage code that may lead to a memory leak and each item placed on the stack is forced to follow a pattern when undo/redo logic takes the WeakReference to the target as a parameter (enforced by making the wrapper core implementation take instances of IChangeInformation).
Major features include:
- Single Threaded Access - Only the thread that creates the undo/redo stack is allowed to use the methods that wrap the core functionality (which are Undo, Redo, and PushChange). If a different thread attempts to use one of these methods an exception will be thrown. This was chosen over making the class thread safe because it seems unlikely that multiple threads would really need to have access to an undo/redo stack. Most likely the undo/redo stack will be created on the UI thread of an application and accessed only from there.
- Disallows Changes During Undo/Redo - It is hard to imagine a need for this functionality and when it does happen it is almost certainly an error.