stride3d/stride · error · ArgumentNullException

Value cannot be null. (Parameter 'name')

Error message

Value cannot be null. (Parameter 'name')

What it means

RegisterRoutedEvent<T> requires a non-null event name because the name is part of the routed event's identity used for lookups (GetRoutedEvent) and duplicate detection. A null name is rejected with ArgumentNullException before any registration state changes.

Solutions

  1. Pass a literal or guaranteed-initialized const string as the event name
  2. Null-check any dynamically sourced name before calling RegisterRoutedEvent
  3. Verify static field initialization order so the name constant is assigned first

Example fix

// before
string evtName = LoadEventName();
var evt = EventManager.RegisterRoutedEvent<RoutedEventArgs>(evtName, RoutingStrategy.Bubble, typeof(MyControl));
// after
string evtName = LoadEventName() ?? "MyCustomEvent";
var evt = EventManager.RegisterRoutedEvent<RoutedEventArgs>(evtName, RoutingStrategy.Bubble, typeof(MyControl));
Defensive patterns

Strategy: validation

Validate before calling

if (string.IsNullOrEmpty(name)) throw new InvalidOperationException("Event name must be a non-empty string");

Type guard

static bool ValidEventName(string name) => !string.IsNullOrWhiteSpace(name);

Try / catch

try { return EventManager.RegisterRoutedEvent<T>(name, strategy, ownerType); }
catch (ArgumentNullException ex) { throw new InvalidOperationException($"Bad routed event name '{name}'", ex); }

Prevention

When it happens

Trigger: Calling EventManager.RegisterRoutedEvent<T>(null, routingStrategy, ownerType), usually via a name constant that is null or a config-driven name that failed to load.

Common situations: Constants/resource files not loaded at static-ctor time; reflection reading an attribute property that defaults to null; refactoring that removed the string literal.

Related errors


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

Appendix: source

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

        }

        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)