Funkcja PackagePart.GetStream() zwraca strumień niemożliwy do wyszukiwania dla skompresowanych części w pakietach ReadWrite

Po otwarciu obiektu Package z funkcją <System.IO.FileAccess.ReadWrite?displayProperty=nameWithType> , a następnie otwórz skompresowaną część do odczytu, która nie została zmodyfikowana w bieżącej sesji, PackagePart.GetStream teraz zwraca strumień tylko do przodu zamiast wyszukiwanego MemoryStream.

Wersja wprowadzona

.NET 11 (wersja zapoznawcza 7)

Poprzednie zachowanie

Wcześniej po otwarciu elementu Package z elementem FileAccess.ReadWrite (który jest mapowany wewnętrznie na ZipArchiveMode.Update), otwarcie skompresowanej części do odczytu zwróciło możliwość wyszukiwania MemoryStream , która zawierała w pełni zdekompresowaną zawartość wpisu.

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

Nowe zachowanie

Począwszy od .NET 11, to samo wywołanie zwraca strumień tylko do przodu (bez możliwości wyszukiwania) dla skompresowanych części, które nie zostały zmodyfikowane w bieżącej sesji.

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)

Aby zaobserwować tę zmianę, wszystkie następujące warunki muszą być spełnione jednocześnie:

  • Pakiet jest otwierany za pomocą FileAccess.ReadWrite elementu (Package.Open(..., FileAccess.ReadWrite)).
  • Część jest otwierana tylko do odczytu (GetStream(FileMode.Open, FileAccess.Read)).
  • Część jest kompresowana (CompressionOption inna niż NotCompressed).
  • Część nie została napisana ani zmodyfikowana wcześniej w tej samej sesji.
  • Konsument bezwarunkowo szuka strumienia lub odczytuje Position.

Nie ma to wpływu na następujące scenariusze:

  • Pakiety tylko do odczytu (FileAccess.Read): ich skompresowane strumienie części były już przekazywane tylko do przodu.
  • Nieskompresowane (Stored) części: Pozostają one możliwe do poszukiwania.
  • Części zmodyfikowane wcześniej w bieżącej sesji: są one obsługiwane z migawki w pamięci, więc pozostają one dostępne do wyszukiwania.
  • Uzyskiwanie Stream.Lengthdostępu do elementu .
  • Odbiorcy tylko do przekazywania (typowy przypadek, taki jak XmlReader, XDocument.Load, zestaw OPEN XML SDK i CopyTo).
  • Struktury docelowe wcześniejsze niż .NET 11: Optymalizacja jest bramowana za NET11_0_OR_GREATER.

Typ zmiany przełamującej

Ta zmiana jest zmianą behawioralną.

Przyczyna zmiany

Wcześniej otwarcie skompresowanej części do odczytu z ReadWrite pakietu zawsze dekompresował cały wpis do elementu przed zwróceniem MemoryStream strumienia. Takie podejście wywołało strumień możliwy do wyszukiwania, ale narzuciło niepotrzebne alokacje pamięci i obciążenie procesora CPU dla typowego przypadku, w którym osoby wywołujące odczytują strumień sekwencyjnie (na przykład XmlReader lub zestaw OPEN XML SDK). Nowe zachowanie przesyła strumienie bezpośrednio z bazowego wpisu archiwum ZIP do odczytu tylko do przodu, co pozwala uniknąć dekompresji z góry.

Aby uzyskać więcej informacji, zobacz dotnet/runtime#129698.

Jeśli kod wymaga możliwego do wyszukiwania strumienia ze skompresowanej części ReadWrite pakietu, skopiuj go ręcznie 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.

Alternatywnie, jeśli przypadek użycia nie wymaga ReadWrite dostępu do pakietu, otwórz go za pomocą FileAccess.Read polecenia . Pakiety tylko do odczytu zwróciły już strumienie tylko do przodu przed tą zmianą, więc ten tryb pozwala uniknąć niespodzianek dla kodu wrażliwego na wyszukiwanie.

Interfejsy API, których dotyczy problem