dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'left')

Error message

Value cannot be null. (Parameter 'left')

What it means

GroupJoin joins two async observables and needs the `left` source sequence to exist; it's the first parameter validated. Passing null throws ArgumentNullException immediately. The operator never handles a null source lazily — the check happens before Create is called.

Solutions

  1. Ensure the left source is non-null before calling; replace null-returning factories with AsyncObservable.Empty<T>().
  2. Null-check the source variable: `if (left == null) left = AsyncObservable.Empty<TLeft>();`
  3. Fix DI/field initialization so the source observable is registered.

Example fix

// before
var result = maybeLeft.GroupJoin(right, lds, rds, rs); // maybeLeft is null
// after
var left = maybeLeft ?? AsyncObservable.Empty<TLeft>();
var result = left.GroupJoin(right, lds, rds, rs);
Defensive patterns

Strategy: validation

Validate before calling

if (left is null) throw new ArgumentNullException(nameof(left));
// or normalize before calling:
left ??= AsyncObservable.Empty<TLeft>();

Type guard

bool IsValidSource<T>(IAsyncObservable<T>? s) => s is not null;

Try / catch

try
{
    var result = left.GroupJoin(right, lds, rds, rs);
}
catch (ArgumentNullException ex) when (ex.ParamName == "left")
{
    var result = AsyncObservable.Empty<TResult>();
}

Prevention

When it happens

Trigger: Calling `leftSource.GroupJoin(...)` where the receiver expression `left` is null (e.g. a null-returning factory), or invoking the static AsyncObservable.GroupJoin with null as the first argument.

Common situations: Async observable factory methods that return null instead of an empty sequence; DI-resolved stream dependencies that weren't registered; LINQ-style chains built conditionally where the seed sequence was never assigned.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/GroupJoin.cs:18

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

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<TResult> GroupJoin<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(this IAsyncObservable<TLeft> left, IAsyncObservable<TRight> right, Func<TLeft, IAsyncObservable<TLeftDuration>> leftDurationSelector, Func<TRight, IAsyncObservable<TRightDuration>> rightDurationSelector, Func<TLeft, IAsyncObservable<TRight>, TResult> resultSelector)
        {
            if (left == null)
                throw new ArgumentNullException(nameof(left));
            if (right == null)
                throw new ArgumentNullException(nameof(right));
            if (leftDurationSelector == null)
                throw new ArgumentNullException(nameof(leftDurationSelector));
            if (rightDurationSelector == null)
                throw new ArgumentNullException(nameof(rightDurationSelector));
            if (resultSelector == null)
                throw new ArgumentNullException(nameof(resultSelector));

            return Create<TResult>(async observer =>
            {
                var subscriptions = new CompositeAsyncDisposable();

                var (leftObserver, rightObserver, disposable) = AsyncObserver.GroupJoin(observer, subscriptions, leftDurationSelector, rightDurationSelector, resultSelector);

                var leftSubscription = await left.SubscribeSafeAsync(leftObserver).ConfigureAwait(false);
                await subscriptions.AddAsync(leftSubscription).ConfigureAwait(false);

View on GitHub (pinned to 94b5d5ab91)