dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'disposable')

Error message

Value cannot be null. (Parameter 'disposable')

What it means

SerialAsyncDisposable.AssignAsync throws ArgumentNullException when the disposable to assign is null. A serial disposable guarantees exactly one current child disposable at a time, and assigning null is disallowed — dispose the serial disposable itself instead of swapping in null.

Solutions

  1. Never pass null to AssignAsync; call DisposeAsync on the SerialAsyncDisposable itself to release the current child
  2. Fix the upstream factory/operator that returned null instead of a real (possibly no-op) disposable
  3. Guard the call site: 'if (d != null) await serial.AssignAsync(d); else await serial.DisposeAsync();'
  4. Use Disposable.Empty / a shared no-op IAsyncDisposable where 'nothing to dispose' is intended

Example fix

// before
await serial.AssignAsync(GetSubscription()); // may return null
// after
var d = GetSubscription() ?? NopAsyncDisposable.Instance;
await serial.AssignAsync(d);
Defensive patterns

Strategy: validation

Validate before calling

if (d is null)
{
    await serial.DisposeAsync(); // clearing = disposing, never assigning null
}
else
{
    await serial.AssignAsync(d);
}

Type guard

bool IsAssignable([NotNullWhen(true)] IAsyncDisposable? d) => d is not null;

Try / catch

try
{
    await serial.AssignAsync(d);
}
catch (ArgumentNullException ex) when (ex.ParamName == "disposable")
{
    // factory returned null; fall back to disposing the serial disposable
    await serial.DisposeAsync();
}

Prevention

When it happens

Trigger: Calling AssignAsync(null) directly; operator sinks (Buffer, Catch, Concat, DoWhile, For, OnErrorResumeNext) passing a null child disposable produced upstream, e.g. when a subscription factory returned null on a cancelled or failed path.

Common situations: A subscription helper returns null instead of a disposable when a source completes synchronously; passing the result of an optional resource acquisition without a null check; replacing an old pattern of assigning null to clear the serial disposable.

Related errors


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

Appendix: source

Thrown at AsyncRx.NET/System.Reactive.Async/Disposables/SerialAsyncDisposable.cs:20

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

namespace System.Reactive.Disposables
{
    public sealed class SerialAsyncDisposable : IAsyncDisposable
    {
        private readonly AsyncGate _gate = new();

        private IAsyncDisposable _disposable;
        private bool _disposed;

        public async ValueTask AssignAsync(IAsyncDisposable disposable)
        {
            if (disposable == null)
                throw new ArgumentNullException(nameof(disposable));

            var shouldDispose = false;
            var old = default(IAsyncDisposable);

            using (await _gate.LockAsync().ConfigureAwait(false))
            {
                if (_disposed)
                {
                    shouldDispose = true;
                }
                else
                {
                    old = _disposable;
                    _disposable = disposable;
                }
            }

            if (old != null)

View on GitHub (pinned to 94b5d5ab91)