dotnet/wpf · error · InvalidOperationException
SR.UndoUnitCantBeAddedTwice
Error message
SR.UndoUnitCantBeAddedTwice
What it means
ParentUndoUnit.Add pushes a completed undo unit onto this parent's unit stack. A unit that is already part of this parent's chain (IsInParentUnitChain) must not be added again, so InvalidOperationException(SR.UndoUnitCantBeAddedTwice) is thrown to prevent the same unit appearing twice in the undo stack.
Solutions
- Do not call Add for a unit already closed into this parent — Close already adds it via the internal path.
- Remove the unit from its current parent before adding it to a different one.
- Track added units (e.g. a HashSet) and skip duplicates in custom undo code.
- If merging behavior is intended, rely on the Merge mechanism rather than re-adding.
Example fix
// before parentUnit.Close(unit, action); parentUnit.Add(unit); // duplicate add // after parentUnit.Close(unit, action); // Close already registers the unit
Defensive patterns
Strategy: validation
Validate before calling
bool alreadyRegistered = addedUnits.Contains(unit); if (!alreadyRegistered) parentUnit.Add(unit);
Type guard
bool IsNotInChain(ParentUndoUnit p, IUndoUnit u) => !p.IsInParentUnitChain(u);
Try / catch
try { parentUnit.Add(unit); }
catch (InvalidOperationException ex) when (ex.Message.Contains("added twice"))
{
// unit already on the stack: ignore
} Prevention
- Remember Close implicitly registers the unit — do not Add afterwards
- Remove a unit from its old parent before re-parenting
- Keep a HashSet of added units in custom undo code
When it happens
Trigger: Calling Add(unit) with a unit already contained in this parent unit (or an ancestor), typically by closing a unit and re-adding it, or adding a parent to one of its own descendants.
Common situations: Custom undo transaction code that both calls Close (which implicitly adds the unit) and then Add explicitly; moving units between stacks without removing them from the original parent; cyclic parent/child wiring.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- SR.UndoNoOpenUnit
- SR.UndoUnitCantBeOpenedTwice
- Container already has Starting Part.
- Current DocumentSequence, FixedDocument, or FixedPage not…
- FixedDocument has more than one related document structure.
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/d2f164d62b08fe04.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/ParentUndoUnit.cs:225
/// </exception>
public virtual void Add(IUndoUnit unit)
{
IParentUndoUnit parentUndoUnit;
ArgumentNullException.ThrowIfNull(unit);
parentUndoUnit = DeepestOpenUnit;
// If we have an open unit, call Add on it
if (parentUndoUnit != null)
{
parentUndoUnit.Add(unit);
return;
}
if (IsInParentUnitChain(unit))
{
throw new InvalidOperationException(SR.UndoUnitCantBeAddedTwice);
}
if (Locked)
{
throw new InvalidOperationException(SR.UndoUnitLocked);
}
if (!Merge(unit))
{
_units.Push(unit);
if (LastUnit is IParentUndoUnit)
{
((IParentUndoUnit)LastUnit).OnNextAdd();
}
SetLastUnit(unit);
}
}View on GitHub (pinned to 81131a70a4)