dotnet/reactive · error · ArgumentNullException
Value cannot be null. (Parameter 'onNextAsync')
Error message
Value cannot be null. (Parameter 'onNextAsync')
What it means
The 4-argument ToLookup overload requires a non-null IEqualityComparer<TKey> because it constructs Lookup<TKey,TValue>(comparer) as the aggregate seed. A null comparer would make bucket lookups undefined, so ArgumentNullException(nameof(comparer)) is thrown before the pipeline is returned (ToLookup.cs:140).
Solutions
- Pass a valid IEqualityComparer<TKey> implementation
- Omit the comparer argument to use EqualityComparer<TKey>.Default via the 3-argument overload
- For case-insensitive keys use StringComparer.OrdinalIgnoreCase or similar
Example fix
// before IEqualityComparer<int> comparer = GetComparer(); // returns null var op = AsyncObservable.ToLookup(sink, x => x.Key, x => x.Value, comparer); // after var op = AsyncObservable.ToLookup(sink, x => x.Key, x => x.Value, EqualityComparer<int>.Default);
Defensive patterns
Strategy: validation
Validate before calling
var cmp = comparer ?? EqualityComparer<TKey>.Default; var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector, cmp);
Type guard
static IEqualityComparer<T> OrDefault<T>(IEqualityComparer<T>? c) => c ?? EqualityComparer<T>.Default;
Try / catch
try
{
var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector, comparer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "comparer")
{
var op = AsyncObservable.ToLookup(observer, keySelector, valueSelector); // default comparer
} Prevention
- Coalesce comparer to EqualityComparer<TKey>.Default when optional
- Prefer the 3-argument overload when default equality is acceptable
- Ensure custom comparers are registered/created before pipeline composition
When it happens
Trigger: Calling AsyncObservable.ToLookup(observer, keySelector, valueSelector, null); passing a comparer variable resolved from DI/config that is null.
Common situations: Comparer chosen based on settings (case-insensitive vs default) with an unhandled else-branch; caching a comparer in a dictionary lookup that missed; custom equality comparer class not registered.
Related errors
- Value cannot be null. (Parameter 'subscribeAsync')
- Value cannot be null. (Parameter 'observer')
- Value cannot be null. (Parameter 'observer')
- Value cannot be null. (Parameter 'source')
- ArgumentNullException: subscription
AI-assisted analysis of dotnet/reactive@94b5d5ab91 (2026-09-15).
Data as JSON: /api/errors/8b7f5892892468d3.
Report an issue: GitHub.
Appendix: source
Thrown at AsyncRx.NET/System.Reactive.Async/AsyncObservableExtensions.cs:17
// 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));
}
public static ValueTask<IAsyncDisposable> SubscribeAsync<T>(this IAsyncObservable<T> source, Func<T, ValueTask> onNextAsync, Func<ValueTask> onCompletedAsync)
{View on GitHub (pinned to 94b5d5ab91)