stride3d/stride · error · InvalidOperationException

A routed event named

Error message

A routed event named '{name}' already exists in provided owner type '{ownerType}' or base classes.

What it means

RegisterRoutedEvent<T> enforces globally unique routed event names per owner type (including base classes). If GetRoutedEvent(ownerType, name) finds an existing event, registration is rejected with InvalidOperationException to prevent ambiguous event identifiers in the routing system.

Solutions

  1. Remove the duplicate RegisterRoutedEvent call and reuse the existing static RoutedEvent<T> field
  2. Rename your event to a unique name within the owner type hierarchy
  3. Check base classes for an existing event of the same name before registering

Example fix

// before
public static readonly RoutedEvent<RoutedEventArgs> TapEvent =
    EventManager.RegisterRoutedEvent<RoutedEventArgs>("Tap", RoutingStrategy.Bubble, typeof(MyButton)); // also registered in base
// after
public static readonly RoutedEvent<RoutedEventArgs> TapEvent =
    BaseControl.TapEvent; // reuse existing registration
Defensive patterns

Strategy: validation

Validate before calling

if (EventManager.GetRoutedEvent<RoutedEventArgs>(ownerType, name) != null)
    throw new InvalidOperationException($"Event '{name}' already registered for {ownerType}");

Try / catch

try { evt = EventManager.RegisterRoutedEvent<T>(name, strategy, ownerType); }
catch (InvalidOperationException ex) when (ex.Message.Contains("already exists")) { evt = EventManager.GetRoutedEvent<RoutedEventArgs>(ownerType, name); }

Prevention

When it happens

Trigger: Calling EventManager.RegisterRoutedEvent twice with the same (name, ownerType) pair — e.g. duplicate static registration code, the same event inherited from a base class, or a control defined in two assemblies both registering the same name.

Common situations: See trigger scenarios.

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/2954bee3ffcd1c21. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.UI/Events/EventManager.cs:135

        /// <summary>
        /// Registers a new routed event.
        /// </summary>
        /// <param name="name">The name of the routed event. The name must be unique within the owner type (base class included) and cannot be null or an empty string.</param>
        /// <param name="routingStrategy">The routing strategy of the event as a value of the enumeration.</param>
        /// <param name="ownerType">The owner class type of the routed event. This cannot be null.</param>
        /// <returns>The identifier for the newly registered routed event. 
        /// This identifier object can now be stored as a static field in a class and then used as a parameter for methods that attach handlers to the event. 
        /// The routed event identifier is also used for other event system APIs.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="name"/> or <paramref name="ownerType"/> is null.</exception>
        /// <exception cref="InvalidOperationException">This exception is thrown if a routed event of name <paramref name="name"/> already exists for type <paramref name="ownerType"/> and parents.
        /// </exception>
        public static RoutedEvent<T> RegisterRoutedEvent<T>(string name, RoutingStrategy routingStrategy, Type ownerType) where T: RoutedEventArgs
        {
            if (name == null) throw new ArgumentNullException(nameof(name));
            if (ownerType == null) throw new ArgumentNullException(nameof(ownerType));
            
            if (GetRoutedEvent(ownerType, name) != null)
                throw new InvalidOperationException("A routed event named '" + name + "' already exists in provided owner type '" + ownerType + "' or base classes.");

            var newRoutedEvent = new RoutedEvent<T> {  Name = name, OwnerType = ownerType, RoutingStrategy = routingStrategy, };
            lock(SyncRoot)
            {
                RoutedEvents.Add(newRoutedEvent);

                if (!OwnerToEvents.ContainsKey(ownerType))
                    OwnerToEvents[ownerType] = new Dictionary<string, RoutedEvent>();

                OwnerToEvents[ownerType][name] = newRoutedEvent; 
            }

            return newRoutedEvent;
        }

        private static readonly List<RoutedEvent> RoutedEvents = new List<RoutedEvent>();
        private static readonly Dictionary<Type, Dictionary<string, RoutedEvent>> OwnerToEvents = new Dictionary<Type, Dictionary<string, RoutedEvent>>();
 

View on GitHub (pinned to 96fad776d2)