dotnet/reactive · error · ArgumentNullException
Value cannot be null. (Parameter 'comparer')
Error message
Value cannot be null. (Parameter 'comparer')
What it means
The protected VirtualTimeSchedulerBase constructor stores the comparer used to order scheduled work by absolute time. A null comparer would break every Clock comparison and queue ordering, so the constructor throws ArgumentNullException.
Solutions
- Pass Comparer<TAbsolute>.Default as the comparer in the base constructor call.
- Pass a concrete comparer instance (e.g. Comparer<long>.Create(...)) for custom time types.
- Validate the comparer parameter in your subclass constructor before calling base().
Example fix
// before
public MyScheduler(long initialClock, IComparer<long> comparer) : base(initialClock, comparer) { }
new MyScheduler(0, null);
// after
new MyScheduler(0, Comparer<long>.Default); Defensive patterns
Strategy: validation
Validate before calling
if (comparer == null) comparer = Comparer<TAbsolute>.Default;
Type guard
bool ValidComparer<TAbsolute>(IComparer<TAbsolute> c) => c is not null;
Try / catch
try { new MyScheduler(0, comparer); } catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { /* fall back to Comparer<T>.Default */ } Prevention
- Always pass Comparer<TAbsolute>.Default unless a custom comparer is required.
- In subclass constructors, propagate the comparer parameter and validate it before base().
When it happens
Trigger: Deriving from VirtualTimeSchedulerBase (or VirtualTimeScheduler) and calling the base constructor with base(initialClock, null), typically when the comparer is a field initialized after the base call or a constructor parameter not propagated.
Common situations: Writing a custom virtual-time scheduler for tests; copying a scheduler subclass and dropping the Comparer<T>.Default argument; a constructor parameter left null by the caller of the subclass.
Related errors
- comparer
- source
- Value cannot be null. (Parameter 'onNextAsync')
- Value cannot be null. (Parameter 'onErrorAsync')
- Value cannot be null. (Parameter 'onCompletedAsync')
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/f0cb3c5e689139c0.
Report an issue: GitHub.
Appendix: source
Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/VirtualTimeScheduler.cs:40
: this(default!, Comparer<TAbsolute>.Default)
{
//
// NB: We allow a default value for TAbsolute here, which typically is a struct. For compat reasons, we can't
// add a generic constraint (either struct or, better, new()), and maybe a derived class has handled null
// in all abstract methods.
//
}
/// <summary>
/// Creates a new virtual time scheduler with the specified initial clock value and absolute time comparer.
/// </summary>
/// <param name="initialClock">Initial value for the clock.</param>
/// <param name="comparer">Comparer to determine causality of events based on absolute time.</param>
/// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <c>null</c>.</exception>
protected VirtualTimeSchedulerBase(TAbsolute initialClock, IComparer<TAbsolute> comparer)
{
Clock = initialClock;
Comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
}
/// <summary>
/// Adds a relative time value to an absolute time value.
/// </summary>
/// <param name="absolute">Absolute time value.</param>
/// <param name="relative">Relative time value to add.</param>
/// <returns>The resulting absolute time sum value.</returns>
protected abstract TAbsolute Add(TAbsolute absolute, TRelative relative);
/// <summary>
/// Converts the absolute time value to a DateTimeOffset value.
/// </summary>
/// <param name="absolute">Absolute time value to convert.</param>
/// <returns>The corresponding DateTimeOffset value.</returns>
protected abstract DateTimeOffset ToDateTimeOffset(TAbsolute absolute);
/// <summary>View on GitHub (pinned to 94b5d5ab91)