dotnet/reactive · error · ArgumentNullException

throw new ArgumentNullException(nameof(observer));

Error message

throw new ArgumentNullException(nameof(observer));

What it means

AsyncObserver.Scan<TSource>(observer, func) — the internal observer factory used by the unseeded sync Scan operator — throws ArgumentNullException because the downstream observer was null. This guard protects the observer-wrapping layer; in typical use the public Scan method always supplies a real observer, so hitting this means Scan was invoked through internal APIs with a broken observer.

Solutions

  1. Pass the actual downstream observer: AsyncObserver.Scan(downstream, func).
  2. Fix the custom subscription path so the observer is created before Scan is called.
  3. Prefer the public source.Scan(func) API, which wires the observer for you.

Example fix

// before
IAsyncObserver<int> downstream = null;
var obs = AsyncObserver.Scan(downstream, (a, b) => a + b); // throws

// after
var obs = AsyncObserver.Scan(observer, (a, b) => a + b); // observer from the subscription callback
Defensive patterns

Strategy: validation

Validate before calling

if (observer is null) throw new InvalidOperationException("downstream observer must be provided to AsyncObserver.Scan");
var obs = AsyncObserver.Scan(observer, func);

Type guard

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

Try / catch

try { var obs = AsyncObserver.Scan(observer, f); }
catch (ArgumentNullException ex) when (ex.ParamName == "observer") { log.LogError("observer chain broken"); throw; }

Prevention

When it happens

Trigger: Calling AsyncObserver.Scan(null, func) directly or via a custom operator that forwards a null IAsyncObserver<TSource> — e.g. a hand-rolled SubscribeSafeAsync path that drops the observer on error.

Common situations: Custom operator/observer implementations reusing AsyncObserver.Scan with an observer that was never constructed; testing internal APIs; observer lost through a nullable pipeline field.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Linq/Operators/Scan.cs:69

        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));
            if (func == null)
                throw new ArgumentNullException(nameof(func));

            return CreateAsyncObservable<TResult>.From(
                source,
                (func, seed),
                static (source, state, observer) => source.SubscribeSafeAsync(AsyncObserver.Scan(observer, state.seed, state.func)));
        }
    }

    public partial class AsyncObserver
    {
        public static IAsyncObserver<TSource> Scan<TSource>(IAsyncObserver<TSource> observer, Func<TSource, TSource, TSource> func)
        {
            if (observer == null)
                throw new ArgumentNullException(nameof(observer));
            if (func == null)
                throw new ArgumentNullException(nameof(func));

            var hasValue = false;
            var value = default(TSource);

            return Create<TSource>(
                async x =>
                {
                    if (hasValue)
                    {
                        try
                        {
                            value = func(value, x);
                        }
                        catch (Exception ex)
                        {
                            await observer.OnErrorAsync(ex).ConfigureAwait(false);

View on GitHub (pinned to 94b5d5ab91)