dotnet/wpf · warning · ObjectDisposedException

SR.TextEditorSpellerInteropHasBeenDisposed

Error message

SR.TextEditorSpellerInteropHasBeenDisposed

What it means

NLGSpellerInterop.Dispose(bool) guards against double-disposal: if _isDisposed is already true, calling Dispose again throws ObjectDisposedException with SR.TextEditorSpellerInteropHasBeenDisposed rather than silently returning.

Solutions

  1. Track disposal in the owner and call Dispose only once (guard with a bool or Interlocked flag).
  2. Wrap the speller interop in a class that implements the standard dispose pattern tolerating multiple Dispose calls.
  3. Catch ObjectDisposedException around cleanup code in shutdown paths.
  4. Note: unlike most .NET types, this Dispose is not idempotent - check _isDisposed-visible state indirectly via calls that throw on use.

Example fix

// before
speller.Dispose();
speller.Dispose(); // throws
// after
if (!disposed)
{
    speller.Dispose();
    disposed = true;
}
Defensive patterns

Strategy: try-catch

Try / catch

try { speller.Dispose(); }
catch (ObjectDisposedException) { /* already disposed */ }

Prevention

When it happens

Trigger: Calling Dispose (or using/Dispose pattern completion) a second time on the same NLGSpellerInterop instance - e.g. explicit Dispose followed by a using block or finalizer-driven cleanup.

Common situations: Speller interop objects shared between TextEditor components and disposed by more than one owner; manual cleanup plus IDisposable dispose in the same shutdown path.

Related errors


AI-assisted analysis of dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/cbea25b29dfc5ccb. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/NLGSpellerInterop.cs:116

        //  IDispose Methods
        //
        //------------------------------------------------------

        #region IDispose Methods

        public override void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Internal interop resource cleanup
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (_isDisposed)
                throw new ObjectDisposedException(SR.TextEditorSpellerInteropHasBeenDisposed);

            if (_textChunk != null)
            {
                Marshal.ReleaseComObject(_textChunk);
                _textChunk = null;
            }

            // Stop the lifetime of Natural Language library
            UnsafeNlMethods.NlUnload();

            _isDisposed = true;
        }

        #endregion IDispose Methods

        //------------------------------------------------------
        //
        //  Internal Methods

View on GitHub (pinned to 81131a70a4)