stride3d/stride · error · InvalidOperationException

This BehaviorCollection has already been attached to a…

Error message

This BehaviorCollection has already been attached to a dependency object.

What it means

BehaviorCollection.Attach throws InvalidOperationException when the collection's AssociatedObject is already set to a different dependency object. Each BehaviorCollection instance may be attached to exactly one object for its lifetime; re-attaching is an unsupported state transition.

Solutions

  1. Create a new BehaviorCollection for each dependency object instead of reusing one.
  2. Detach from the old object (set AssociatedObject to null / use Detach if available) before re-attaching.
  3. Store behaviors as per-instance resources rather than shared StaticResource; use DynamicResource or x:Shared="False".
  4. Guard with ReferenceEquals(AssociatedObject, target) checks — Attach is a no-op for the same object.

Example fix

// before
var shared = (BehaviorCollection)Application.Current.Resources["MyBehaviors"];
shared.Attach(controlA);
shared.Attach(controlB); // throws
// after
var forA = new BehaviorCollection();
forA.AddRange(shared);
forA.Attach(controlA);
Defensive patterns

Strategy: try-catch

Validate before calling

if (behaviors.AssociatedObject != null && !ReferenceEquals(behaviors.AssociatedObject, target))
    behaviors = CloneBehaviors(behaviors); // make a per-control copy
behaviors.Attach(target);

Type guard

static bool CanAttach(BehaviorCollection c, DependencyObject target) => c.AssociatedObject == null || ReferenceEquals(c.AssociatedObject, target);

Try / catch

try { behaviors.Attach(control); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already been attached"))
{
    var copy = new BehaviorCollection();
    copy.AddRange(behaviors);
    copy.Attach(control);
}

Prevention

When it happens

Trigger: Calling Attach on a BehaviorCollection whose AssociatedObject is non-null and different from the new dependencyObject; reusing a BehaviorCollection across windows/controls; re-attaching a collection stored in a shared resource.

Common situations: Sharing a BehaviorCollection defined in App.xaml resources between multiple controls; recycling controls in virtualized lists while keeping behavior collections; calling Attach twice after re-templating.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/cbbdde6e34ddc114. Report an issue: GitHub.

Appendix: source

Thrown at sources/presentation/Stride.Core.Presentation.Wpf/Interactivity/BehaviorCollection.cs:44

        public BehaviorCollection Clone()
        {
            var clone = new BehaviorCollection();
            foreach (var behavior in Items)
            {
                clone.Add((Behavior)behavior.Clone());
            }
            return clone;
        }

        public void Attach([NotNull] DependencyObject dependencyObject)
        {
            if (dependencyObject == null) throw new ArgumentNullException(nameof(dependencyObject));
            // Aleady attached
            if (ReferenceEquals(AssociatedObject, dependencyObject))
                return;

            if (AssociatedObject != null)
                throw new InvalidOperationException("This BehaviorCollection has already been attached to a dependency object.");

            AssociatedObject = dependencyObject;
            var behaviors = Microsoft.Xaml.Behaviors.Interaction.GetBehaviors(dependencyObject);
            foreach (var behavior in this)
            {
                behaviors.Add(behavior);
            }
        }

        public void Detach()
        {
            if (AssociatedObject != null)
            {
                var behaviors = Microsoft.Xaml.Behaviors.Interaction.GetBehaviors(AssociatedObject);
                foreach (var behavior in this)
                {
                    behaviors.Remove(behavior);
                }

View on GitHub (pinned to 96fad776d2)