Use STREAM_DISPOSEASYNC capability guards for stream disposal

This commit is contained in:
Adam Hathcock
2026-10-13 12:41:38 +01:00
parent d431c80b38
commit 3bbe0cc93f
41 changed files with 70 additions and 65 deletions

View File

@@ -25,7 +25,7 @@ public partial class GZipArchive
CancellationToken cancellationToken = default
)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
#else
await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
@@ -47,7 +47,7 @@ public partial class GZipArchive
{
throw new InvalidFormatException("Only one entry is allowed in a GZip Archive");
}
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var writer = new GZipWriter(stream, options);
#else
await using var writer = new GZipWriter(stream, options);
@@ -58,7 +58,7 @@ public partial class GZipArchive
{
if (!entry.IsDirectory)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
#else
await using var entryStream = await entry
@@ -77,7 +77,7 @@ public partial class GZipArchive
}
foreach (var entry in newEntries.Where(x => !x.IsDirectory))
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
#else
await using var entryStream = await entry

View File

@@ -77,7 +77,7 @@ public static partial class IArchiveEntryExtensions
var entryStream = await archiveEntry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStreamScope = entryStream;
#else
await using var entryStreamScope = entryStream
@@ -168,7 +168,7 @@ public static partial class IArchiveEntryExtensions
options,
async (x, fm, ct) =>
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var fs = File.Open(x, fm);
#else
await using var fs = File.Open(x, fm);

View File

@@ -83,7 +83,7 @@ public static class IWritableAsyncArchiveExtensions
)
where TOptions : IWriterOptions
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);
#else
await using var stream = fileInfo.Open(FileMode.Create, FileAccess.Write);

View File

@@ -22,7 +22,7 @@ public partial class TarArchive
CancellationToken cancellationToken = default
)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var writer = new TarWriter(stream, options);
#else
await using var writer = new TarWriter(stream, options);
@@ -43,7 +43,7 @@ public partial class TarArchive
}
else
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
#else
await using var entryStream = await entry
@@ -75,7 +75,7 @@ public partial class TarArchive
}
else
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
.OpenEntryStreamAsync(cancellationToken)
.ConfigureAwait(false);

View File

@@ -84,7 +84,7 @@ public partial class ZipArchive
CancellationToken cancellationToken = default
)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var writer = new ZipWriter(stream, options);
#else
await using var writer = new ZipWriter(stream, options);
@@ -105,7 +105,7 @@ public partial class ZipArchive
}
else
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
#else
await using var entryStream = await entry
@@ -135,7 +135,7 @@ public partial class ZipArchive
}
else
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await entry
#else
await using var entryStream = await entry

View File

@@ -122,7 +122,7 @@ internal partial class MarkHeader
{
if (!leaveStreamOpen)
{
#if LEGACY_DOTNET && !NETSTANDARD2_1
#if !STREAM_DISPOSEASYNC
stream.Dispose();
#else
await stream.DisposeAsync().ConfigureAwait(false);

View File

@@ -122,7 +122,7 @@ internal sealed class SevenZipStreamsCompressor(Stream outputStream)
uint inputCrc2;
long inputSize2;
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var lzma2Stream = new Lzma2EncoderStream(
#else
await using var lzma2Stream = new Lzma2EncoderStream(
@@ -156,7 +156,7 @@ internal sealed class SevenZipStreamsCompressor(Stream outputStream)
uint inputCrc;
long inputSize;
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var lzmaStream = LzmaStream.Create(encoderProperties, false, outCrcStream);
#else
await using var lzmaStream = LzmaStream.Create(encoderProperties, false, outCrcStream);

View File

@@ -34,7 +34,7 @@ internal class TarReadOnlySubStream : Stream
base.Dispose(disposing);
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
public override async System.Threading.Tasks.ValueTask DisposeAsync()
{
if (_isDisposed)

View File

@@ -7,7 +7,7 @@ public abstract partial class Volume
{
public virtual async ValueTask DisposeAsync()
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
_actualStream.Dispose();
await Task.CompletedTask.ConfigureAwait(false);
#else

View File

@@ -10,7 +10,7 @@ namespace SharpCompress.Common.Zip;
internal partial class WinzipAesCryptoStream
{
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (_isDisposed)

View File

@@ -87,7 +87,7 @@ internal partial class WinzipAesCryptoStream : Stream
{
if (!_authRead)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
// Legacy Stream lacks DisposeAsync, including for async-only inputs.
#pragma warning disable VSTHRD002 // Avoid problematic synchronous waits
#pragma warning disable CA2012

View File

@@ -115,7 +115,7 @@ public sealed partial class BZip2Stream : IAsyncDisposable
CancellationToken cancellationToken = default
) => await stream.WriteAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false);
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -136,7 +136,7 @@ public sealed partial class BZip2Stream : IAsyncDisposable
stream.Dispose();
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);

View File

@@ -670,7 +670,7 @@ internal sealed partial class CBZip2OutputStream : IAsyncDisposable
await bsStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -696,7 +696,7 @@ internal sealed partial class CBZip2OutputStream : IAsyncDisposable
}
bsStream = null;
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);

View File

@@ -8,7 +8,7 @@ namespace SharpCompress.Compressors.Deflate;
public partial class DeflateStream
{
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -22,7 +22,7 @@ public partial class DeflateStream
}
_disposed = true;
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#else
await Task.CompletedTask.ConfigureAwait(false);

View File

@@ -183,7 +183,7 @@ public partial class GZipStream
}
#endif
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -200,7 +200,7 @@ public partial class GZipStream
await BaseStream.DisposeAsync().ConfigureAwait(false);
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}

View File

@@ -510,7 +510,7 @@ internal partial class ZlibBaseStream : Stream, IStreamStack
}
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -521,7 +521,7 @@ internal partial class ZlibBaseStream : Stream, IStreamStack
return;
}
isDisposed = true;
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
if (_stream is null)

