dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'comparer')

Error message

Value cannot be null. (Parameter 'comparer')

What it means

System.Interactive's DistinctUntilChanged(source, comparer) eagerly validates arguments at call time (not enumeration time). When the IEqualityComparer<TSource> passed as 'comparer' is null, it throws ArgumentNullException immediately. This fail-fast pattern ensures the error points at the buggy call site rather than a later InvalidOperationException during MoveNext.

Solutions

  1. Pass an actual comparer: DistinctUntilChanged(source, EqualityComparer<TSource>.Default) if you wanted default semantics.
  2. If you have no comparer, call the 1-argument overload DistinctUntilChanged(source) which uses the default element comparer.
  3. Null-check or ?? the comparer at the call site: comparer ?? EqualityComparer<TSource>.Default.
  4. Fix the upstream producer (factory, DI container registration, config) so it does not return null comparers.

Example fix

// before
var comparer = _comparerRegistry[type]; // null when unregistered
var result = source.DistinctUntilChanged(comparer);
// after
var comparer = _comparerRegistry[type] ?? EqualityComparer<MyType>.Default;
var result = source.DistinctUntilChanged(comparer);
Defensive patterns

Strategy: validation

Validate before calling

if (source is null) throw new ArgumentNullException(nameof(source));
if (comparer is null) comparer = EqualityComparer<TSource>.Default; // or reject with a clear message
var result = source.DistinctUntilChanged(comparer);

Type guard

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

Try / catch

try
{
    var result = source.DistinctUntilChanged(comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "comparer")
{
    // fall back to default comparer
    result = source.DistinctUntilChanged(EqualityComparer<TSource>.Default);
}

Prevention

When it happens

Trigger: Calling EnumerableEx.DistinctUntilChanged(source, comparer) with a null second argument, typically because the comparer came from a variable/parameter/lookup that was null (e.g. a static field not yet initialized, a config-resolved comparer, or passing null intending 'default comparer').

Common situations: Developers porting from Distinct() which accepts an implicit default comparer, DI-resolved comparer services that failed to register, refactoring where a comparer factory returns null, or calling the 2-arg overload believing it takes a keySelector (it takes a comparer, so null slips through intent).

Related errors


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

Appendix: source

Thrown at Ix.NET/Source/System.Interactive/System/Linq/Operators/DistinctUntilChanged.cs:37

            if (source == null)
                throw new ArgumentNullException(nameof(source));

            return DistinctUntilChangedCore(source, x => x, EqualityComparer<TSource>.Default);
        }

        /// <summary>
        /// Returns consecutive distinct elements by using the specified equality comparer to compare values.
        /// </summary>
        /// <typeparam name="TSource">Source sequence element type.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="comparer">Comparer used to compare values.</param>
        /// <returns>Sequence without adjacent non-distinct elements.</returns>
        public static IEnumerable<TSource> DistinctUntilChanged<TSource>(this IEnumerable<TSource> source, IEqualityComparer<TSource> comparer)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return DistinctUntilChangedCore(source, x => x, comparer);
        }

        /// <summary>
        /// Returns consecutive distinct elements based on a key value by using the specified equality comparer to compare key values.
        /// </summary>
        /// <typeparam name="TSource">Source sequence element type.</typeparam>
        /// <typeparam name="TKey">Key type.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="keySelector">Key selector.</param>
        /// <returns>Sequence without adjacent non-distinct elements.</returns>
        public static IEnumerable<TSource> DistinctUntilChanged<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));

View on GitHub (pinned to 94b5d5ab91)