13 KiB
description, applyTo
| description | applyTo |
|---|---|
| Guidelines for building SharpCompress - A C# compression library | **/*.cs |
SharpCompress Development
About SharpCompress
SharpCompress is a pure C# compression library supporting multiple archive formats (Zip, Tar, GZip, BZip2, 7Zip, Rar, LZip, XZ, ZStandard, Arc, Arj, Ace, LZW). The project currently targets .NET Framework 4.8, .NET Standard 2.0/2.1, .NET 6.0, .NET 8.0, and .NET 10.0. The library provides both seekable Archive APIs and forward-only Reader/Writer APIs for streaming scenarios.
C# Instructions
- Use language features supported by the current project toolchain (
LangVersion=latest) and existing codebase patterns. - Add comments for non-obvious logic and important design decisions; avoid redundant comments.
- Follow the existing code style and patterns in the codebase.
General Instructions
- Do not commit or stage changes unless the user explicitly asks for it.
- Make only high confidence suggestions when reviewing code changes.
- Write code with good maintainability practices, including comments on why certain design decisions were made.
- Handle edge cases and write clear exception handling.
- For libraries or external dependencies, mention their usage and purpose in comments.
- Preserve backward compatibility when making changes to public APIs.
Workspace Hygiene
- Do not edit generated or machine-local files unless required for the task (for example:
bin/,obj/,*.csproj.user). - Avoid broad formatting-only diffs in unrelated files.
Naming Conventions
- Follow PascalCase for component names, method names, and public members.
- Use camelCase for private fields and local variables.
- Prefix interface names with "I" (e.g., IUserService).
Code Formatting
Copilot agents: You MUST run the format task after making code changes to ensure consistency.
- Use CSharpier for code formatting to ensure consistent style across the project
- CSharpier is configured as a local tool in
.config/dotnet-tools.json
Commands
-
Restore tools (first time only):
dotnet tool restore -
Check if files are formatted correctly (doesn't modify files):
dotnet csharpier check .- Exit code 0: All files are properly formatted
- Exit code 1: Some files need formatting (will show which files and differences)
-
Format files (modifies files):
dotnet csharpier format .- Formats all files in the project to match CSharpier style
- Run from project root directory
-
Configure your IDE to format on save using CSharpier for the best experience
Additional Notes
- The project also uses
.editorconfigfor editor settings (indentation, encoding, etc.) - Let CSharpier handle code style while
.editorconfighandles editor behavior - Always run
dotnet csharpier check .before committing to verify formatting
Project Setup and Structure
- The project targets multiple frameworks: .NET Framework 4.8, .NET Standard 2.0/2.1, .NET 6.0, .NET 8.0, and .NET 10.0
- Main library is in
src/SharpCompress/ - Tests are in
tests/SharpCompress.Test/ - Performance tests are in
tests/SharpCompress.Performance/ - Test archives are in
tests/TestArchives/ - Build project is in
build/ - Use
dotnet buildto build the solution - Use
dotnet testto run tests - Solution file:
SharpCompress.slnx
Directory Structure
src/SharpCompress/
├── Archives/ # IArchive implementations (Zip, Tar, Rar, 7Zip, GZip)
├── Readers/ # IReader implementations (forward-only)
├── Writers/ # IWriter implementations (forward-only)
├── Compressors/ # Low-level compression streams (BZip2, Deflate, LZMA, etc.)
├── Factories/ # Format detection and factory pattern
├── Common/ # Shared types (ArchiveType, Entry, Options)
├── Crypto/ # Encryption implementations
└── IO/ # Stream utilities and wrappers
tests/SharpCompress.Test/
├── Zip/, Tar/, Rar/, SevenZip/, GZip/, BZip2/ # Format-specific tests
├── TestBase.cs # Base test class with helper methods
tests/
├── SharpCompress.Test/ # Unit/integration tests
├── SharpCompress.Performance/ # Benchmark tests
└── TestArchives/ # Test data archives
Factory Pattern
Factory implementations can implement one or more interfaces (IArchiveFactory, IReaderFactory, IWriterFactory) depending on format capabilities:
ArchiveFactory.OpenArchive()- Opens archive API objects from seekable streams/filesArchiveFactory.OpenAsyncArchive()- Opens async archive API objects for async archive use casesReaderFactory.OpenReader()- Auto-detects and opens forward-only readersReaderFactory.OpenAsyncReader()- Auto-detects and opens forward-only async readersWriterFactory.OpenWriter()- Creates a writer for a specifiedArchiveTypeWriterFactory.OpenAsyncWriter()- Creates an async writer for async write scenarios- Factories located in:
src/SharpCompress/Factories/
Nullable Reference Types
- Declare variables non-nullable, and check for
nullat entry points. - Always use
is nulloris not nullinstead of== nullor!= null. - Trust the C# null annotations and don't add null checks when the type system says a value cannot be null.
SharpCompress-Specific Guidelines
Supported Formats
SharpCompress supports multiple archive and compression formats:
- Archive Formats: Zip, Tar, 7Zip, Rar (read-only), Ace (read-only), Arc (read-only), Arj (read-only), LZW (read-only)
- Compression: DEFLATE, BZip2, LZMA/LZMA2, PPMd, ZStandard, LZip, XZ (decompress only), Deflate64 (decompress only), legacy Zip/Arc/Arj/Ace methods (read-only as applicable)
- Combined Formats: Tar.GZip, Tar.BZip2, Tar.LZip, Tar.XZ (decompress only), Tar.ZStandard (decompress only), Tar.LZW (decompress only)
- ZIP ZStandard: ZIP supports ZStandard reading and writing; Tar.ZStandard is decompress-only.
- See docs/FORMATS.md for complete format support matrix
Stream Handling Rules
- Disposal semantics: The default
ReaderOptions.LeaveStreamOpenvalue isfalse, 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
ReaderOptionspreset being used internally; some implementations may useReaderOptions.ForFilePath, while others may use defaultReaderOptionswith the same ownership semantics
- Do not rely on a specific
- Several high-level overloads that accept a caller-provided
Streamuse 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
- File-based overloads (e.g.,
- For caller-provided streams: When the overload accepts
ReaderOptions, passReaderOptions.ForExternalStreamor useReaderOptionswithLeaveStreamOpen = truewhenever the caller must retain ownership of the stream- Example:
var options = new ReaderOptions { LeaveStreamOpen = true }; - Or:
var options = ReaderOptions.ForExternalStream;
- Example:
- 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
NonDisposingStreamwrapper when working with compression streams directly to prevent disposal - Always dispose of readers, writers, and archives in
using/await usingblocks - For forward-only operations, use Reader/Writer APIs; for random access, use Archive APIs
Async/Await Patterns
- All I/O operations support async/await with
CancellationToken - Async methods follow the naming convention:
MethodNameAsync - For async archive scenarios, prefer
ArchiveFactory.OpenAsyncArchive(...)over syncOpenArchive(...). - For async forward-only read scenarios, prefer
ReaderFactory.OpenAsyncReader(...)over syncOpenReader(...). - For async write scenarios, prefer
WriterFactory.OpenAsyncWriter(...)over syncOpenWriter(...). - Key async methods:
WriteEntryToAsync- Extract entry asynchronouslyWriteAllToDirectoryAsync- Extract all entries asynchronouslyWriteAsync- Write entry asynchronouslyWriteAllAsync- Write directory asynchronouslyOpenEntryStreamAsync- Open entry stream asynchronously
- Always provide
CancellationTokenparameter in async methods
Archive APIs vs Reader/Writer APIs
- Archive API: Use for random access with seekable streams (e.g.,
ZipArchive,TarArchive) - Reader API: Use for forward-only reading on non-seekable streams (e.g.,
ZipReader,TarReader) - Writer API: Use for forward-only writing on streams (e.g.,
ZipWriter,TarWriter) - 7Zip only supports Archive API due to format limitations
Tar-Specific Considerations
- Tar format requires file size in the header
- If no size is specified to TarWriter and the stream is not seekable, an exception will be thrown
- Tar combined with compression is supported for reading with GZip, BZip2, LZip, XZ, ZStandard, and LZW
- Tar writing supports uncompressed Tar, GZip, BZip2, and LZip wrappers
Zip-Specific Considerations
- Supports Zip64 for large files (seekable streams only)
- Supports PKWare and WinZip AES encryption
- Multiple compression methods: None, Shrink, Reduce, Implode, DEFLATE, Deflate64, BZip2, LZMA, PPMd
- Encrypted LZMA is not supported
Performance Considerations
- For large files, use Reader/Writer APIs with non-seekable streams to avoid loading entire file in memory
- Leverage async I/O for better scalability
- Consider compression level trade-offs (speed vs. size)
- Use appropriate buffer sizes for stream operations
Testing
- Always include test cases for critical paths of the application.
- Test with multiple archive formats when making changes to core functionality.
- Include tests for both Archive and Reader/Writer APIs when applicable.
- Test async operations with cancellation tokens.
- Do not emit "Act", "Arrange" or "Assert" comments.
- Copy existing style in nearby files for test method names and capitalization.
- Use test archives from
tests/TestArchivesdirectory for consistency. - Test stream disposal and
LeaveStreamOpenbehavior. - Test edge cases: empty archives, large files, corrupted archives, encrypted archives.
Validation Expectations
- Run targeted tests for the changed area first.
- On non-Windows machines, avoid net48 test runs unless Mono is installed; use framework-specific validation such as
--framework net10.0instead. - Run
dotnet csharpier format .after code edits. - Run
dotnet csharpier check .before handing off changes.
Test Organization
- Base class:
TestBase- ProvidesTEST_ARCHIVES_PATH,SCRATCH_FILES_PATH, temp directory management - Framework: xUnit with AwesomeAssertions
- Test archives:
tests/TestArchives/- Use existing archives, don't create new ones unnecessarily - Match naming style of nearby test files
Public API Change Checklist
- Preserve existing public method signatures and behavior when possible.
- If a breaking change is unavoidable, document it and provide a migration path.
- Add or update tests that cover backward compatibility expectations.
- Avoid exposing public
initsetters, positional records,requiredmembers, or other metadata that forces consumers onto newer C# language versions; validate older-consumer compatibility with tests when changing exported APIs.
Public API Documentation Checklist
- When adding, removing, renaming, or changing public APIs, update the public docs in the same change.
- Check
docs/API.mdfor new factory methods, options, interfaces, extension methods, enums, archive/reader/writer APIs, compression provider APIs, and examples. - Check
docs/USAGE.mdwhen the API change affects recommended usage patterns or requires a new example. - Check
docs/FORMATS.mdwhen the API change affects supported archive formats, compression methods, reader/archive/writer availability, or detection behavior. - Check
README.mdwhen the change affects the top-level support summary, target frameworks, major capabilities, or user-facing feature list. - For public API changes, verify examples compile conceptually against the actual public signatures and avoid documenting internal-only types.
Stream Ownership and Position Checklist
- Verify
LeaveStreamOpenbehavior for externally owned streams. - Validate behavior for both seekable and non-seekable streams.
- Ensure stream position assumptions are explicit and tested.
Common Pitfalls
- Don't mix Archive and Reader APIs - Archive needs seekable stream, Reader doesn't
- Don't mix sync and async open paths - For async workflows use
OpenAsyncArchive/OpenAsyncReader/OpenAsyncWriter, notOpenArchive/OpenReader/OpenWriter - Solid archives (Rar, 7Zip) - Use
ExtractAllEntries()for best performance, not individual entry extraction - Stream disposal - Always set
LeaveStreamOpenexplicitly when needed (default is to close) - Tar + non-seekable stream - Must provide file size or it will throw
- Format detection - Use
ReaderFactory.OpenReader()/ReaderFactory.OpenAsyncReader()for auto-detection, test with actual archive files