{"record":{"id":"9e17025288d14859","repo":"dotnet/reactive","slug":"action-value-cannot-be-null-scheduler-services-emulation","errorCode":null,"errorMessage":"action (Value cannot be null)","messagePattern":"action \\(Value cannot be null\\)","errorType":"validation","errorClass":"ArgumentNullException","httpStatus":null,"severity":"error","filePath":"Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs","lineNumber":42,"sourceCode":"        /// <param name=\"action\">Action to be executed, potentially updating the state.</param>\n        /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"scheduler\"/> or <paramref name=\"action\"/> is <c>null</c>.</exception>\n        /// <exception cref=\"ArgumentOutOfRangeException\"><paramref name=\"period\"/> is less than <see cref=\"TimeSpan.Zero\"/>.</exception>\n        public static IDisposable SchedulePeriodic<TState>(this IScheduler scheduler, TState state, TimeSpan period, Func<TState, TState> action)\n        {\n            if (scheduler == null)\n            {\n                throw new ArgumentNullException(nameof(scheduler));\n            }\n\n            if (period < TimeSpan.Zero)\n            {\n                throw new ArgumentOutOfRangeException(nameof(period));\n            }\n\n            if (action == null)\n            {\n                throw new ArgumentNullException(nameof(action));\n            }\n\n            return SchedulePeriodic_(scheduler, state, period, action);\n        }\n\n        /// <summary>\n        /// Schedules a periodic piece of work by dynamically discovering the scheduler's capabilities.\n        /// If the scheduler supports periodic scheduling, the request will be forwarded to the periodic scheduling implementation.\n        /// If the scheduler provides stopwatch functionality, the periodic task will be emulated using recursive scheduling with a stopwatch to correct for time slippage.\n        /// Otherwise, the periodic task will be emulated using recursive scheduling.\n        /// </summary>\n        /// <typeparam name=\"TState\">The type of the state passed to the scheduled action.</typeparam>\n        /// <param name=\"scheduler\">Scheduler to execute the action on.</param>\n        /// <param name=\"state\">State passed to the action to be executed.</param>\n        /// <param name=\"period\">Period for running the work periodically.</param>\n        /// <param name=\"action\">Action to be executed.</param>\n        /// <returns>The disposable object used to cancel the scheduled recurring action (best effort).</returns>\n        /// <exception cref=\"ArgumentNullException\"><paramref name=\"scheduler\"/> or <paramref name=\"action\"/> is <c>null</c>.</exception>","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/dotnet/reactive/blob/94b5d5ab912789f5abe9a72138a25bbd716fe59c/Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Services.Emulation.cs#L24-L60","documentation":"SchedulePeriodic<TState>(IScheduler, TState, TimeSpan, Func<TState,TState>) throws ArgumentNullException when the per-tick Func<TState,TState> action is null. This overload's action both observes and returns the next state, so Rx validates it before delegating to SchedulePeriodic_. Like other argument checks, it fires at the call site.","triggerScenarios":"Calling scheduler.SchedulePeriodic(state, period, (Func<TState,TState>)null), passing an unassigned delegate field, or a function-returning-func helper that returned null.","commonSituations":"State-update functions built dynamically (e.g. a reducer table lookup that missed), delegates captured from an event or plugin that was not loaded, and refactors where the lambda was deleted but the call kept.","solutions":["Pass a non-null state-transforming Func, e.g. scheduler.SchedulePeriodic(state, period, s => Tick(s))","Validate with ArgumentNullException.ThrowIfNull(action) at your own boundary for a clearer message","If the Func comes from a registry/factory, add a fallback (identity function) or fail fast when it cannot be resolved"],"exampleFix":"// before\nFunc<int, int> tick = reducers[name]; // missing key -> null\nscheduler.SchedulePeriodic(state, period, tick);\n\n// after\nFunc<int, int> tick = reducers.TryGetValue(name, out var r) ? r : s => s;\nscheduler.SchedulePeriodic(state, period, tick);","handlingStrategy":"validation","validationCode":"if (action is null) throw new ArgumentNullException(nameof(action));\nscheduler.SchedulePeriodic(state, period, action);","typeGuard":"bool HasTickFunction<TState>(Func<TState, TState> a) => a is not null;","tryCatchPattern":"try { scheduler.SchedulePeriodic(state, period, action); }\ncatch (ArgumentNullException ex) when (ex.ParamName == \"action\") { /* use identity fallback: s => s */ }","preventionTips":["Never allow delegate-producing lookups to return null; use TryGetValue with fallback","Declare tick functions as non-nullable under NRT","Unit-test reducer/registry tables for missing keys"],"tags":["rx","scheduler","argument-null"],"backgroundTag":"null-argument","analyzedSha":"94b5d5ab912789f5abe9a72138a25bbd716fe59c","analyzedAt":"2026-09-15T02:26:24.759Z","contentChangedAt":"2026-09-15T02:26:24.759Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}