dotnet/reactive · error · ArgumentNullException
action (Value cannot be null)
Error message
action (Value cannot be null)
What it means
This is the action-null branch of the same Schedule(Action) extension: the scheduler was valid but the Action delegate passed for scheduling is null, so ArgumentNullException with paramName "action" is thrown. Rx validates both arguments up front.
Solutions
- Pass a non-null lambda or delegate, even a no-op such as () => { }
- Check the delegate-producing code path and give it a default value before scheduling
- Guard the call: only schedule when the callback is non-null, otherwise run it inline or skip
Example fix
// before
Action callback = GetCallback(); // may be null
scheduler.Schedule(callback); // throws
// after
Action callback = GetCallback() ?? (() => { });
scheduler.Schedule(callback); Defensive patterns
Strategy: validation
Validate before calling
if (action is null)
action = () => { }; // no-op default
scheduler.Schedule(action); Type guard
bool IsRunnable(Action? a) => a is not null;
Try / catch
try
{
scheduler.Schedule(callback);
}
catch (ArgumentNullException ex) when (ex.ParamName == "action")
{
scheduler.Schedule(() => { });
} Prevention
- Initialize Action fields with a no-op instead of null
- Make factory methods that produce callbacks return no-op delegates rather than null
- Validate optional callbacks before passing them to Schedule
When it happens
Trigger: Calling scheduler.Schedule(null) or passing a null delegate variable: e.g. scheduler.Schedule(myAction) where myAction is an uninitialized Action field, or a method returning null Action that is forwarded directly into Schedule.
Common situations: Optional callbacks wired only under certain conditions that end up null; reflection or configuration-built delegates that failed to resolve; refactors that removed a lambda assignment but left the Schedule call.
Related errors
- Value cannot be null. (Parameter 'bufferClosingSelector')
- scheduler (Value cannot be null)
- Value cannot be null. (Parameter 'invokeHandler')
- Value cannot be null. (Parameter 'invoke')
- null (Parameter 'scheduler')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/06f47b67630ccf9c.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/Scheduler.Simple.cs:27
public static partial class Scheduler
{
/// <summary>
/// Schedules an action to be executed.
/// </summary>
/// <param name="scheduler">Scheduler to execute the action on.</param>
/// <param name="action">Action to execute.</param>
/// <returns>The disposable object used to cancel the scheduled action (best effort).</returns>
/// <exception cref="ArgumentNullException"><paramref name="scheduler"/> or <paramref name="action"/> is <c>null</c>.</exception>
public static IDisposable Schedule(this IScheduler scheduler, Action action)
{
if (scheduler == null)
{
throw new ArgumentNullException(nameof(scheduler));
}
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
// Surprisingly, passing the method group of Invoke will create a fresh
// delegate each time although it's static, while an anonymous
// lambda without the need of a closure will be cached.
// Once Roslyn supports caching delegates for method groups,
// the anonymous lambda can be replaced by the method group again. Until then,
// to avoid the repetition of code, the call to Invoke is left intact.
// Watch https://github.com/dotnet/roslyn/issues/5835
return scheduler.Schedule(action, static (_, a) => Invoke(a));
}
/// <summary>
/// Schedules an action to be executed.
/// </summary>
/// <param name="scheduler">Scheduler to execute the action on.</param>
/// <param name="state">A state object to be passed to <paramref name="action"/>.</param>
/// <param name="action">Action to execute.</param>View on GitHub (pinned to 94b5d5ab91)