PackagePart.GetStream() restituisce un flusso non ricercabile per parti compresse nei pacchetti ReadWrite

Quando si apre un oggetto Package con System.IO.FileAccess.ReadWrite?displayProperty=nameWithType> e quindi si apre una parte compressa per la lettura che non è stata modificata nella sessione corrente, PackagePart.GetStream ora restituisce un flusso forward-only anziché un oggetto seekableMemoryStream<.

Versione introdotta

.NET 11 Preview 7

Comportamento precedente

In precedenza, quando si apre un Package oggetto con FileAccess.ReadWrite (che esegue il mapping internamente a ZipArchiveMode.Update), l'apertura di una parte compressa per la lettura ha restituito un elemento ricercabile MemoryStream che conteneva il contenuto della voce completamente decompresso.

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

Nuovo comportamento

A partire da .NET 11, la stessa chiamata restituisce un flusso forward-only (non ricercabile) per parti compresse che non sono state modificate nella sessione corrente.

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)

Per osservare questa modifica, è necessario che tutte le condizioni seguenti siano vere contemporaneamente:

  • Il pacchetto viene aperto con FileAccess.ReadWrite (Package.Open(..., FileAccess.ReadWrite)).
  • La parte viene aperta solo per la lettura (GetStream(FileMode.Open, FileAccess.Read)).
  • La parte è compressa (CompressionOption diversa da NotCompressed).
  • La parte non è stata scritta o modificata in precedenza nella stessa sessione.
  • Il consumer cerca in modo incondizionato il flusso o legge Position.

Gli scenari seguenti non sono interessati:

  • Pacchetti di sola lettura (FileAccess.Read): i flussi di parti compressi erano già forward-only.
  • Parti non compresse (Stored): rimangono ricercabili.
  • Parti modificate in precedenza nella sessione corrente: vengono gestite da uno snapshot in memoria, in modo che rimangano ricercabili.
  • Accesso a Stream.Length.
  • Consumer forward-only (caso comune, ad esempio XmlReader, , XDocument.Load, Open XML SDK e CopyTo).
  • Framework di destinazione precedenti a .NET 11: l'ottimizzazione viene controllata dietro NET11_0_OR_GREATER.

Tipo di cambiamento che interrompe la compatibilità

Questa modifica è una modifica funzionale.

Motivo della modifica

In precedenza, l'apertura di una parte compressa per la lettura da un ReadWrite pacchetto decomprimeva sempre l'intera voce in un MemoryStream oggetto prima di restituire il flusso. Questo approccio ha prodotto un flusso ricercabile, ma ha imposto allocazioni di memoria non necessarie e sovraccarico della CPU per il caso comune in cui i chiamanti leggono il flusso in sequenza (ad esempio, XmlReader o Open XML SDK). Il nuovo comportamento viene trasmesso direttamente dalla voce di archivio ZIP sottostante per le letture forward-only, evitando così la decompressione iniziale.

Per altre informazioni, vedere dotnet/runtime#129698.

Se il codice richiede un flusso ricercabile da una parte compressa di un ReadWrite pacchetto, copiarlo manualmente: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.

In alternativa, se il caso d'uso non richiede ReadWrite l'accesso al pacchetto, aprirlo con FileAccess.Read . I pacchetti di sola lettura hanno già restituito flussi forward-only prima di questa modifica, quindi questa modalità evita eventuali sorprese per il codice sensibile alla ricerca.

Le API interessate