dotnet/wpf · error · ObjectDisposedException

SR.TextEditorSpellerInteropHasBeenDisposed

Error message

SR.TextEditorSpellerInteropHasBeenDisposed

What it means

WinRTSpellerInterop.Dispose(bool) throws ObjectDisposedException(SR.TextEditorSpellerInteropHasBeenDisposed) if Dispose is called when _isDisposed is already true. Double-dispose of the speller interop is treated as an error rather than idempotent.

Solutions

  1. Track your own disposed flag and skip the second Dispose call.
  2. Call Dispose from exactly one place in your ownership chain.
  3. Wrap in try/catch (ObjectDisposedException) when you can't control the disposal path.

Example fix

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

Strategy: try-catch

Validate before calling

if (spellerDisposed) return;

Try / catch

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

Prevention

When it happens

Trigger: Calling Dispose() (or the finalizer path) a second time on the same WinRTSpellerInterop instance — e.g. explicit Dispose followed by a using block or container teardown.

Common situations: Owning object disposal chains that dispose the speller twice; finalizer racing with an explicit Dispose without the guard callers expect.

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/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/91df9af91f39a2aa. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/WinRTSpellerInterop.cs:110

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

        /// <summary>
        /// Internal interop resource cleanup
        /// </summary>
        /// <param name="disposing">
        ///     False when called from the Finalizer
        ///     True when called explicitly from Dispose()
        /// </param>
        protected override void Dispose(bool disposing)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(SR.TextEditorSpellerInteropHasBeenDisposed);
            }

            try
            {
                // Ensure that Dispose is called from the UI thread
                // If it is not, then make it so
                if (BeginInvokeOnUIThread((Action<bool>)Dispose, DispatcherPriority.Normal, disposing) == null)
                {
                    // Already on UI thread
                    // Continue with core Dispose logic

                    ReleaseAllResources(disposing);
                    _isDisposed = true;
                }
            }
            catch (InvalidOperationException)
            {
                // We have no way determining whether or not this is running

View on GitHub (pinned to 81131a70a4)