dotnet/reactive · error · ArgumentNullException

Value cannot be null. (Parameter 'item')

Error message

Value cannot be null. (Parameter 'item')

What it means

CompositeDisposable.Add requires a non-null IDisposable; a null item has no Dispose behavior and would break Remove/Dispose bookkeeping, so ArgumentNullException with parameter name 'item' is thrown before the lock is taken. Callers listed (Subscribe, DisposeWith, Run, etc.) are typical upstream paths where a null disposable flows in.

Solutions

  1. Never return null from helper methods that produce disposables — return Disposable.Empty instead
  2. Check for null before adding: if (d != null) composite.Add(d);
  3. Ensure your ISubscribe-like implementations follow the Rx contract of returning a non-null IDisposable

Example fix

// before
composite.Add(SubscribeToSomething()); // may return null
// after
var d = SubscribeToSomething();
if (d != null) composite.Add(d);
Defensive patterns

Strategy: type-guard

Validate before calling

if (item != null) composite.Add(item);

Type guard

void AddSafe(CompositeDisposable cd, IDisposable? item) { if (item is not null) cd.Add(item); }

Try / catch

try { composite.Add(d); }
catch (ArgumentNullException) { /* log: subscription returned null disposable */ }

Prevention

When it happens

Trigger: composite.Add(null), or passing the result of a Subscribe/handler-registration helper that returned null (e.g. a hand-rolled Subscribe returning null instead of Disposable.Empty).

Common situations: Manual subscription management where teardown registration methods return null on failure; conditional code paths assigning null to a disposable variable; interop with other libraries returning null tokens.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Disposables/CompositeDisposable.cs:165

            return (list, list.Count);
        }

        /// <summary>
        /// Gets the number of disposables contained in the <see cref="CompositeDisposable"/>.
        /// </summary>
        public int Count => Volatile.Read(ref _count);

        /// <summary>
        /// Adds a disposable to the <see cref="CompositeDisposable"/> or disposes the disposable if the <see cref="CompositeDisposable"/> is disposed.
        /// </summary>
        /// <param name="item">Disposable to add.</param>
        /// <exception cref="ArgumentNullException"><paramref name="item"/> is <c>null</c>.</exception>
        public void Add(IDisposable item)
        {
            if (item == null)
            {
                throw new ArgumentNullException(nameof(item));
            }

            lock (_gate)
            {
                if (!_disposed)
                {
                    if (_disposables is List<IDisposable?> listDisposables)
                    {
                        listDisposables.Add(item);

                        // Once we get to thousands of items (which happens with wide fan-out/in configurations)
                        // the cost of linear search becomes too high. We switch to a dictionary at that point.
                        // See https://github.com/dotnet/reactive/issues/2005
                        if (listDisposables.Count > MaximumLinearSearchThreshold)
                        {
                            // If we've blown through this threshold, chances are there's more to come,
                            // so allocate some more spare capacity.
                            var dictionary = new Dictionary<IDisposable, int>(listDisposables.Count + (listDisposables.Count / 4));

View on GitHub (pinned to 94b5d5ab91)