AvaloniaUI/Avalonia · error · InvalidOperationException
{this} already has an owner.
Error message
{this} already has an owner. What it means
ExpressionNode.SetOwner throws InvalidOperationException when Owner is already non-null. Each ExpressionNode belongs to exactly one BindingExpression for its lifetime; reusing a node in a second binding would corrupt both expressions' node chains. The guard prevents silent double-ownership.
Source
Thrown at src/Avalonia.Base/Data/Core/ExpressionNodes/ExpressionNode.cs:75
public virtual void BuildString(StringBuilder builder, IReadOnlyList<ExpressionNode> nodes)
{
if (Index > 0)
nodes[Index - 1].BuildString(builder, nodes);
BuildString(builder);
}
/// <summary>
/// Sets the owner binding.
/// </summary>
/// <param name="owner">The owner binding.</param>
/// <param name="index">The index of the node in the binding path.</param>
/// <exception cref="InvalidOperationException">
/// The node already has an owner.
/// </exception>
public void SetOwner(BindingExpression owner, int index)
{
if (Owner is not null)
throw new InvalidOperationException($"{this} already has an owner.");
Owner = owner;
Index = index;
}
/// <summary>
/// Sets the <see cref="Source"/> from which the node will read its value and updates
/// the current <see cref="Value"/>, notifying the <see cref="Owner"/> if the value
/// changes.
/// </summary>
/// <param name="source">
/// The new source from which the node will read its value. May be
/// <see cref="AvaloniaProperty.UnsetValue"/> in which case the source will be considered
/// to be null.
/// </param>
/// <param name="dataValidationError">
/// Any data validation error reported by the previous expression node.
/// </param>
public void SetSource(object? source, Exception? dataValidationError)View on GitHub (pinned to 11c5427268)
Solutions
- Create fresh ExpressionNode instances for each binding; never reuse a node across BindingExpressions.
- If pooling, ensure each node's Owner is cleared (or a new node created) before reuse.
- When copying a node chain, deep-clone the nodes rather than aliasing them.
Example fix
// before - reusing a node list across two expressions var nodes = BuildNodes(); expr1.SetNodes(nodes); expr2.SetNodes(nodes); // throws: already owned // after expr1.SetNodes(BuildNodes()); expr2.SetNodes(BuildNodes());
Defensive patterns
Strategy: validation
Validate before calling
// ExpressionNode instances must be uniquely owned. Never reuse across bindings.
if (node.Owner is not null)
throw new InvalidOperationException("Node already owned; create a new instance."); Type guard
static bool IsUnowned(ExpressionNode n) => n.Owner is null;
Prevention
- Never cache/share ExpressionNode instances across BindingExpressions.
- Build a fresh node chain per binding.
- When pooling, reset Owner (or recreate nodes) before reuse.
When it happens
Trigger: Passing the same ExpressionNode instance into two different BindingExpression instances, or calling SetOwner twice on one node.
Common situations: Caching/sharing node instances to 'optimize' binding creation; a factory that returns pooled nodes without resetting ownership; a copy that aliases node references instead of cloning.
Related errors
- Binding mode cannot be Default.
- UpdateSourceTrigger cannot be Default.
- Expected '{open}'.
- The ResourceDictionary already has a parent.
- Two way bindings are not supported with a string format
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/fdfafbc5c5f8acb2.
Report an issue: GitHub.