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

  1. Balance every PushTransform with a PopTransform; use try/finally so the Pop always executes.
  2. Audit render overrides for any early return/continue/break that occurs after a push but before its pop.
  3. 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

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


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/81389ee9cf6466fe. Report an issue: GitHub.