View File

@@ -21,7 +21,7 @@ public partial class ZlibStream
await _baseStream.FlushAsync(cancellationToken).ConfigureAwait(false);
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (_disposed)

View File

@@ -200,7 +200,7 @@ internal sealed class Lzma2EncoderStream : Stream
private static readonly byte[] _endMarker = [0x00];
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -229,7 +229,7 @@ internal sealed class Lzma2EncoderStream : Stream
}
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}

View File

@@ -454,7 +454,7 @@ public partial class LzmaStream
}
#endif
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -494,7 +494,7 @@ public partial class LzmaStream
}
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
GC.SuppressFinalize(this);

View File

@@ -160,7 +160,7 @@ public class PpmdStream : Stream, IAsyncDisposable
}
catch
{
#if LEGACY_DOTNET && !NETSTANDARD2_1
#if !STREAM_DISPOSEASYNC
instance.Dispose();
#else
await instance.DisposeAsync().ConfigureAwait(false);
@@ -196,7 +196,7 @@ public class PpmdStream : Stream, IAsyncDisposable
base.Dispose(disposing);
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -215,7 +215,7 @@ public class PpmdStream : Stream, IAsyncDisposable
_modelH?.Dispose();
_modelH = null;
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
await base.DisposeAsync().ConfigureAwait(false);
#endif
}

View File

@@ -8,7 +8,7 @@ namespace SharpCompress.Compressors.ZStandard;
public partial class CompressionStream
{
#if !LEGACY_DOTNET || NETSTANDARD2_1
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
#else
public async ValueTask DisposeAsync()
@@ -16,7 +16,7 @@ public partial class CompressionStream
{
if (compressor == null)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
Dispose(true);
GC.SuppressFinalize(this);
await Task.CompletedTask.ConfigureAwait(false);
@@ -35,7 +35,7 @@ public partial class CompressionStream
ReleaseUnmanagedResources();
GC.SuppressFinalize(this);
}
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
Dispose(true);
await Task.CompletedTask.ConfigureAwait(false);
#else

View File

@@ -203,7 +203,7 @@ public partial class DecompressionStream : Stream
}
}
#if LEGACY_DOTNET && !NETSTANDARD2_1
#if !STREAM_DISPOSEASYNC
public virtual ValueTask DisposeAsync()
{
try

View File

@@ -22,11 +22,11 @@ namespace SharpCompress.IO;
/// </para>
/// </remarks>
public abstract class AsyncDisposableStream : Stream
#if NO_STREAM_DISPOSEASYNC
#if !STREAM_DISPOSEASYNC
, IAsyncDisposable
#endif
{
#if NO_STREAM_DISPOSEASYNC
#if !STREAM_DISPOSEASYNC
public virtual ValueTask DisposeAsync()
{
Dispose();

View File

@@ -45,7 +45,7 @@ internal sealed partial class ProgressReportingStream
}
#endif
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (!_leaveOpen)

View File

@@ -28,7 +28,9 @@ internal sealed partial class SeekableSharpCompressStream
ReadOnlyMemory<byte> buffer,
CancellationToken cancellationToken = default
) => _stream.WriteAsync(buffer, cancellationToken);
#endif
#if STREAM_DISPOSEASYNC
public override ValueTask DisposeAsync()
{
if (_isDisposed)

View File

@@ -251,7 +251,7 @@ public partial class SharpCompressStream
}
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
public override async ValueTask DisposeAsync()
{
if (!isDisposed)

View File

@@ -104,7 +104,7 @@ public abstract partial class AbstractReader<TEntry, TVolume>
}
}
//don't know the size so we have to try to decompress to skip
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
#else
await using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
@@ -138,7 +138,7 @@ public abstract partial class AbstractReader<TEntry, TVolume>
private async ValueTask WriteAsync(Stream writeStream, CancellationToken cancellationToken)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);
#else
await using var s = await OpenEntryStreamAsync(cancellationToken).ConfigureAwait(false);

