PackagePart.GetStream() は、ReadWrite パッケージ内の圧縮された部分に対してシークできないストリームを返します

<System.IO.FileAccess.ReadWrite?displayProperty=nameWithType> でPackageを開き現在のセッションで変更されていない読み取り用の圧縮部分を開くと PackagePart.GetStream、シーク可能なMemoryStreamではなく順方向専用ストリームが返されるようになりました。

導入されたバージョン

.NET 11 Preview 7

以前の動作

以前は、(内部的にZipArchiveMode.Updateにマップされる) FileAccess.ReadWriteを使用してPackageを開くと、読み取り用に圧縮された部分を開くと、完全に展開されたエントリコンテンツを含むシーク可能なMemoryStreamが返されました。

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

新しい動作

.NET 11 以降では、同じ呼び出しは、現在のセッションで変更されていない圧縮された部分の順方向専用 (シーク不可) ストリームを返します。

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)

この変更を確認するには、次のすべての条件が同時に満たされている必要があります。

  • パッケージは FileAccess.ReadWrite (Package.Open(..., FileAccess.ReadWrite)) で開かれます。
  • パーツは読み取り専用で開かれます (GetStream(FileMode.Open, FileAccess.Read))。
  • パーツは圧縮されます (NotCompressed以外のCompressionOption)。
  • パートは、同じセッションで以前に書き込まれたり変更されたりしていません。
  • コンシューマーは無条件にストリームをシークするか、 Positionを読み取ります。

次のシナリオは影響を受けません。

  • 読み取り専用パッケージ (FileAccess.Read): 圧縮されたパーツ ストリームは既に転送専用でした。
  • 非圧縮 (Stored) 部分: それらはシーク可能なままです。
  • 現在のセッションで以前に変更されたパーツ: これらはメモリ内スナップショットから提供されるため、シーク可能なままです。
  • Stream.Lengthへのアクセス。
  • 前方専用コンシューマー ( XmlReaderXDocument.Load、Open XML SDK、 CopyTo など、
  • .NET 11 より前のターゲット フレームワーク: 最適化はNET11_0_OR_GREATERの背後で制御されます。

破壊的変更の種類

この変更は 動作の変更です。

変更理由

以前は、 ReadWrite パッケージからの読み取り用に圧縮された部分を開くと、ストリームが返される前に、常にエントリ全体が MemoryStream に展開されました。 この方法ではシーク可能なストリームが生成されましたが、呼び出し元がストリームを順番に読み取るだけの一般的なケース (たとえば、 XmlReader や Open XML SDK) に不要なメモリ割り当てと CPU オーバーヘッドが課されました。 新しい動作は、転送専用の読み取りのために、基になる ZIP アーカイブ エントリから直接ストリーミングされます。そのため、事前展開を回避できます。

詳細については、「 dotnet/runtime#129698」を参照してください。

コードで、 ReadWrite パッケージの圧縮された部分からのシーク可能なストリームが必要な場合は、手動で MemoryStream にコピーします。

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.

または、ユース ケースでパッケージへの ReadWrite アクセスが必要ない場合は、代わりに FileAccess.Read で開きます。 読み取り専用パッケージは、この変更の前に既に前方専用ストリームを返しているため、このモードではシークに依存するコードの驚きを回避できます。

影響を受ける API