Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
When you open a Package with <System.IO.FileAccess.ReadWrite?displayProperty=nameWithType> and then open a compressed part for reading that hasn't been modified in the current session, PackagePart.GetStream now returns a forward-only stream instead of a seekable MemoryStream.
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.
using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite);
PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative));
// 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.
using Package package = Package.Open("file.docx", FileMode.Open, FileAccess.ReadWrite);
PackagePart part = package.GetPart(new Uri("/word/document.xml", UriKind.Relative));
// 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 (
CompressionOptionother thanNotCompressed). - The part wasn't written or modified earlier in the same session.
- The consumer unconditionally 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.
- Accessing
Stream.Length. - Forward-only consumers (the common case, such as
XmlReader,XDocument.Load, the Open XML SDK, andCopyTo). - 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.
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.
Recommended action
If your code requires a seekable stream from a compressed part of a ReadWrite package, copy it into a MemoryStream manually:
using Stream partStream = part.GetStream(FileMode.Open, FileAccess.Read);
using MemoryStream seekable = new();
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.