stride3d/stride · error · ArgumentNullException

Value cannot be null. (Parameter 'ownerType')

Error message

Value cannot be null. (Parameter 'ownerType')

What it means

RegisterRoutedEvent<T> requires a non-null ownerType; the owner type scopes the event name and is used for duplicate detection and later GetRoutedEvent lookups. Null is rejected immediately with ArgumentNullException.

Solutions

  1. Pass typeof(YourControl) of the class that owns the event
  2. If the type is resolved dynamically, null-check the Type before registering
  3. Fix the type name/assembly string in Type.GetType calls

Example fix

// before
var owner = Type.GetType("MyApp.MyControl"); // null if wrong assembly
EventManager.RegisterRoutedEvent<RoutedEventArgs>("Tap", RoutingStrategy.Bubble, owner);
// after
EventManager.RegisterRoutedEvent<RoutedEventArgs>("Tap", RoutingStrategy.Bubble, typeof(MyApp.MyControl));
Defensive patterns

Strategy: validation

Validate before calling

if (ownerType == null) throw new InvalidOperationException("Owner type must be provided");

Type guard

static bool ValidOwnerType(Type t) => t != null && !t.IsGenericTypeDefinition;

Try / catch

try { return EventManager.RegisterRoutedEvent<T>(name, strategy, ownerType); }
catch (ArgumentNullException ex) when (ex.ParamName == "ownerType") { logger.LogError(ex, "Owner type was null for event {Name}", name); throw; }

Prevention

When it happens

Trigger: Calling EventManager.RegisterRoutedEvent<T>(name, routingStrategy, null), e.g. passing typeof(...) of a generic type parameter resolved at runtime that came back null, or a Type field not yet initialized.

Common situations: Reflection-based Type.GetType with a bad assembly-qualified name returning null; passing a variable instead of typeof(YourControl); static initialization order issues.

Related errors


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

Appendix: source

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

        private static readonly Dictionary<Type, Dictionary<RoutedEvent, RoutedEventHandlerInfo>> ClassesToClassHandlers = new Dictionary<Type, Dictionary<RoutedEvent, RoutedEventHandlerInfo>>();

        /// <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;
        }

View on GitHub (pinned to 96fad776d2)