AvaloniaUI/Avalonia · error · InvalidOperationException
Not all states are disposed
Error message
Not all states are disposed
What it means
Thrown by DisposeCore when the _transforms stack still has entries at dispose time. Each PushTransformCore pushes the current transform; each PopTransformCore pops it. A non-empty stack at disposal means more pushes than pops occurred — i.e. the caller left one or more transform states on the stack, corrupting the saved-state invariant.
Source
Thrown at src/Avalonia.Base/Media/PlatformDrawingContext.cs:132
protected override void PopTextOptionsCore() => _impl.PopTextOptions();
/// <inheritdoc />
protected override void PopEffectCore()
{
if (_impl is IDrawingContextImplWithEffects effectImpl)
{
effectImpl.PopEffect();
}
}
protected override void DisposeCore()
{
if (_ownsImpl)
_impl.Dispose();
if (_transforms != null)
{
if (_transforms.Count != 0)
throw new InvalidOperationException("Not all states are disposed");
TransformStackPool.ReturnAndSetNull(ref _transforms);
}
}
public void DrawRectangle(IExperimentalAcrylicMaterial material, RoundedRect rect)
{
if (_impl is IDrawingContextWithAcrylicLikeSupport idc)
idc.DrawRectangle(material, rect);
else
DrawRectangle(new ImmutableSolidColorBrush(material.FallbackColor), null, rect);
}
}
View on GitHub (pinned to 11c5427268)
Solutions
- Balance every PushTransform with a PopTransform; use try/finally so the Pop always executes.
- Audit render overrides for any early return/continue/break that occurs after a push but before its pop.
- If you wrap push/pop in a helper or using-expression, ensure it disposes on all exit paths.
Example fix
// before
foreach (var m in matrices)
{
context.PushTransform(m);
Draw(context);
// forgot to pop
}
// after
foreach (var m in matrices)
{
context.PushTransform(m);
try { Draw(context); }
finally { context.PopTransform(); }
} Defensive patterns
Strategy: validation
Validate before calling
// Track push/pop depth and assert it returns to 0 before dispose. Debug.Assert(transformDepth == 0, "Unbalanced transforms before dispose");
Try / catch
try { /* render block with balanced push/pop */ }
finally { /* pop all remaining transforms */ } Prevention
- Ensure every PushTransform has a matching PopTransform on all code paths.
- Use try/finally (or a using-expression helper) around render blocks.
- Add a debug assertion that transform depth is zero at scope exit.
When it happens
Trigger: A PushTransform without a matching PopTransform before the drawing context is disposed (using block ends). Each unbalanced push leaves one entry, so N unbalanced pushes leave N entries.
Common situations: Custom render code that pushes transforms inside a loop/branch but only pops on the happy path; an exception between Push and Pop that skips the Pop; forgetting to pop after a conditional push.
Related errors
- PlatformDrawingContext
- Animator has no property specified.
- {item.DebugDisplay} must have a value of type {typeof(Transf
- Cannot dispose while memory is pinned.
- PathMarkupParser
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/81389ee9cf6466fe.
Report an issue: GitHub.