From e817f3bd1f21fd7e35909e7e57dae0b50a42bc2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:47:38 +0000 Subject: [PATCH 1/5] Initial plan From 20b733b5264bd45c87c9cac5a130119fd07f4db0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 4 Aug 2026 15:49:52 +0000 Subject: [PATCH 2/5] Add breaking change doc for PackagePart.GetStream non-seekable behavior Co-authored-by: gewarren <24882762+gewarren@users.noreply.github.com> --- docs/core/compatibility/11.md | 1 + .../11/packagepart-getstream-non-seekable.md | 91 +++++++++++++++++++ docs/core/compatibility/toc.yml | 2 + 3 files changed, 94 insertions(+) create mode 100644 docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md diff --git a/docs/core/compatibility/11.md b/docs/core/compatibility/11.md index 521cb77a3ea0e..258a8fb1caf94 100644 --- a/docs/core/compatibility/11.md +++ b/docs/core/compatibility/11.md @@ -29,6 +29,7 @@ See [Breaking changes in ASP.NET Core 11](/aspnet/core/breaking-changes/11/overv | [Environment.TickCount made consistent with Windows timeout behavior](core-libraries/11/environment-tickcount-windows-behavior.md) | Behavioral change | | [NamedPipeServerStream with PipeOptions.CurrentUserOnly tightens Unix socket file permissions](core-libraries/11/namedpipeserverstream-unix-permissions.md) | Behavioral change | | [Nullable.GetUnderlyingType throws for custom Type subclasses](core-libraries/11/nullable-getunderlyingtype-throws.md) | Behavioral change | +| [PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages](core-libraries/11/packagepart-getstream-non-seekable.md) | Behavioral change | | [API obsoletions with non-default diagnostic IDs (.NET 11)](core-libraries/11/obsolete-apis.md) | Source incompatible | | [SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix](core-libraries/11/safefilehandle-isasync-unix.md) | Behavioral change | | [TAR-reading APIs verify header checksums when reading](core-libraries/11/tar-checksum-validation.md) | Behavioral change | diff --git a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md new file mode 100644 index 0000000000000..cbf30cf5de7b0 --- /dev/null +++ b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md @@ -0,0 +1,91 @@ +--- +title: "Breaking change: PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages" +description: "Learn about the breaking change in .NET 11 where PackagePart.GetStream() returns a forward-only stream instead of a seekable MemoryStream for compressed parts of a ReadWrite package." +ms.date: 08/04/2026 +ai-usage: ai-assisted +--- +# PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages + +When you open a with `FileAccess.ReadWrite` and then open a compressed part for reading that hasn't been modified in the current session, now returns a forward-only stream instead of a seekable . + +## Version introduced + +.NET 11 Preview 7 + +## Previous behavior + +Previously, when you opened a `Package` with `FileAccess.ReadWrite` (which maps internally to `ZipArchiveMode.Update`), opening a compressed part for reading returned a seekable `MemoryStream` that contained the fully decompressed entry content. + +```csharp +using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite); +PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative)); + +// Previously: returned a seekable MemoryStream. +using Stream stream = part.GetStream(FileMode.Open, FileAccess.Read); +Console.WriteLine(stream.CanSeek); // true +stream.Seek(0, SeekOrigin.Begin); // succeeded +Console.WriteLine(stream.Position); // 0 +``` + +## New behavior + +Starting in .NET 11, the same call returns a forward-only (non-seekable) stream for compressed parts that haven't been modified in the current session. + +```csharp +using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite); +PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative)); + +// Now: returns a forward-only (non-seekable) stream. +using Stream stream = part.GetStream(FileMode.Open, FileAccess.Read); +Console.WriteLine(stream.CanSeek); // false +stream.Seek(0, SeekOrigin.Begin); // throws NotSupportedException +stream.Position = 0; // throws NotSupportedException +Console.WriteLine(stream.Length); // still works (reported from entry metadata) +``` + +All the following conditions must be true simultaneously for you to observe this change: + +- The package is opened with `FileAccess.ReadWrite` (`Package.Open(..., FileAccess.ReadWrite)`). +- The part is opened for reading only (`GetStream(FileMode.Open, FileAccess.Read)`). +- The part is compressed (`CompressionOption` other than `NotCompressed`). +- The part wasn't written or modified earlier in the same session. +- The consumer seeks the stream or reads `Position`. + +The following scenarios aren't affected: + +- Read-only packages (`FileAccess.Read`): Their compressed part streams were already forward-only. +- Uncompressed (`Stored`) parts: They remain seekable. +- Parts modified earlier in the current session: They're served from an in-memory snapshot, so they remain seekable. +- `Stream.Length`: It still works for all read streams. +- Forward-only consumers (the common case, such as `XmlReader`, `XDocument.Load`, the Open XML SDK, and `CopyTo`): They're unaffected. +- Target frameworks earlier than .NET 11: The optimization is gated behind `NET11_0_OR_GREATER`. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change). + +## Reason for change + +Previously, opening a compressed part for reading from a `ReadWrite` package always decompressed the entire entry into a `MemoryStream` before it returned the stream. This approach produced a seekable stream, but it imposed unnecessary memory allocations and CPU overhead for the common case where callers only read the stream sequentially (for example, `XmlReader` or the Open XML SDK). The new behavior streams directly from the underlying ZIP archive entry for forward-only reads, which avoids the up-front decompression. + +For more information, see [dotnet/runtime#129698](https://github.com/dotnet/runtime/pull/129698). + +## Recommended action + +If your code requires a seekable stream from a compressed part of a `ReadWrite` package, copy it into a `MemoryStream` manually: + +```csharp +using Stream partStream = part.GetStream(FileMode.Open, FileAccess.Read); +using var seekable = new MemoryStream(); +partStream.CopyTo(seekable); +seekable.Position = 0; +// Use 'seekable'. It's fully buffered and seekable. +``` + +Alternatively, if your use case doesn't require `ReadWrite` access to the package, open it with `FileAccess.Read` instead. Read-only packages already returned forward-only streams before this change, so this mode avoids any surprise for seek-sensitive code. + +## Affected APIs + +- +- +- diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index aee280b97e29f..e9e2a88006b41 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -24,6 +24,8 @@ items: href: core-libraries/11/namedpipeserverstream-unix-permissions.md - name: Nullable.GetUnderlyingType throws for custom Type subclasses href: core-libraries/11/nullable-getunderlyingtype-throws.md + - name: PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages + href: core-libraries/11/packagepart-getstream-non-seekable.md - name: API obsoletions with non-default diagnostic IDs href: core-libraries/11/obsolete-apis.md - name: SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix From c0b9c04f9d48057e3538c88e60e380f2970b4445 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:49:31 -0700 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../11/packagepart-getstream-non-seekable.md | 8 ++++---- docs/core/compatibility/toc.yml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md index cbf30cf5de7b0..73986ec2ba614 100644 --- a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md +++ b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md @@ -6,7 +6,7 @@ ai-usage: ai-assisted --- # PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages -When you open a with `FileAccess.ReadWrite` and then open a compressed part for reading that hasn't been modified in the current session, now returns a forward-only stream instead of a seekable . +When you open a with and then open a compressed part for reading that hasn't been modified in the current session, now returns a forward-only stream instead of a seekable . ## Version introduced @@ -20,7 +20,7 @@ Previously, when you opened a `Package` with `FileAccess.ReadWrite` (which maps using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite); PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative)); -// Previously: returned a seekable MemoryStream. +// Returned a seekable MemoryStream. using Stream stream = part.GetStream(FileMode.Open, FileAccess.Read); Console.WriteLine(stream.CanSeek); // true stream.Seek(0, SeekOrigin.Begin); // succeeded @@ -35,7 +35,7 @@ Starting in .NET 11, the same call returns a forward-only (non-seekable) stream using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite); PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative)); -// Now: returns a forward-only (non-seekable) stream. +// Returns a forward-only (non-seekable) stream. using Stream stream = part.GetStream(FileMode.Open, FileAccess.Read); Console.WriteLine(stream.CanSeek); // false stream.Seek(0, SeekOrigin.Begin); // throws NotSupportedException @@ -57,7 +57,7 @@ The following scenarios aren't affected: - Uncompressed (`Stored`) parts: They remain seekable. - Parts modified earlier in the current session: They're served from an in-memory snapshot, so they remain seekable. - `Stream.Length`: It still works for all read streams. -- Forward-only consumers (the common case, such as `XmlReader`, `XDocument.Load`, the Open XML SDK, and `CopyTo`): They're unaffected. +- Forward-only consumers (the common case, such as `XmlReader`, `XDocument.Load`, the Open XML SDK, and `CopyTo`). - Target frameworks earlier than .NET 11: The optimization is gated behind `NET11_0_OR_GREATER`. ## Type of breaking change diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index e9e2a88006b41..d1c6ee2d67e8c 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -24,11 +24,11 @@ items: href: core-libraries/11/namedpipeserverstream-unix-permissions.md - name: Nullable.GetUnderlyingType throws for custom Type subclasses href: core-libraries/11/nullable-getunderlyingtype-throws.md - - name: PackagePart.GetStream() returns a non-seekable stream for compressed parts in ReadWrite packages + - name: PackagePart.GetStream() returns non-seekable stream for compressed parts href: core-libraries/11/packagepart-getstream-non-seekable.md - name: API obsoletions with non-default diagnostic IDs href: core-libraries/11/obsolete-apis.md - - name: SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state on Unix + - name: SafeFileHandle.IsAsync and FileStream.IsAsync accurately reflect non-blocking state href: core-libraries/11/safefilehandle-isasync-unix.md - name: TAR-reading APIs verify header checksums when reading href: core-libraries/11/tar-checksum-validation.md From 876cb6f6dc162b26e10e1fca336a592289b7eea3 Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Tue, 4 Aug 2026 14:03:08 -0700 Subject: [PATCH 4/5] Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../core-libraries/11/packagepart-getstream-non-seekable.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md index 73986ec2ba614..01d9d3e8440c0 100644 --- a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md +++ b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md @@ -56,7 +56,7 @@ The following scenarios aren't affected: - Read-only packages (`FileAccess.Read`): Their compressed part streams were already forward-only. - Uncompressed (`Stored`) parts: They remain seekable. - Parts modified earlier in the current session: They're served from an in-memory snapshot, so they remain seekable. -- `Stream.Length`: It still works for all read streams. +- Accessing `Stream.Length`. - Forward-only consumers (the common case, such as `XmlReader`, `XDocument.Load`, the Open XML SDK, and `CopyTo`). - Target frameworks earlier than .NET 11: The optimization is gated behind `NET11_0_OR_GREATER`. @@ -76,7 +76,7 @@ If your code requires a seekable stream from a compressed part of a `ReadWrite` ```csharp using Stream partStream = part.GetStream(FileMode.Open, FileAccess.Read); -using var seekable = new MemoryStream(); +using MemoryStream seekable = new(); partStream.CopyTo(seekable); seekable.Position = 0; // Use 'seekable'. It's fully buffered and seekable. From b12eadaef79b297204ddd608292fe3b4be721e9c Mon Sep 17 00:00:00 2001 From: Genevieve Warren <24882762+gewarren@users.noreply.github.com> Date: Wed, 5 Aug 2026 08:59:15 -0700 Subject: [PATCH 5/5] Apply suggestion from @gewarren --- .../core-libraries/11/packagepart-getstream-non-seekable.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md index 01d9d3e8440c0..791f686556568 100644 --- a/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md +++ b/docs/core/compatibility/core-libraries/11/packagepart-getstream-non-seekable.md @@ -49,7 +49,7 @@ All the following conditions must be true simultaneously for you to observe this - The part is opened for reading only (`GetStream(FileMode.Open, FileAccess.Read)`). - The part is compressed (`CompressionOption` other than `NotCompressed`). - The part wasn't written or modified earlier in the same session. -- The consumer seeks the stream or reads `Position`. +- The consumer unconditionally seeks the stream or reads `Position`. The following scenarios aren't affected: