Skip to content
13 changes: 10 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,17 @@ SharpCompress supports multiple archive and compression formats:
- See [docs/FORMATS.md](docs/FORMATS.md) for complete format support matrix

### Stream Handling Rules
- **Disposal**: As of version 0.21, SharpCompress closes wrapped streams by default
- Use `ReaderOptions` or `WriterOptions` with `LeaveStreamOpen = true` to control stream disposal
- **Disposal semantics**: The default `ReaderOptions.LeaveStreamOpen` value is `false`, but effective stream ownership depends on which API overload you call
- File-based overloads (e.g., `OpenArchive(string filePath)`) open the file internally and own that stream, so it is closed by default with the archive/reader
- Do **not** rely on a specific `ReaderOptions` preset being used internally; some implementations may use `ReaderOptions.ForFilePath`, while others may use default `ReaderOptions` with the same ownership semantics
- Several high-level overloads that accept a caller-provided `Stream` use external-stream semantics by default (for example, `ReaderFactory.OpenReader(Stream)` / `ArchiveFactory.OpenArchive(Stream)`), so the caller's stream is typically left open unless you opt into different ownership behavior
- Do **not** assume every stream-based overload behaves identically; some APIs require you to pass stream ownership options explicitly
- **For caller-provided streams**: When the overload accepts `ReaderOptions`, pass `ReaderOptions.ForExternalStream` or use `ReaderOptions` with `LeaveStreamOpen = true` whenever the caller must retain ownership of the stream
- Example: `var options = new ReaderOptions { LeaveStreamOpen = true };`
- Or: `var options = ReaderOptions.ForExternalStream;`
- **For file paths**: SharpCompress manages the stream lifecycle for the internally opened file stream; no manual disposal is needed beyond the archive/reader itself
- Use `NonDisposingStream` wrapper when working with compression streams directly to prevent disposal
- Always dispose of readers, writers, and archives in `using` blocks
- Always dispose of readers, writers, and archives in `using` / `await using` blocks
- For forward-only operations, use Reader/Writer APIs; for random access, use Archive APIs

### Async/Await Patterns
Expand Down
4 changes: 2 additions & 2 deletions src/SharpCompress/Archives/Zip/ZipArchive.Factory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public static IWritableArchive<ZipWriterOptions> OpenArchive(
new SourceStream(
fileInfo,
i => ZipArchiveVolumeFactory.GetFilePart(i, fileInfo),
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
readerOptions ?? ReaderOptions.ForFilePath
)
Comment on lines 42 to 45
Comment on lines 41 to 45
);
}
Expand All @@ -57,7 +57,7 @@ public static IWritableArchive<ZipWriterOptions> OpenArchive(
new SourceStream(
files[0],
i => i < files.Count ? files[i] : null,
readerOptions ?? new ReaderOptions() { LeaveStreamOpen = false }
readerOptions ?? ReaderOptions.ForFilePath
Comment thread
puk06 marked this conversation as resolved.
)
Comment on lines 58 to 61
Comment on lines 57 to 61
);
}
Expand Down
31 changes: 29 additions & 2 deletions src/SharpCompress/Readers/ReaderOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,36 @@ namespace SharpCompress.Readers;
public sealed record ReaderOptions : IReaderOptions
{
/// <summary>
/// SharpCompress will keep the supplied streams open. Default is true.
/// Whether SharpCompress leaves the supplied streams open when the reader/archive is disposed.
/// As of v0.21, the library is documented to close streams by default; this option now defaults to false.
/// Set to true when passing caller-owned streams that should not be disposed.
/// </summary>
Comment thread
adamhathcock marked this conversation as resolved.
Comment on lines 26 to 30
Comment on lines +27 to 30
public bool LeaveStreamOpen { get; init; } = true;
/// <remarks>
/// <para>
/// <b>Default behavior (LeaveStreamOpen = false):</b>
/// When you open an archive from a file path (e.g., <c>GZipArchive.OpenArchive(filePath)</c>),
/// SharpCompress manages the stream lifetime and closes it on Dispose.
/// </para>
Comment on lines +33 to +36
/// <para>
/// <b>Caller-provided streams (LeaveStreamOpen = true):</b>
/// When you pass a stream you created (FileStream, MemoryStream, NetworkStream, etc.),
/// set LeaveStreamOpen = true to prevent SharpCompress from disposing it.
/// Use <see cref="ForExternalStream"/> preset for convenience.
Comment thread
adamhathcock marked this conversation as resolved.
Comment on lines +33 to +41
/// </para>
Comment thread
adamhathcock marked this conversation as resolved.
/// <para>
/// <b>Example:</b>
/// <code>
/// // File-based: stream managed by library
/// using var archive = GZipArchive.OpenArchive(filePath); // LeaveStreamOpen = false
///
/// // Caller-provided stream: caller manages lifetime
/// using var stream = File.OpenRead(filePath);
/// var options = new ReaderOptions { LeaveStreamOpen = true };
/// using var archive = GZipArchive.OpenArchive(stream, options);
/// </code>
/// </para>
/// </remarks>
public bool LeaveStreamOpen { get; init; } = false;
Comment thread
adamhathcock marked this conversation as resolved.
Comment thread
puk06 marked this conversation as resolved.

/// <summary>
/// Encoding to use for archive entry names.
Expand Down