dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'observer')

Error message

Value cannot be null. (Parameter 'observer')

What it means

The comparer-overload of ToLookup validates all delegates up front. A null keySelector makes grouping impossible, so ArgumentNullException(nameof(keySelector)) is thrown immediately when composing the operator, before any elements flow.

Solutions

  1. Supply a non-null Func<TSource,ValueTask<TKey>> or Func<TSource,TKey> key selector
  2. Assert the key selector is resolved (e.g. from config/DI) before composing the pipeline
  3. Default to an identity selector x => x when grouping by the element itself

Example fix

// before
var op = AsyncObservable.ToLookup(sink, (Func<Order, int>)null, x => x.Name, comparer);
// after
var op = AsyncObservable.ToLookup(sink, x => x.CustomerId, x => x.Name, comparer);
Defensive patterns

Strategy: validation

Validate before calling

if (observer is null || keySelector is null) throw new ArgumentException("observer and keySelector are required");
var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector, comparer);

Type guard

static bool CanGroup<TSource,TKey>(Func<TSource,ValueTask<TKey>> keySelector) => keySelector != null;

Try / catch

try
{
    var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "keySelector")
{
    keySelector = x => new ValueTask<TKey>(DefaultKey(x)); // fallback key
}

Prevention

When it happens

Trigger: Calling AsyncObservable.ToLookup(observer, null, valueSelector, comparer) — 4-argument overload at ToLookup.cs:136 (the 3-argument overload's keySelector guard hits the same pattern at line 122).

Common situations: Key selector built from a lookup table or configuration map that was null; refactoring renamed a method leaving a null Func; generic helper forwarding a null selector.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/AsyncObservableBase.cs:14

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information. 

using System.Threading.Tasks;

namespace System.Reactive
{
    public abstract class AsyncObservableBase<T> : IAsyncObservable<T>
    {
        public async ValueTask<IAsyncDisposable> SubscribeAsync(IAsyncObserver<T> observer)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));

            var autoDetach = new AutoDetachAsyncObserver(observer);

            var subscription = await SubscribeAsyncCore(autoDetach).ConfigureAwait(false);

            await autoDetach.AssignAsync(subscription).ConfigureAwait(false);

            return autoDetach;
        }

        protected abstract ValueTask<IAsyncDisposable> SubscribeAsyncCore(IAsyncObserver<T> observer);

        private sealed class AutoDetachAsyncObserver : AsyncObserverBase<T>, IAsyncDisposable
        {
            private readonly IAsyncObserver<T> _observer;
            private readonly object _gate = new();

            private IAsyncDisposable _subscription;

View on GitHub (pinned to 94b5d5ab91)