dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'source')

Error message

Value cannot be null. (Parameter 'source')

What it means

In the comparer-overload of ToLookup, the valueSelector is checked after observer and keySelector. Passing a null valueSelector prevents projecting element values into the Lookup<TKey,TValue> accumulator built via Aggregate, so ArgumentNullException(nameof(valueSelector)) is thrown at composition time (ToLookup.cs:138).

Solutions

  1. Pass a non-null Func<TSource,ValueTask<TValue>> as the value selector
  2. Guard computed delegates with a null check before composing
  3. Use x => x to keep whole elements as values instead of null

Example fix

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

Strategy: validation

Validate before calling

if (keySelector is null) throw new ArgumentNullException(nameof(keySelector));
if (valueSelector is null) throw new ArgumentNullException(nameof(valueSelector));

Type guard

static bool ValidSelectors<TSource,TKey,TValue>(Func<TSource,ValueTask<TKey>> k, Func<TSource,ValueTask<TValue>> v) => k != null && v != null;

Try / catch

try
{
    var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "valueSelector")
{
    // fall back to identity selector or report configuration error
}

Prevention

When it happens

Trigger: Calling AsyncObservable.ToLookup(observer, keySelector, null, comparer) — 4-argument overload.

Common situations: Value selector supplied conditionally (only for some overloads); a projected delegate stored in a field that was never assigned; copy-paste between overloads dropping one argument.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/AsyncObservableExtensions.cs:15

// 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.Reactive;
using System.Threading.Tasks;

namespace System
{
    public static class AsyncObservableExtensions
    {
        public static ValueTask<IAsyncDisposable> SubscribeAsync<T>(this IAsyncObservable<T> source, Func<T, ValueTask> onNextAsync)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (onNextAsync == null)
                throw new ArgumentNullException(nameof(onNextAsync));

            return source.SubscribeAsync(new AsyncObserver<T>(onNextAsync, ex => new ValueTask(Task.FromException(ex)), () => default));
        }

        public static ValueTask<IAsyncDisposable> SubscribeAsync<T>(this IAsyncObservable<T> source, Func<T, ValueTask> onNextAsync, Func<Exception, ValueTask> onErrorAsync)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (onNextAsync == null)
                throw new ArgumentNullException(nameof(onNextAsync));
            if (onErrorAsync == null)
                throw new ArgumentNullException(nameof(onErrorAsync));

            return source.SubscribeAsync(new AsyncObserver<T>(onNextAsync, onErrorAsync, () => default));
        }

View on GitHub (pinned to 94b5d5ab91)