View File

@@ -45,7 +45,7 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var fs = File.Open(x, fm);
#else
await using var fs = File.Open(x, fm);
@@ -87,7 +87,7 @@ public static class IAsyncReaderExtensions
options,
async (x, fm, ct) =>
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var fs = File.Open(x, fm);
#else
await using var fs = File.Open(x, fm);
@@ -116,7 +116,7 @@ public static class IAsyncReaderExtensions
CancellationToken cancellationToken
)
{
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var entryStream = await reader
#else
await using var entryStream = await reader

View File

@@ -31,8 +31,8 @@
<PropertyGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'netstandard2.0' Or '$(TargetFramework)' == 'netstandard2.1' ">
<DefineConstants>$(DefineConstants);LEGACY_DOTNET</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net48' Or '$(TargetFramework)' == 'netstandard2.0'">
<DefineConstants>$(DefineConstants);NO_STREAM_DISPOSEASYNC</DefineConstants>
<PropertyGroup Condition=" '$(TargetFramework)' == 'netstandard2.1' Or '$(TargetFramework)' == 'net6.0' Or '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0' ">
<DefineConstants>$(DefineConstants);STREAM_DISPOSEASYNC</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net8.0' Or '$(TargetFramework)' == 'net10.0' ">
<IsTrimmable>true</IsTrimmable>

View File

@@ -70,7 +70,7 @@ internal static partial class Utility
)
{
// Use ReadOnlySubStream to limit reading and leverage framework's CopyToAsync
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);
#else
await using var limitedStream = new IO.ReadOnlySubStream(source, maxLength);

View File

@@ -77,7 +77,7 @@ public static class IWriterExtensions
{
throw new ArgumentException("Source does not exist: " + source.FullName);
}
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var stream = source.OpenRead();
#else
await using var stream = source.OpenRead();

View File

@@ -83,7 +83,7 @@ public partial class ZipWriter
#if !SYNC_ONLY
cancellationToken.ThrowIfCancellationRequested();
#endif
#if LEGACY_DOTNET
#if !STREAM_DISPOSEASYNC
using var output = await WriteToStreamAsync(
#else
await using var output = await WriteToStreamAsync(

View File

@@ -12,6 +12,9 @@
<PlatformTarget>AnyCPU</PlatformTarget>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net10.0' ">
<DefineConstants>$(DefineConstants);STREAM_DISPOSEASYNC</DefineConstants>
</PropertyGroup>
<PropertyGroup Condition="$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Windows)))">
<DefineConstants>$(DefineConstants);WINDOWS</DefineConstants>
</PropertyGroup>

View File

@@ -58,7 +58,7 @@ public class BufferPoolLifetimeTests
stream.Dispose();
}
stream.Dispose();
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
await stream.DisposeAsync();
#else
await Task.CompletedTask;

View File

@@ -25,7 +25,7 @@ public class SharpCompressLifetimeTests
Assert.NotNull(ring.GetValue(stream));
if (asyncDispose)
{
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
await Assert.ThrowsAsync<IOException>(async () => await stream.DisposeAsync());
#else
Assert.Throws<IOException>(() => stream.Dispose());

View File

@@ -9,7 +9,7 @@ namespace SharpCompress.Test.Streams;
public class SharpCompressStreamEdgeAsyncTest
{
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask DisposeAsync_WithLeaveStreamOpenTrue_DoesNotDisposeUnderlying()

View File

@@ -49,7 +49,7 @@ public class SharpCompressStreamErrorAsyncTest
}
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask DisposeAsync_WithThrowOnDisposeTrue_ThrowsInvalidOperation()
{

View File

@@ -84,7 +84,7 @@ public class SharpCompressStreamPassthroughAsyncTest
Assert.Equal(3, ms.Length);
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask CreateNonDisposing_DoesNotDisposeUnderlying_Async()
{

View File

@@ -82,7 +82,7 @@ public class WinzipAesCryptoStreamTests
{
stream.Dispose();
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
else if (completion == "dispose-async")
{
await stream.DisposeAsync();
@@ -153,7 +153,7 @@ public class WinzipAesCryptoStreamTests
stream.Dispose();
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async Task DisposeAsync_Verifies_Unread_Ciphertext()
{

View File

@@ -286,7 +286,7 @@ public class TarArchiveAsyncTests : ArchiveTests
Assert.Equal(0, opened.Length);
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask Tar_Save_New_Entry_Async_Only_Streams()
{

View File

@@ -265,7 +265,7 @@ public class ZipArchiveAsyncTests : ArchiveTests
Assert.Equal(0, opened.Length);
}
#if !LEGACY_DOTNET
#if STREAM_DISPOSEASYNC
[Fact]
public async ValueTask Zip_Save_New_Entry_Async_Only_Streams()
{