dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'observer')

Error message

Value cannot be null. (Parameter 'observer')

What it means

AsyncObserver.Switch is the low-level sink factory for the Switch operator and validates that the downstream observer is non-null, throwing ArgumentNullException at Switch.cs:35. It guards the state machine (gate, hasLatest, latest tracking) that would otherwise capture a null observer and fail on the first OnNextAsync notification.

Solutions

  1. Pass a valid IAsyncObserver<TSource> obtained from your subscription context
  2. If writing a custom operator, validate the observer yourself before delegating to AsyncObserver.Switch
  3. Prefer the public source.Switch() extension, which wires up a real observer automatically

Example fix

// before
var (sink, cancel) = AsyncObserver.Switch<TSource>(observer); // observer may be null
// after
if (observer == null) throw new ArgumentNullException(nameof(observer));
var (sink, cancel) = AsyncObserver.Switch<TSource>(observer);
Defensive patterns

Strategy: validation

Validate before calling

if (observer is null)
    throw new ArgumentNullException(nameof(observer));
var (sink, cancel) = AsyncObserver.Switch(observer);

Type guard

static bool HasObserver<T>(IAsyncObserver<T>? o) => o is not null;

Try / catch

try
{
    var (sink, cancel) = AsyncObserver.Switch(observer);
}
catch (ArgumentNullException ex) when (ex.ParamName == "observer")
{
    // custom-operator wiring bug: the downstream observer was never supplied
    logger.LogError(ex, "AsyncObserver.Switch requires a non-null observer");
}

Prevention

When it happens

Trigger: Calling AsyncObserver.Switch<TSource>(null) directly, or a custom Create(...) composition that passes a null observer into AsyncObserver.Switch.

Common situations: Writing custom operators on top of AsyncObserver primitives where the observer parameter flows through unvalidated; unit tests passing null to exercise guard clauses.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Switch.cs:35

            return Create<IAsyncObservable<TSource>, TSource>(
                source,
                async static (source, observer) =>
                {
                    var (sink, cancel) = AsyncObserver.Switch(observer);

                    var subscription = await source.SubscribeSafeAsync(sink).ConfigureAwait(false);

                    return StableCompositeAsyncDisposable.Create(subscription, cancel);
                });
        }
    }

    public partial class AsyncObserver
    {
        public static (IAsyncObserver<IAsyncObservable<TSource>>, IAsyncDisposable) Switch<TSource>(IAsyncObserver<TSource> observer)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));

            var gate = new AsyncGate();

            var isStopped = false;
            var hasLatest = false;
            var latest = 0UL;

            var disposable = new SerialAsyncDisposable();

            return
                (
                    Create<IAsyncObservable<TSource>>(
                        async xs =>
                        {
                            ulong id;

                            using (await gate.LockAsync().ConfigureAwait(false))
                            {

View on GitHub (pinned to 94b5d5ab91)