dotnet/wpf · error · ObjectDisposedException

Cannot access the stream after it is closed.

Error message

Cannot access the stream after it is closed.

What it means

DeobfuscatingStream keeps an underlying _obfuscatedStream; when the wrapper is disposed, the field is set to null and every subsequent Stream API call (Read, Write, Seek, SetLength, Flush, Position) routes through CheckDisposed, which throws ObjectDisposedException. This library throws it because the deobfuscation wrapper no longer has a usable underlying stream after Close/Dispose.

Solutions

  1. Re-open the XPS part (e.g. via PackWebRequest/PackagePart.GetStream) to obtain a fresh DeobfuscatingStream instead of reusing the disposed one
  2. Restructure code so all reads happen inside the using block that owns the stream
  3. Track disposal state in the owning class and skip/refresh operations on disposed streams

Example fix

// before
DeobfuscatingStream s = CreateFontStream();
s.Dispose();
var b = new byte[32]; s.Read(b, 0, 32); // throws
// after
using (DeobfuscatingStream s = CreateFontStream())
{
    var b = new byte[32];
    s.Read(b, 0, 32);
}
Defensive patterns

Strategy: try-catch

Validate before calling

if (stream == null) throw new InvalidOperationException("Stream not initialized");
// track disposal explicitly:
bool disposed = _disposed; // expose via property if wrapping

Type guard

static bool IsUsable(DeobfuscatingStream s) => s != null && !disposedSet.Contains(s); // track in a HashSet on Dispose

Try / catch

try { stream.Read(buf, 0, buf.Length); } catch (ObjectDisposedException) { stream = CreateFontStream(); stream.Read(buf, 0, buf.Length); }

Prevention

When it happens

Trigger: Calling Read/Write/Seek/SetLength/Flush or reading/writing the Position property after calling Dispose() (or Close()) on the DeobfuscatingStream instance.

Common situations: Caching the stream in a field and reusing it after a 'using' block ended; disposing in one code path while a background reader still holds a reference; double-dispose followed by read in exception cleanup paths.

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/ea2dbe0ab6021390. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationCore/MS/internal/IO/Packaging/DeobfuscatingStream.cs:269

        #region Private
        //------------------------------------------------------
        //
        //  Private Properties
        //
        //------------------------------------------------------
        //------------------------------------------------------
        //
        //  Private Methods
        //
        //------------------------------------------------------
        /// <summary>
        /// Call this before accepting any public API call (except some Stream calls that
        /// are allowed to respond even when Closed
        /// </summary>
        private void CheckDisposed()
        {
            if (_obfuscatedStream == null)
                throw new ObjectDisposedException(null, SR.Media_StreamClosed);
        }

        /// <summary>
        /// Apply de-obfuscation as necessary (the first 32 bytes of the underlying stream are obfuscated
        /// </summary>
        private void Deobfuscate(byte[] buffer, int offset, int count, long readPosition)
        {
            // only the first 32 bytes of the underlying stream are obfuscated
            //   if the read position is beyond offset 32, there is no need to do de-obfuscation
            if (readPosition >= ObfuscatedLength || count <= 0)
                return;

            // Find out how many bytes in the buffer are needed to be XORed
            // Note on casting:
            //      count can be safely cast to long since it is int
            //      We don't need to check for overflow of (readPosition + (long) count) since count is int and readPosition is less than
            //          ObfuscatedLength
            //      The result of (Math.Min(ObfuscatedLength, readPosition + (long) count) - readPosition) can be safely cast to int

View on GitHub (pinned to 81131a70a4)