dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'disposable')

Error message

Value cannot be null. (Parameter 'disposable')

What it means

The ContextDisposable constructor throws ArgumentNullException when the disposable argument is null. The wrapped IDisposable is the action payload whose Dispose runs on the provided SynchronizationContext; a null wrapped resource cannot be dispatched. The constructor validates both parameters with ?? throw.

Solutions

  1. Check the resource for null before constructing; skip creation when null.
  2. Substitute Disposable.Empty when a no-op disposable is acceptable: new ContextDisposable(ctx, Disposable.Empty).
  3. Fix the resource factory so it throws a meaningful error instead of returning null.

Example fix

// before
var d = new ContextDisposable(ctx, AcquireResource()); // may return null
// after
var resource = AcquireResource();
var d = resource != null ? new ContextDisposable(ctx, resource) : null;
Defensive patterns

Strategy: validation

Validate before calling

if (resource == null) return null; // or use Disposable.Empty
var d = new ContextDisposable(context, resource);

Type guard

ContextDisposable TryCreate(SynchronizationContext ctx, IDisposable d) => ctx == null || d == null ? null : new ContextDisposable(ctx, d);

Try / catch

try { var d = new ContextDisposable(context, resource); } catch (ArgumentNullException ex) when (ex.ParamName == "disposable") { /* handle missing resource */ }

Prevention

When it happens

Trigger: new ContextDisposable(ctx, null), usually when the wrapped resource comes from a factory/lookup that returned null (failed acquisition of a stream, subscription, or handle).

Common situations: Wrapping resources obtained from methods that return null on failure instead of throwing; conditional resource creation where the null path was not handled before constructing the ContextDisposable.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Disposables/ContextDisposable.cs:26

namespace System.Reactive.Disposables
{
    /// <summary>
    /// Represents a disposable resource whose disposal invocation will be posted to the specified <seealso cref="SynchronizationContext"/>.
    /// </summary>
    public sealed class ContextDisposable : ICancelable
    {
        private volatile IDisposable _disposable;

        /// <summary>
        /// Initializes a new instance of the <see cref="ContextDisposable"/> class that uses the specified <see cref="SynchronizationContext"/> on which to dispose the specified disposable resource.
        /// </summary>
        /// <param name="context">Context to perform disposal on.</param>
        /// <param name="disposable">Disposable whose Dispose operation to run on the given synchronization context.</param>
        /// <exception cref="ArgumentNullException"><paramref name="context"/> or <paramref name="disposable"/> is null.</exception>
        public ContextDisposable(SynchronizationContext context, IDisposable disposable)
        {
            Context = context ?? throw new ArgumentNullException(nameof(context));
            _disposable = disposable ?? throw new ArgumentNullException(nameof(disposable));
        }

        /// <summary>
        /// Gets the provided <see cref="SynchronizationContext"/>.
        /// </summary>
        public SynchronizationContext Context { get; }

        /// <summary>
        /// Gets a value that indicates whether the object is disposed.
        /// </summary>
        public bool IsDisposed => _disposable == BooleanDisposable.True;

        /// <summary>
        /// Disposes the underlying disposable on the provided <see cref="SynchronizationContext"/>.
        /// </summary>
        public void Dispose()
        {
            var old = Interlocked.Exchange(ref _disposable, BooleanDisposable.True);

View on GitHub (pinned to 94b5d5ab91)