dotnet/wpf · error · InvalidOperationException
SR.UndoUnitCantBeOpenedTwice
Error message
SR.UndoUnitCantBeOpenedTwice
What it means
ParentUndoUnit.Open starts a new open undo unit nested inside this parent. The same undo unit cannot be opened twice while it is already part of the parent chain: if `newUnit` is found in this unit's parent chain, InvalidOperationException(SR.UndoUnitCantBeOpenedTwice) is thrown to prevent recursive/cyclic opening.
Solutions
- Call Close(unit, ...) for the currently open unit before calling Open on it (or any ancestor) again.
- Check OpenedUnit/DeepestOpenUnit state before calling Open and skip if the unit is already open.
- Ensure your edit transaction boundaries are balanced: every Open has exactly one matching Close, including in exception paths (try/finally).
- If the unit should merge, use Add/Merge instead of opening again.
Example fix
// before if (currentUnit != null) parentUndoUnit.Open(currentUnit); // throws if already in chain // after if (parentUndoUnit.OpenedUnit != currentUnit) parentUndoUnit.Open(currentUnit);
Defensive patterns
Strategy: validation
Validate before calling
bool canOpen = parentUnit != null
&& parentUnit.OpenedUnit == null
&& !ReferenceEquals(parentUnit.OpenedUnit, newUnit); Type guard
bool IsOpenInChain(ParentUndoUnit parent, IUndoUnit unit) =>
parent != null && unit != null &&
object.ReferenceEquals(parent.DeepestOpenUnit, unit); Try / catch
try { parentUnit.Open(newUnit); }
catch (InvalidOperationException ex) when (ex.Message.Contains("opened twice"))
{
// unit already open: skip or close the previous unit first
} Prevention
- Balance every Open with exactly one Close using try/finally
- Check OpenedUnit before opening
- Never open an ancestor unit inside its own descendant
When it happens
Trigger: Calling Open(newUnit) on a ParentUndoUnit when newUnit already appears in its parent unit chain (IsInParentUnitChain returns true), i.e. re-opening an already-open or ancestor unit.
Common situations: Custom text-editing undo implementations calling Open twice for the same unit without an intervening Close; re-entrant edit handlers (e.g. nested property-change events) opening the same unit again; forgetting that Open fails when no deeper unit is open but the unit is an ancestor.
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.UndoUnitCantBeAddedTwice
- Current DocumentSequence, FixedDocument, or FixedPage not…
- IAmbientProvider
- InvalidOperationException()
AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14).
Data as JSON: /api/errors/a4a599babf3de27c.
Report an issue: GitHub.
Appendix: source
Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/MS/Internal/documents/ParentUndoUnit.cs:69
/// </summary>
/// <param name="newUnit">
/// IParentUndoUnit to open
/// </param>
/// <exception cref="ArgumentNullException">
/// Thrown if passed unit is null.
/// </exception>
public virtual void Open(IParentUndoUnit newUnit)
{
IParentUndoUnit deepestOpen;
ArgumentNullException.ThrowIfNull(newUnit);
deepestOpen = DeepestOpenUnit;
if (deepestOpen == null)
{
if (IsInParentUnitChain(newUnit))
{
throw new InvalidOperationException(SR.UndoUnitCantBeOpenedTwice);
}
_openedUnit = newUnit;
newUnit?.Container = this;
}
else
{
newUnit?.Container = deepestOpen;
deepestOpen.Open(newUnit);
}
}
/// <summary>
/// Closes the current open unit, adding it to the containing unit's undo stack if committed.
/// </summary>
public virtual void Close(UndoCloseAction closeAction)
{View on GitHub (pinned to 81131a70a4)