dotnet/reactive · error · ArgumentNullException

rightDurationSelector

Error message

rightDurationSelector

What it means

GroupJoin needs rightDurationSelector to compute, for each right item, the observable whose completion removes that item from open join windows. A null selector is rejected with ArgumentNullException as the fourth guard in the method, before any subscription.

Solutions

  1. Provide a right duration observable, e.g. `r => rightTimer` or an immediate-complete async observable.
  2. Re-check overload/parameter order: left is (left, right, leftDurationSelector, rightDurationSelector, resultSelector).
  3. Guard delegates in wrapper APIs before forwarding to GroupJoin.

Example fix

// before
var result = left.GroupJoin(right, l => leftTimer, null, (l, rs) => rs);
// after
var result = left.GroupJoin(right, l => leftTimer, r => rightTimer, (l, rs) => rs);
Defensive patterns

Strategy: validation

Validate before calling

if (rightDurationSelector is null) throw new ArgumentNullException(nameof(rightDurationSelector));

Type guard

bool IsValidSelector<TRight,TDur>(Func<TRight, IAsyncObservable<TDur>>? f) => f is not null;

Try / catch

try
{
    var result = left.GroupJoin(right, lds, rds, rs);
}
catch (ArgumentNullException ex) when (ex.ParamName == "rightDurationSelector")
{
    // supply a default duration or switch to a non-windowed operator
}

Prevention

When it happens

Trigger: Calling GroupJoin with null as the `rightDurationSelector` (Func<TRight, IAsyncObservable<TRightDuration>>), e.g. `left.GroupJoin(right, lds, null, rs)`.

Common situations: Asymmetric refactors where the left duration was added but the right one left null; porting synchronous Rx GroupJoin code where selector slots were reordered; generic helpers passing through nullable delegates.

Related errors


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

Appendix: source

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

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);

                var rightSubscription = await right.SubscribeSafeAsync(rightObserver).ConfigureAwait(false);
                await subscriptions.AddAsync(rightSubscription).ConfigureAwait(false);

                return disposable;
            });
        }

View on GitHub (pinned to 94b5d5ab91)