dotnet/reactive · error · ArgumentNullException

ArgumentNullException(nameof(comparer))

Error message

ArgumentNullException(nameof(comparer))

What it means

The comparer overload of ToLookup requires a non-null IEqualityComparer<TKey>. Passing a null comparer throws ArgumentNullException(nameof(comparer)) synchronously; pass EqualityComparer<TKey>.Default for default key comparison.

Solutions

  1. Use EqualityComparer<TKey>.Default when you want default comparison.
  2. Verify your comparer is constructed/registered before building the query.
  3. Drop the comparer argument and use the two-selector overload.

Example fix

// before
IEqualityComparer<string> cmp = GetComparer(); // may be null
var lookup = src.ToLookup(o => o.Name, o => o, cmp);
// after
var cmp = GetComparer() ?? EqualityComparer<string>.Default;
var lookup = src.ToLookup(o => o.Name, o => o, cmp);
Defensive patterns

Strategy: validation

Validate before calling

var cmp = maybeComparer ?? EqualityComparer<TKey>.Default;
if (cmp is null) throw new ArgumentNullException(nameof(comparer));

Type guard

static bool IsValidComparer<TKey>(IEqualityComparer<TKey> c) => c is not null;

Try / catch

try { var lookup = src.ToLookup(keySel, valSel, cmp); }
catch (ArgumentNullException ex) when (ex.ParamName == "comparer") { /* fall back to default comparer */ }

Prevention

When it happens

Trigger: Calling source.ToLookup(keySelector, valueSelector, comparer) with comparer == null, usually because a comparer field was null or the comparer parameter defaulted to null.

Common situations: Injecting custom comparers via DI where registration is missing; conditionally chosen comparers that end up null for a given key type.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/ToLookup.cs:37

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

            return CreateAsyncObservable<ILookup<TKey, TValue>>.From(
                source,
                (keySelector, valueSelector),
                static (source, state, observer) => source.SubscribeSafeAsync(AsyncObserver.ToLookup(observer, state.keySelector, state.valueSelector)));
        }

        public static IAsyncObservable<ILookup<TKey, TValue>> ToLookup<TSource, TKey, TValue>(this IAsyncObservable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> valueSelector, IEqualityComparer<TKey> comparer)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (valueSelector == null)
                throw new ArgumentNullException(nameof(valueSelector));
            if (comparer == null)
                throw new ArgumentNullException(nameof(comparer));

            return CreateAsyncObservable<ILookup<TKey, TValue>>.From(
                source,
                (keySelector, valueSelector, comparer),
                static (source, state, observer) => source.SubscribeSafeAsync(AsyncObserver.ToLookup(observer, state.keySelector, state.valueSelector, state.comparer)));
        }

        public static IAsyncObservable<ILookup<TKey, TValue>> ToLookup<TSource, TKey, TValue>(this IAsyncObservable<TSource> source, Func<TSource, ValueTask<TKey>> keySelector, Func<TSource, ValueTask<TValue>> valueSelector)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (keySelector == null)
                throw new ArgumentNullException(nameof(keySelector));
            if (valueSelector == null)
                throw new ArgumentNullException(nameof(valueSelector));

            return CreateAsyncObservable<ILookup<TKey, TValue>>.From(
                source,

View on GitHub (pinned to 94b5d5ab91)