dotnet/reactive · error · ArgumentNullException

comparer (Parameter 'comparer')

Error message

comparer (Parameter 'comparer')

What it means

The protected ScheduledItem<TAbsolute>.ctor(dueTime, comparer) throws ArgumentNullException when comparer is null. The comparer orders work items inside priority-queue-based schedulers (e.g. VirtualTimeScheduler, PriorityQueue) and is required for every enqueue, so the base constructor validates it immediately.

Solutions

  1. Pass Comparer<TAbsolute>.Default when no custom ordering is needed.
  2. Implement or register an IComparer<TAbsolute> and ensure it is non-null before constructing.
  3. In subclass constructors, guard: 'base(dueTime, comparer ?? Comparer<TAbsolute>.Default)'.
  4. Check DI registration for the comparer service.

Example fix

// before
protected MyItem(DateTimeOffset dueTime, IComparer<DateTimeOffset> comparer) : base(dueTime, comparer) { }
// after
protected MyItem(DateTimeOffset dueTime, IComparer<DateTimeOffset> comparer)
    : base(dueTime, comparer ?? Comparer<DateTimeOffset>.Default) { }
Defensive patterns

Strategy: validation

Validate before calling

if (comparer == null)
    comparer = Comparer<TAbsolute>.Default;

Type guard

bool HasComparer<TAbsolute>(IComparer<TAbsolute> c) => c is not null;

Try / catch

try
{
    var item = new MyScheduledItem(dueTime, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "comparer")
{
    var item = new MyScheduledItem(dueTime, Comparer<TAbsolute>.Default);
}

Prevention

When it happens

Trigger: Writing a custom ScheduledItem subclass that calls base(dueTime, null), or constructing built-in ScheduledItem variants through helper code that resolves an IComparer<TAbsolute> dynamically and gets null.

Common situations: Custom virtual-time schedulers for unit tests where a Comparer was forgotten; generics with default-comparer helpers returning null instead of Comparer<T>.Default; DI-injected comparer services not registered.

Related errors


AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15). Data as JSON: /api/errors/207cc0d4cc84da13. Report an issue: GitHub.

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Concurrency/ScheduledItem.cs:30

    /// </summary>
    /// <typeparam name="TAbsolute">Absolute time representation type.</typeparam>
#pragma warning disable CA1033, CA1063, CA1816 // (Overridable IDisposable.) This is a specialized base type, and it would be inappropriate to encourage anyone to build derived types that do more in Dispose.
    public abstract class ScheduledItem<TAbsolute> : IScheduledItem<TAbsolute>, IComparable<ScheduledItem<TAbsolute>>, IDisposable
        where TAbsolute : IComparable<TAbsolute>
    {
        private SingleAssignmentDisposableValue _disposable;
        private readonly IComparer<TAbsolute> _comparer;

        /// <summary>
        /// Creates a new scheduled work item to run at the specified time.
        /// </summary>
        /// <param name="dueTime">Absolute time at which the work item has to be executed.</param>
        /// <param name="comparer">Comparer used to compare work items based on their scheduled time.</param>
        /// <exception cref="ArgumentNullException"><paramref name="comparer"/> is <c>null</c>.</exception>
        protected ScheduledItem(TAbsolute dueTime, IComparer<TAbsolute> comparer)
        {
            DueTime = dueTime;
            _comparer = comparer ?? throw new ArgumentNullException(nameof(comparer));
        }

        /// <summary>
        /// Gets the absolute time at which the item is due for invocation.
        /// </summary>
        public TAbsolute DueTime { get; }

        /// <summary>
        /// Invokes the work item.
        /// </summary>
        public void Invoke()
        {
            if (!_disposable.IsDisposed)
            {
                _disposable.Disposable = InvokeCore();
            }
        }

View on GitHub (pinned to 94b5d5ab91)