dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'onCompletedAsync')

Error message

Value cannot be null. (Parameter 'onCompletedAsync')

What it means

UnsafeAsyncObserver's constructor requires a non-null onCompletedAsync delegate and throws ArgumentNullException when it is null. The library intentionally has no default completion handler so that subscription authors always define completion behavior explicitly. The guard fires immediately in the constructor, before any events are delivered.

Solutions

  1. Pass a non-null completion delegate, e.g. () => ValueTask.CompletedTask
  2. Supply all three delegates at every UnsafeAsyncObserver construction site
  3. Create a factory/builder with sensible defaults to avoid hand-rolling the three arguments each time

Example fix

// before
var observer = new UnsafeAsyncObserver<int>(
    x => ValueTask.CompletedTask,
    ex => ValueTask.CompletedTask,
    null);
// after
var observer = new UnsafeAsyncObserver<int>(
    x => ValueTask.CompletedTask,
    ex => ValueTask.CompletedTask,
    () => ValueTask.CompletedTask);
Defensive patterns

Strategy: validation

Validate before calling

if (onCompletedAsync == null) throw new ArgumentException("onCompletedAsync delegate is required");

Type guard

static bool IsValidObserver<T>(Func<T, ValueTask> n, Func<Exception, ValueTask> e, Func<ValueTask> c) => n != null && e != null && c != null;

Try / catch

try { var obs = new UnsafeAsyncObserver<int>(onNext, onError, onCompleted); } catch (ArgumentNullException ex) { /* ex.ParamName == "onCompletedAsync" */ }

Prevention

When it happens

Trigger: Calling new UnsafeAsyncObserver<T>(onNextAsync, onErrorAsync, null) — passing a null Func<ValueTask> as the third constructor argument.

Common situations: Building an observer that only cares about values or errors and forgetting completion; generating observers via code that conditionally omits the completion callback; converting from Rx observers where OnCompleted is implemented but was not wired through.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Internal/UnsafeAsyncObserver.cs:19

// 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.Threading.Tasks;

namespace System.Reactive
{
    public class UnsafeAsyncObserver<T> : IAsyncObserver<T>
    {
        private readonly Func<T, ValueTask> _onNextAsync;
        private readonly Func<Exception, ValueTask> _onErrorAsync;
        private readonly Func<ValueTask> _onCompletedAsync;

        public UnsafeAsyncObserver(Func<T, ValueTask> onNextAsync, Func<Exception, ValueTask> onErrorAsync, Func<ValueTask> onCompletedAsync)
        {
            _onNextAsync = onNextAsync ?? throw new ArgumentNullException(nameof(onNextAsync));
            _onErrorAsync = onErrorAsync ?? throw new ArgumentNullException(nameof(onErrorAsync));
            _onCompletedAsync = onCompletedAsync ?? throw new ArgumentNullException(nameof(onCompletedAsync));
        }

        public ValueTask OnCompletedAsync() => _onCompletedAsync();

        public ValueTask OnErrorAsync(Exception error) => _onErrorAsync(error ?? throw new ArgumentNullException(nameof(error)));

        public ValueTask OnNextAsync(T value) => _onNextAsync(value);
    }
}

View on GitHub (pinned to 94b5d5ab91)