dotnet/reactive · error · ArgumentNullException

rightDurationSelector

Error message

rightDurationSelector

What it means

The Join operator throws ArgumentNullException because the rightDurationSelector delegate was null. This delegate maps each right item to its duration observable and is required for windowed join semantics. The library validates it eagerly at call time.

Solutions

  1. Supply a non-null Func<TRight, IAsyncObservable<TRightDuration>> as the fourth argument.
  2. Ensure the delegate variable is initialized before the call.
  3. Use a stub selector returning an appropriate duration observable when the value is genuinely optional.

Example fix

// before
var result = left.Join(right, l => l.Duration, null, (l, r) => (l, r));
// after
var result = left.Join(right, l => l.Duration, r => AsyncObservable.Empty<TRightDuration>(), (l, r) => (l, r));
Defensive patterns

Strategy: validation

Validate before calling

if (rightDurationSelector is null) throw new ArgumentException("rightDurationSelector is required", nameof(rightDurationSelector));

Type guard

bool SelectorSet<TR, TD>(Func<TR, IAsyncObservable<TD>>? f) => f is not null;

Try / catch

try { var joined = left.Join(right, lds, rds, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "rightDurationSelector") { /* supply the missing selector */ }

Prevention

When it happens

Trigger: Calling left.Join(right, leftDurationSelector, null, resultSelector) — e.g. the fourth argument was dropped during a refactor or a nullable delegate variable held null.

Common situations: Refactors that changed a Join overload and dropped the right duration selector, or delegates resolved from configuration/DI that default to null.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Join.cs:23

using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Threading;
using System.Threading.Tasks;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<TResult> Join<TLeft, TRight, TLeftDuration, TRightDuration, TResult>(this IAsyncObservable<TLeft> left, IAsyncObservable<TRight> right, Func<TLeft, IAsyncObservable<TLeftDuration>> leftDurationSelector, Func<TRight, IAsyncObservable<TRightDuration>> rightDurationSelector, Func<TLeft, 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 CreateAsyncObservable<TResult>.From(
                left,
                (right, leftDurationSelector, rightDurationSelector, resultSelector),
                static async (left, state, observer) =>
                {
                    var subscriptions = new CompositeAsyncDisposable();

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

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

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

View on GitHub (pinned to 94b5d5ab91)