AvaloniaUI/Avalonia · error · InvalidOperationException
Transform container stack is non-empty
Error message
Transform container stack is non-empty
What it means
ImmediateDrawingContext.Dispose throws InvalidOperationException("Transform container stack is non-empty") when _transformContainers still has entries after all states are popped. Every PushTransformContainer must be matched by disposing its PushedState before the context is disposed.
Source
Thrown at src/Avalonia.Base/Media/ImmediateDrawingContext.cs:362
throw new ObjectDisposedException(nameof(DrawingContext));
_transformContainers.Push(new TransformContainer(CurrentTransform, _currentContainerTransform));
_currentContainerTransform = CurrentTransform * _currentContainerTransform;
_currentTransform = Matrix.Identity;
return new PushedState(this, PushedState.PushedStateType.MatrixContainer);
}
/// <summary>
/// Disposes of any resources held by the <see cref="DrawingContext"/>.
/// </summary>
public void Dispose()
{
if (_states is null || _transformContainers is null)
throw new ObjectDisposedException(nameof(DrawingContext));
while (_states.Count != 0)
_states.Peek().Dispose();
StateStackPool.ReturnAndSetNull(ref _states);
if (_transformContainers.Count != 0)
throw new InvalidOperationException("Transform container stack is non-empty");
TransformStackPool.ReturnAndSetNull(ref _transformContainers);
if (_ownsImpl)
PlatformImpl.Dispose();
}
private static bool PenIsVisible(IPen? pen)
{
return pen?.Brush != null && pen.Thickness > 0;
}
public object? TryGetFeature(Type type) => PlatformImpl.GetFeature(type);
}
}
View on GitHub (pinned to 11c5427268)
Solutions
- Always wrap PushTransformContainer in a using/using-declaration.
- Ensure every push has a matching pop on all code paths including exceptions.
- Keep transform-container pushes lexically scoped.
Example fix
// before
ctx.PushTransformContainer(); // result discarded
ctx.Dispose(); // throws: stack non-empty
// after
using (ctx.PushTransformContainer()) { /* draw */ }
ctx.Dispose(); Defensive patterns
Strategy: validation
Validate before calling
// Always pair PushTransformContainer with a using.
using (ctx.PushTransformContainer()) { /* draw */ }
// Now safe to dispose the context. Prevention
- Wrap every PushTransformContainer in using.
- Ensure matching pop on all code paths including exceptions.
- Keep transform-container pushes lexically scoped.
When it happens
Trigger: Calling PushTransformContainer and disposing the context without disposing the returned PushedState — a leaked transform-container push leaves an entry on the stack.
Common situations: Forgetting to using/dispose a PushTransformContainer result; an exception skipping the dispose; conditional branches that push but do not pop.
Related errors
- Wrong Push/Pop state order
- ImmediateDrawingContext
- DrawingContext
- Unable to obtain ANativeWindow
- TopLevel.InternalView was not expected to be null.
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/9854188352bd6955.
Report an issue: GitHub.