dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'comparer')

Error message

Value cannot be null. (Parameter 'comparer')

What it means

The comparer-based SequenceEqual overload requires a non-null IEqualityComparer<TSource>; a null comparer throws ArgumentNullException with parameter 'comparer'. Unlike LINQ-to-Objects, this library does not default a null comparer to EqualityComparer<T>.Default.

Solutions

  1. Pass an explicit comparer, e.g. EqualityComparer<TSource>.Default or Comparer-based lambda wrapper.
  2. Coalesce the comparer: comparer ?? EqualityComparer<TSource>.Default.
  3. Fix the DI registration or factory so the comparer is always provided.
  4. If the comparer is truly optional, call the two-argument SequenceEqual overload instead.

Example fix

// before
var eq = left.SequenceEqual(right, options.Comparer); // options.Comparer is null
// after
var eq = left.SequenceEqual(right, options.Comparer ?? EqualityComparer<string>.Default);
Defensive patterns

Strategy: validation

Validate before calling

comparer ??= EqualityComparer<TSource>.Default;

Type guard

static bool HasComparer<T>(IEqualityComparer<T>? c) => c is not null;

Try / catch

try { var eq = left.SequenceEqual(right, comparer); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { var eq2 = left.SequenceEqual(right); /* default comparer */ }

Prevention

When it happens

Trigger: Calling first.SequenceEqual(second, null) where the comparer comes from an optional parameter, DI-resolved service, or config-driven selection that yielded null.

Common situations: Optional comparer parameters defaulted to null instead of EqualityComparer<TSource>.Default; dependency-injected comparer not registered; settings key missing so the comparer factory returns null.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/SequenceEqual.cs:48

                    var secondTask = second.SubscribeSafeAsync(secondObserver);

                    // REVIEW: Consider concurrent subscriptions.

                    var d1 = await firstTask.ConfigureAwait(false);
                    var d2 = await secondTask.ConfigureAwait(false);

                    return StableCompositeAsyncDisposable.Create(d1, d2);
                });
        }

        public static IAsyncObservable<bool> SequenceEqual<TSource>(this IAsyncObservable<TSource> first, IAsyncObservable<TSource> second, IEqualityComparer<TSource> comparer)
        {
            if (first == null)
                throw new ArgumentNullException(nameof(first));
            if (second == null)
                throw new ArgumentNullException(nameof(second));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return CreateAsyncObservable<bool>.From(
                first,
                (second, comparer),
                static async (first, state, observer) =>
                {
                    var (firstObserver, secondObserver) = AsyncObserver.SequenceEqual(observer, state.comparer);

                    var firstTask = first.SubscribeSafeAsync(firstObserver);
                    var secondTask = state.second.SubscribeSafeAsync(secondObserver);

                    // REVIEW: Consider concurrent subscriptions.

                    var d1 = await firstTask.ConfigureAwait(false);
                    var d2 = await secondTask.ConfigureAwait(false);

                    return StableCompositeAsyncDisposable.Create(d1, d2);
                });

View on GitHub (pinned to 94b5d5ab91)