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

  1. Pass Comparer<TAbsolute>.Default as the comparer in the base constructor call.
  2. Pass a concrete comparer instance (e.g. Comparer<long>.Create(...)) for custom time types.
  3. 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

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


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)