dotnet/reactive · error · ArgumentNullException

leftDurationSelector

Error message

leftDurationSelector

What it means

The Join operator throws ArgumentNullException because the leftDurationSelector delegate was null. This delegate maps each left item to the observable that governs how long it stays joinable; without it the operator cannot function, so it is validated eagerly. The library throws immediately at operator creation rather than during subscription.

Solutions

  1. Supply a non-null Func<TLeft, IAsyncObservable<TLeftDuration>> as the third argument.
  2. Verify the delegate variable is assigned before the Join call (guard with a null check or default lambda).
  3. If durations are irrelevant, return an empty/never-completing observable from the selector as appropriate instead of passing null.

Example fix

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

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try { var joined = left.Join(right, lds, rds, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "leftDurationSelector") { /* supply a default selector and retry */ }

Prevention

When it happens

Trigger: Calling left.Join(right, null, rightDurationSelector, resultSelector) — typically the lambda was omitted or a delegate variable was null.

Common situations: Storing selector delegates in fields/dependency-injected services that were never assigned, or calling via reflection/dynamic wrappers that pass null defaults.

Related errors


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

Appendix: source

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

// See the LICENSE file in the project root for more information. 

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

View on GitHub (pinned to 94b5d5ab91)