dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'source')

Error message

Value cannot be null. (Parameter 'source')

What it means

The Switch extension operator requires a non-null source observable of inner observables. It throws ArgumentNullException at composition time (Switch.cs:15) so that a null source fails immediately at the call site rather than surfacing as a NullReferenceException deep inside SubscribeAsync. This is standard defensive validation across all AsyncRx operators.

Solutions

  1. Verify the outer observable is non-null before invoking .Switch()
  2. Check any factory/cache lookup that produced the source and handle its null return before chaining
  3. Add a null check or throw a descriptive exception at the pipeline construction site
  4. Restructure so the inner observables are produced by a non-null source (e.g. Select over an existing sequence)

Example fix

// before
var merged = maybeSource.Switch(); // NRE/ANRE when maybeSource is null
// after
if (maybeSource == null) throw new InvalidOperationException("inner source not initialized");
var merged = maybeSource.Switch();
Defensive patterns

Strategy: validation

Validate before calling

if (source is null)
    throw new InvalidOperationException("Switch requires a non-null source of inner observables.");
var merged = source.Switch();

Type guard

static bool HasSource<T>(IAsyncObservable<IAsyncObservable<T>>? s) => s is not null;

Try / catch

try
{
    var merged = source.Switch();
}
catch (ArgumentNullException ex) when (ex.ParamName == "source")
{
    logger.LogError(ex, "Switch called with null source; check the upstream factory/lookup");
}

Prevention

When it happens

Trigger: Calling source.Switch() where source is a null IAsyncObservable<IAsyncObservable<T>>, e.g. a factory method or dictionary lookup that returned null before being chained with .Switch().

Common situations: Chaining Switch onto the result of another operator or a memoized cache entry that is null; refactoring where an observable field was renamed or not yet assigned; conditional pipeline construction where a branch forgot to assign the source.

Related errors


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

Appendix: source

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

// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT License.
// See the LICENSE file in the project root for more information. 

using System.Reactive.Disposables;
using System.Threading;

namespace System.Reactive.Linq
{
    public partial class AsyncObservable
    {
        public static IAsyncObservable<TSource> Switch<TSource>(this IAsyncObservable<IAsyncObservable<TSource>> source)
        {
            if (source == null)
                throw new ArgumentNullException(nameof(source));

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

View on GitHub (pinned to 94b5d5ab91)