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
- Remove the duplicate RegisterRoutedEvent call and reuse the existing static RoutedEvent<T> field
- Rename your event to a unique name within the owner type hierarchy
- 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
- Register each routed event exactly once in a static readonly field
- Check base classes for same-named events before adding new ones
- Namespace event names uniquely across shared control libraries
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
- Value cannot be null. (Parameter 'routedEvent')
- Value cannot be null. (Parameter 'handler')
- Value cannot be null. (Parameter 'name')
- Value cannot be null. (Parameter 'ownerType')
- The routed event cannot be changed while the event is being…
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)