dotnet/maui · error · InvalidOperationException

Cannot assign a native control without an Element; Renderer

Error message

Cannot assign a native control without an Element; Renderer unbound and/or disposed. Please consult Microsoft.Maui.Controls.Compatibility renderers for reference implementation of OnElementChanged.

What it means

VisualElementRenderer.SetControl (called via SetNativeControl) throws InvalidOperationException when assigning a native control while Element is null. This indicates the renderer is unbound — either disposed, or SetNativeControl was called before SetElement in the lifecycle. The renderer needs an Element to wire up platform-state consistency and event handlers.

Source

Thrown at src/Compatibility/Core/src/Windows/VisualElementRenderer.cs:429

				oldControl.Loaded -= OnControlLoaded;
				oldControl.GotFocus -= OnControlGotFocus;
				oldControl.LostFocus -= OnControlLostFocus;
			}

			UpdateTracker();

			if (control == null)
			{
				_controlChanged?.Invoke(this, EventArgs.Empty);
				return;
			}

			Control.HorizontalAlignment = Microsoft.UI.Xaml.HorizontalAlignment.Stretch;
			Control.VerticalAlignment = Microsoft.UI.Xaml.VerticalAlignment.Stretch;

			if (Element == null)
				throw new InvalidOperationException(
					"Cannot assign a native control without an Element; Renderer unbound and/or disposed. " +
					"Please consult Microsoft.Maui.Controls.Compatibility renderers for reference implementation of OnElementChanged.");

			Element.IsPlatformStateConsistent = false;
			control.Loaded += OnControlLoaded;

			control.GotFocus += OnControlGotFocus;
			control.LostFocus += OnControlLostFocus;

#pragma warning disable RS0030 // Do not use banned APIs; Panel.Children is banned for performance reasons.
			Children.Add(control);
#pragma warning restore RS0030 // Do not use banned APIs

			UpdateBackgroundColor();
			UpdateBackground();

			if (Element != null && !string.IsNullOrEmpty(Element.AutomationId))
				SetAutomationId(Element.AutomationId);

View on GitHub (pinned to f377ff1c5e)

Solutions

  1. Guard SetNativeControl: if (Element != null) { SetNativeControl(control); }
  2. Ensure SetNativeControl is called after SetElement — typically inside OnElementChanged after base.SetElement
  3. Check _disposed flag before calling SetNativeControl in async callbacks
  4. Avoid calling SetNativeControl from constructors; defer to OnElementChanged

Example fix

// before
protected override void OnElementChanged(ElementChangedEventArgs<MyView> e)
{
    base.OnElementChanged(e);
    SetNativeControl(new MyNativeControl()); // may throw if Element is null after dispose
}

// after
protected override void OnElementChanged(ElementChangedEventArgs<MyView> e)
{
    base.OnElementChanged(e);
    if (e.NewElement != null && !_disposed && Control == null)
        SetNativeControl(new MyNativeControl());
}
Defensive patterns

Strategy: validation

Validate before calling

// Before calling SetNativeControl, verify Element is bound
if (Element == null || _disposed)
    return;
SetNativeControl(nativeControl);

Try / catch

try
{
    SetNativeControl(control);
}
catch (InvalidOperationException ex) when (ex.Message.Contains("without an Element"))
{
    // Renderer was disposed — abort control assignment
    // This is a lifecycle race; the renderer should not be reused
}

Prevention

When it happens

Trigger: Calling SetNativeControl after Dispose has nulled the Element; renderer reuse after disposal; calling SetNativeControl in OnElementChanged before Element is assigned; async race condition where Dispose runs between element binding and control assignment (common in cell recycling).

Common situations: Custom renderer calling SetNativeControl in the constructor instead of OnElementChanged; ListView/CollectionView cell recycling reuses disposed renderers; rapid page navigation triggers disposal during async control setup.

Related errors


AI-assisted analysis of dotnet/maui@f377ff1c5e (2026-08-13). Data as JSON: /api/errors/479045258d4d9b08. Report an issue: GitHub.