From c303856c5f8d8ccfcc1918fb70893526c33bb23a Mon Sep 17 00:00:00 2001 From: Victor Irzak Date: Mon, 27 Jul 2026 23:42:23 -0400 Subject: [PATCH 1/3] Generate the synchronous archive entry extensions from the async ones The five synchronous WriteTo/WriteToDirectory/WriteToFile overloads were maintained by hand alongside their async counterparts, which had drifted: the async ones report progress through ExtractionOptions.BufferSize while the sync ones did not, and each fix has had to be made twice. Zomp.SyncMethodGenerator produces the synchronous copy from the async source at compile time. The async methods are the only ones written out; the sync versions are emitted into the same partial class, keeping the public API identical - same names, same overloads, same signatures. The generator is a build-time only dependency (PrivateAssets="all"), so it adds nothing to the shipped package and no runtime reference. Documentation summaries lose the word "asynchronously" because a summary is now shared by both copies of the method. --- Directory.Packages.props | 1 + .../Archives/IArchiveEntryExtensions.cs | 79 ++----------------- src/SharpCompress/SharpCompress.csproj | 3 + src/SharpCompress/packages.lock.json | 48 +++++++++-- 4 files changed, 53 insertions(+), 78 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 7559f05e..ecf3d44b 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,5 +19,6 @@ Version="17.14.15" /> + diff --git a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs index 502a419e..6b6b96b7 100644 --- a/src/SharpCompress/Archives/IArchiveEntryExtensions.cs +++ b/src/SharpCompress/Archives/IArchiveEntryExtensions.cs @@ -7,7 +7,7 @@ using SharpCompress.IO; namespace SharpCompress.Archives; -public static class IArchiveEntryExtensions +public static partial class IArchiveEntryExtensions { /// The archive entry to extract. extension(IArchiveEntry archiveEntry) @@ -16,48 +16,9 @@ public static class IArchiveEntryExtensions /// Extract entry to the specified stream. /// /// The stream to write the entry content to. - /// Optional progress reporter for tracking extraction progress. - public void WriteTo(Stream streamToWriteTo, IProgress? progress = null) => - archiveEntry.WriteTo(streamToWriteTo, bufferSize: null, progress: progress); - - /// - /// Extract entry to the specified stream. - /// - /// The stream to write the entry content to. - /// Options for configuring extraction behavior. - /// Optional progress reporter for tracking extraction progress. - public void WriteTo( - Stream streamToWriteTo, - ExtractionOptions options, - IProgress? progress = null - ) => archiveEntry.WriteTo(streamToWriteTo, options.BufferSize, options, progress); - - private void WriteTo( - Stream streamToWriteTo, - int? bufferSize, - ExtractionOptions? options = null, - IProgress? progress = null - ) - { - if (archiveEntry.IsDirectory) - { - throw new ExtractionException("Entry is a file directory and cannot be extracted."); - } - - using var entryStream = archiveEntry.OpenEntryStream(); - var checkedStream = options is null - ? entryStream - : IEntryExtensions.WrapWithChecksumValidation(archiveEntry, entryStream, options); - var sourceStream = WrapWithProgress(checkedStream, archiveEntry, progress); - sourceStream.CopyTo(streamToWriteTo, bufferSize ?? Constants.BufferSize); - } - - /// - /// Extract entry to the specified stream asynchronously. - /// - /// The stream to write the entry content to. /// Cancellation token. /// Optional progress reporter for tracking extraction progress. + [Zomp.SyncMethodGenerator.CreateSyncVersion(PreserveProgress = true)] public async ValueTask WriteToAsync( Stream streamToWriteTo, IProgress? progress = null, @@ -73,12 +34,13 @@ public static class IArchiveEntryExtensions .ConfigureAwait(false); /// - /// Extract entry to the specified stream asynchronously. + /// Extract entry to the specified stream. /// /// The stream to write the entry content to. /// Options for configuring extraction behavior. /// Optional progress reporter for tracking extraction progress. /// Cancellation token. + [Zomp.SyncMethodGenerator.CreateSyncVersion(PreserveProgress = true)] public async ValueTask WriteToAsync( Stream streamToWriteTo, ExtractionOptions options, @@ -95,6 +57,7 @@ public static class IArchiveEntryExtensions ) .ConfigureAwait(false); + [Zomp.SyncMethodGenerator.CreateSyncVersion(PreserveProgress = true)] private async ValueTask WriteToAsync( Stream streamToWriteTo, int? bufferSize, @@ -167,19 +130,7 @@ public static class IArchiveEntryExtensions /// /// Extract to specific directory, retaining filename /// - public void WriteToDirectory( - string destinationDirectory, - ExtractionOptions? options = null - ) => - entry.WriteEntryToDirectory( - destinationDirectory, - options, - (path) => entry.WriteToFile(path, options) - ); - - /// - /// Extract to specific directory asynchronously, retaining filename - /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async ValueTask WriteToDirectoryAsync( string destinationDirectory, ExtractionOptions? options = null, @@ -198,23 +149,7 @@ public static class IArchiveEntryExtensions /// /// Extract to specific file /// - public void WriteToFile(string destinationFileName, ExtractionOptions? options = null) - { - options ??= new ExtractionOptions(); - entry.WriteEntryToFile( - destinationFileName, - options, - (x, fm) => - { - using var fs = File.Open(x, fm); - entry.WriteTo(fs, options?.BufferSize ?? Constants.BufferSize, options, null); - } - ); - } - - /// - /// Extract to specific file asynchronously - /// + [Zomp.SyncMethodGenerator.CreateSyncVersion] public async ValueTask WriteToFileAsync( string destinationFileName, ExtractionOptions? options = null, diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index 991eccb9..e4b75113 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -39,6 +39,9 @@ + + + diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 54571844..7f5613ad 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -53,6 +53,12 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", @@ -175,6 +181,12 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", @@ -289,6 +301,12 @@ "System.Runtime.CompilerServices.Unsafe": "6.0.0" } }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", @@ -321,9 +339,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.9, )", - "resolved": "10.0.9", - "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" + "requested": "[10.0.10, )", + "resolved": "10.0.10", + "contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -357,6 +375,12 @@ "resolved": "1.16.0", "contentHash": "3kdIIceBPumwjw279FuiVMfVENT2cGASXJgcigdySsbX2dJB8ofUgG6i47yqF/k1qu6fvNR3csrSekZPviR6kQ==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", @@ -414,6 +438,12 @@ "resolved": "1.16.0", "contentHash": "3kdIIceBPumwjw279FuiVMfVENT2cGASXJgcigdySsbX2dJB8ofUgG6i47yqF/k1qu6fvNR3csrSekZPviR6kQ==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", @@ -441,9 +471,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.28, )", - "resolved": "8.0.28", - "contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ==" + "requested": "[8.0.29, )", + "resolved": "8.0.29", + "contentHash": "HSBTfrkIZijz8z3ybLRKB7E8rHk4QQufFwpHa9fc5CMIgRhRzdn4mBGmlyXZqaueiMPtuJcnjresGvSTfaW8Mg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -477,6 +507,12 @@ "resolved": "1.16.0", "contentHash": "3kdIIceBPumwjw279FuiVMfVENT2cGASXJgcigdySsbX2dJB8ofUgG6i47yqF/k1qu6fvNR3csrSekZPviR6kQ==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", From e783200cd2d7938c7dee950ade62bed166940a27 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 28 Jul 2026 09:59:36 +0100 Subject: [PATCH 2/3] minor fixes --- Directory.Packages.props | 2 +- global.json | 2 +- src/SharpCompress/SharpCompress.csproj | 3 --- src/SharpCompress/packages.lock.json | 12 ++++++------ 4 files changed, 8 insertions(+), 11 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index ecf3d44b..c09d8026 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -19,6 +19,6 @@ Version="17.14.15" /> - + diff --git a/global.json b/global.json index f5d6141c..1f2c64d6 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "10.0.301", - "rollForward": "disable" + "rollForward": "latestPatch" } } diff --git a/src/SharpCompress/SharpCompress.csproj b/src/SharpCompress/SharpCompress.csproj index e4b75113..991eccb9 100644 --- a/src/SharpCompress/SharpCompress.csproj +++ b/src/SharpCompress/SharpCompress.csproj @@ -39,9 +39,6 @@ - - - diff --git a/src/SharpCompress/packages.lock.json b/src/SharpCompress/packages.lock.json index 7f5613ad..8d592036 100644 --- a/src/SharpCompress/packages.lock.json +++ b/src/SharpCompress/packages.lock.json @@ -339,9 +339,9 @@ "net10.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[10.0.10, )", - "resolved": "10.0.10", - "contentHash": "f5VCIE7AJpd5YvzNTeMGVzQIgyE9tX+AreTYwQF+REbu+DZo/2Ae+jNSwhPEYrVz6RRkd7y8ubXjk6Nn6Ka+Cg==" + "requested": "[10.0.9, )", + "resolved": "10.0.9", + "contentHash": "4Iw41e2h7I4t70SJcX2GCmbyKJIlA273Cfm9RJMM050/3VBejGAG1KcthP5Z2L6SQcbfbf6BhNWO26+ZG+GzMg==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", @@ -471,9 +471,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.29, )", - "resolved": "8.0.29", - "contentHash": "HSBTfrkIZijz8z3ybLRKB7E8rHk4QQufFwpHa9fc5CMIgRhRzdn4mBGmlyXZqaueiMPtuJcnjresGvSTfaW8Mg==" + "requested": "[8.0.28, )", + "resolved": "8.0.28", + "contentHash": "XMqgVjlLxLqWmEh3c49haXLQwsMNtvo6YscUaqfvEGfg1iA8hnYgkUVq3i9Zu9gKeNKMWiiZKVwZExc/qyEAsQ==" }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", From cf2dfa0c30d94f375d8e45cd396f464e24e17c82 Mon Sep 17 00:00:00 2001 From: Adam Hathcock Date: Tue, 28 Jul 2026 10:08:16 +0100 Subject: [PATCH 3/3] redid the restore --- build/packages.lock.json | 6 ++++++ global.json | 2 +- tests/SharpCompress.AotSmoke/packages.lock.json | 6 ++++++ tests/SharpCompress.Performance/packages.lock.json | 6 ++++++ tests/SharpCompress.Test/packages.lock.json | 12 ++++++++++++ 5 files changed, 31 insertions(+), 1 deletion(-) diff --git a/build/packages.lock.json b/build/packages.lock.json index 8cc0db72..734b855a 100644 --- a/build/packages.lock.json +++ b/build/packages.lock.json @@ -52,6 +52,12 @@ "resolved": "13.0.0", "contentHash": "zcCR1pupa1wI1VqBULRiQKeHKKZOuJhi/K+4V5oO+rHJZlaOD53ViFo1c3PavDoMAfSn/FAXGAWpPoF57rwhYg==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", diff --git a/global.json b/global.json index 1f2c64d6..f5d6141c 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { "version": "10.0.301", - "rollForward": "latestPatch" + "rollForward": "disable" } } diff --git a/tests/SharpCompress.AotSmoke/packages.lock.json b/tests/SharpCompress.AotSmoke/packages.lock.json index 19031145..7016eb4f 100644 --- a/tests/SharpCompress.AotSmoke/packages.lock.json +++ b/tests/SharpCompress.AotSmoke/packages.lock.json @@ -46,6 +46,12 @@ "resolved": "1.16.0", "contentHash": "3kdIIceBPumwjw279FuiVMfVENT2cGASXJgcigdySsbX2dJB8ofUgG6i47yqF/k1qu6fvNR3csrSekZPviR6kQ==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.Build.Tasks.Git": { "type": "Transitive", "resolved": "10.0.300", diff --git a/tests/SharpCompress.Performance/packages.lock.json b/tests/SharpCompress.Performance/packages.lock.json index 739ca4c7..47481ccf 100644 --- a/tests/SharpCompress.Performance/packages.lock.json +++ b/tests/SharpCompress.Performance/packages.lock.json @@ -62,6 +62,12 @@ "resolved": "1.16.0", "contentHash": "3kdIIceBPumwjw279FuiVMfVENT2cGASXJgcigdySsbX2dJB8ofUgG6i47yqF/k1qu6fvNR3csrSekZPviR6kQ==" }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "BenchmarkDotNet.Annotations": { "type": "Transitive", "resolved": "0.15.8", diff --git a/tests/SharpCompress.Test/packages.lock.json b/tests/SharpCompress.Test/packages.lock.json index 2f685ff1..d1e626ce 100644 --- a/tests/SharpCompress.Test/packages.lock.json +++ b/tests/SharpCompress.Test/packages.lock.json @@ -70,6 +70,12 @@ "xunit.v3.mtp-v1": "[3.2.2]" } }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0", @@ -392,6 +398,12 @@ "xunit.v3.mtp-v1": "[3.2.2]" } }, + "Zomp.SyncMethodGenerator": { + "type": "Direct", + "requested": "[2.0.40, )", + "resolved": "2.0.40", + "contentHash": "lpU06HVF3AXHHU+qQSmoCCYPf3QfAUD4tePJ6o2PZta0K1Vt2jEyChwEeqTuG1940SB7kT6Wp5BagmL+7ILimw==" + }, "Microsoft.ApplicationInsights": { "type": "Transitive", "resolved": "2.23.0",