dotnet/reactive · error · InvalidOperationException

Strings_Core.DISPOSABLE_ALREADY_ASSIGNED

Error message

Strings_Core.DISPOSABLE_ALREADY_ASSIGNED

What it means

SingleAssignmentDisposableValue.Disposable may be assigned only once; a second assignment raises InvalidOperationException with the DISPOSABLE_ALREADY_ASSIGNED message. This enforces the single-assignment contract that lets the class safely swap-free storage of one disposable.

Solutions

  1. Create a new SingleAssignmentDisposableValue for each assignment instead of reusing one.
  2. Check whether it is already assigned (e.g. keep a flag or use TrySetSingle semantics) before setting.
  3. Switch to SerialDisposable, which allows the inner disposable to be replaced repeatedly.

Example fix

// before
subscription.Disposable = newDisposable; // second assignment -> throws
// after
subscription = new SingleAssignmentDisposableValue { Disposable = newDisposable };
Defensive patterns

Strategy: type-guard

Validate before calling

// Only assign once per instance; recreate for new subscriptions
subscription = new SingleAssignmentDisposableValue { Disposable = d };

Type guard

static bool CanAssign(SingleAssignmentDisposableValue s, IDisposable newValue) => true; // no public read; guard by design: never reassign

Try / catch

try { sabv.Disposable = d; }
catch (InvalidOperationException) { /* already assigned: create a new instance or ignore duplicate assign */ }

Prevention

When it happens

Trigger: Setting the Disposable property a second time with a different value after an initial assignment — e.g. re-using a SingleAssignmentDisposableValue field for a second subscription or re-running an initialization path.

Common situations: Re-subscribing with the same field without recreating the wrapper, event handlers firing initialization twice, retry loops that assign a new disposable into the same field, or accidental shared instances between two call sites.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at Rx.NET/Source/src/System.Reactive/Disposables/SingleAssignmentDisposableValue.cs:39

            // We use a sentinel value to indicate we've been disposed. This sentinel never leaks
            // to the outside world (see the Disposable property getter), so no-one can ever assign
            // this value to us manually.
            Volatile.Read(ref _current) == BooleanDisposable.True;

        /// <summary>
        /// Gets or sets the underlying disposable. After disposal, the result of getting this property is undefined.
        /// </summary>
        /// <exception cref="InvalidOperationException">Thrown if the <see cref="SingleAssignmentDisposable"/> has already been assigned to.</exception>
        public IDisposable? Disposable
        {
            get => Disposables.Disposable.GetValueOrDefault(ref _current);
            set
            {
                var result = Disposables.Disposable.TrySetSingle(ref _current, value);

                if (result == TrySetSingleResult.AlreadyAssigned)
                {
                    throw new InvalidOperationException(Strings_Core.DISPOSABLE_ALREADY_ASSIGNED);
                }
            }
        }

        /// <summary>
        /// Disposes the underlying disposable.
        /// </summary>
        public void Dispose()
        {
            Disposables.Disposable.Dispose(ref _current);
        }

        /// <inheritdoc/>
        public override readonly bool Equals(object? obj) => false;

        /// <inheritdoc/>
        public override readonly int GetHashCode() => 0;

View on GitHub (pinned to 94b5d5ab91)