dotnet/reactive · error · ArgumentNullException

resultSelector

Error message

resultSelector

What it means

The Join operator throws ArgumentNullException because the resultSelector delegate was null. The result selector combines each matched (left, right) pair into a TResult and is essential to the operator's output. It is validated up front so the failure occurs at operator construction, not on first match.

Solutions

  1. Pass a non-null Func<TLeft, TRight, TResult> as the fifth argument.
  2. If you truly need pass-through, use (l, r) => (l, r) tuple selection instead of null.
  3. Check any wrapper/helper API that forwards selectors and ensure defaults are non-null.

Example fix

// before
var result = left.Join(right, l => l.Duration, r => r.Duration, null);
// after
var result = left.Join(right, l => l.Duration, r => r.Duration, (l, r) => new Match(l, r));
Defensive patterns

Strategy: validation

Validate before calling

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

Type guard

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

Try / catch

try { var joined = left.Join(right, lds, rds, rs); }
catch (ArgumentNullException ex) when (ex.ParamName == "resultSelector") { /* supply a selector, e.g. tuple projection */ }

Prevention

When it happens

Trigger: Calling left.Join(right, leftDurationSelector, rightDurationSelector, null) — commonly caused by forgetting the final lambda or a null delegate field.

Common situations: Copy-paste of Join calls where the result lambda was removed, or generic helper methods that forward a null selector parameter.

Related errors


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

Appendix: source

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

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

                    return disposable;
                });

View on GitHub (pinned to 94b5d5ab91)