Merge remote-tracking branch 'origin/master' into perf/7zip-profile-pooling

# Conflicts:
#	src/SharpCompress/Compressors/LZMA/Lzma2EncoderStream.cs
#	src/SharpCompress/Compressors/LZMA/LzmaStream.Async.cs
#	src/SharpCompress/IO/ReadOnlyMemoryStream.cs
This commit is contained in:
Adam Hathcock
2026-10-20 08:31:32 +01:00
18 changed files with 411 additions and 102 deletions

View File

@@ -3,6 +3,7 @@ using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Common;
using SharpCompress.IO;
namespace SharpCompress.Compressors.LZMA;
@@ -339,11 +340,12 @@ internal sealed class Lzma2EncoderStream : Stream
try
{
_encoder.Code(inputMs, outputMs, length, -1, null);
// Trial-decode to exclude trailing range-encoder bytes from the chunk size.
outputMs.Position = 0;
// Trial decoding excludes range-encoder flush bytes from the chunk size.
var consumed = FindConsumedBytes(outputMs, (int)outputMs.Length, length);
var consumed = FindConsumedBytes(outputMs, length);
outputMs.SetLength(consumed);
outputMs.Position = 0;
// The caller owns the pooled output until the chunk write completes.
return outputMs;
}
catch
@@ -356,7 +358,7 @@ internal sealed class Lzma2EncoderStream : Stream
}
}
private int FindConsumedBytes(Stream input, int compressedSize, int uncompressedSize)
private int FindConsumedBytes(PooledMemoryStream input, int uncompressedSize)
{
// Build 5-byte LZMA property header: [pb/lp/lc byte] [dictSize as LE int32]
Span<byte> props = stackalloc byte[5];
@@ -369,7 +371,7 @@ internal sealed class Lzma2EncoderStream : Stream
var decoder = _trialDecoder ??= new Decoder();
decoder.SetDecoderProperties(props);
decoder.Code(input, Stream.Null, compressedSize, uncompressedSize, null);
decoder.Code(input, Stream.Null, input.Length, uncompressedSize, null);
return (int)input.Position;
}
@@ -434,7 +436,14 @@ internal sealed class Lzma2EncoderStream : Stream
{
ArrayPool<byte>.Shared.Return(header);
}
await compressedData.CopyToAsync(_output, 65536, cancellationToken).ConfigureAwait(false);
// Compressed chunks fit 64 KiB; avoid an 80 KiB request rounding to 128 KiB.
await compressedData
.CopyToAsync(
_output,
Math.Min(Constants.BufferSize, MAX_COMPRESSED_CHUNK_SIZE),
cancellationToken
)
.ConfigureAwait(false);
}
/// <summary>

View File

@@ -465,7 +465,7 @@ public partial class LzmaStream
cancellationToken.ThrowIfCancellationRequested();
if (_encoder != null)
{
using var input = new SharpCompress.IO.ReadOnlyMemoryStream(buffer);
using var input = new ReadOnlyMemoryStream(buffer);
_position = await _encoder
.CodeAsync(input, false, cancellationToken)
.ConfigureAwait(false);

View File

@@ -412,13 +412,9 @@ public class PpmdStream : Stream, IAsyncDisposable
cancellationToken.ThrowIfCancellationRequested();
if (_compress)
{
using var input = new ReadOnlyMemoryStream(buffer);
await _model
.EncodeBlockAsync(
_stream,
new MemoryStream(buffer.ToArray()),
false,
cancellationToken
)
.EncodeBlockAsync(_stream, input, false, cancellationToken)
.ConfigureAwait(false);
}
}

View File

@@ -5,55 +5,80 @@ using System.Threading.Tasks;
namespace SharpCompress.IO;
// Borrows caller memory only until the awaited encoder operation completes.
// Borrows memory for the lifetime of an encoder call; never copies or owns the backing storage.
internal sealed class ReadOnlyMemoryStream(ReadOnlyMemory<byte> memory) : Stream
{
private int position;
public override bool CanRead => true;
private ReadOnlyMemory<byte> _memory = memory;
private int _position;
private bool _disposed;
public override bool CanRead => !_disposed;
public override bool CanSeek => false;
public override bool CanWrite => false;
public override long Length => memory.Length;
public override long Length =>
!_disposed
? _memory.Length
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
public override long Position
{
get => position;
get =>
!_disposed
? _position
: throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
set => throw new NotSupportedException();
}
private int ReadMemory(Span<byte> buffer)
public override int Read(byte[] buffer, int offset, int count)
{
var count = Math.Min(buffer.Length, memory.Length - position);
memory.Span.Slice(position, count).CopyTo(buffer);
position += count;
ThrowHelper.ThrowIfNull(buffer, nameof(buffer));
if (offset < 0 || count < 0 || offset > buffer.Length - count)
{
throw new ArgumentOutOfRangeException(nameof(count), "Invalid buffer range.");
}
return ReadCore(buffer.AsSpan(offset, count));
}
private int ReadCore(Span<byte> buffer)
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
}
var count = Math.Min(buffer.Length, _memory.Length - _position);
_memory.Span.Slice(_position, count).CopyTo(buffer);
_position += count;
return count;
}
public override int Read(byte[] buffer, int offset, int count) =>
ReadMemory(buffer.AsSpan(offset, count));
public override int ReadByte() => position < memory.Length ? memory.Span[position++] : -1;
public override int ReadByte()
{
if (_disposed)
{
throw new ObjectDisposedException(nameof(ReadOnlyMemoryStream));
}
return _position < _memory.Length ? _memory.Span[_position++] : -1;
}
public override Task<int> ReadAsync(
byte[] buffer,
int offset,
int count,
CancellationToken cancellationToken
)
{
cancellationToken.ThrowIfCancellationRequested();
return Task.FromResult(Read(buffer, offset, count));
}
) =>
cancellationToken.IsCancellationRequested
? Task.FromCanceled<int>(cancellationToken)
: Task.FromResult(Read(buffer, offset, count));
#if !LEGACY_DOTNET
public override int Read(Span<byte> buffer) => ReadMemory(buffer);
public override int Read(Span<byte> buffer) => ReadCore(buffer);
public override ValueTask<int> ReadAsync(
Memory<byte> buffer,
CancellationToken cancellationToken = default
)
{
cancellationToken.ThrowIfCancellationRequested();
return new ValueTask<int>(ReadMemory(buffer.Span));
}
) =>
cancellationToken.IsCancellationRequested
? ValueTask.FromCanceled<int>(cancellationToken)
: ValueTask.FromResult(ReadCore(buffer.Span));
#endif
public override void Flush() { }
@@ -64,4 +89,11 @@ internal sealed class ReadOnlyMemoryStream(ReadOnlyMemory<byte> memory) : Stream
public override void Write(byte[] buffer, int offset, int count) =>
throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
_disposed = true;
_memory = default;
base.Dispose(disposing);
}
}

View File

@@ -103,21 +103,18 @@ public partial class SevenZipWriter
// Write the encoded header to the output
var headerStartPos = output.Position;
encodedHeaderStream.Position = 0;
await encodedHeaderStream.CopyToAsync(output).ConfigureAwait(false);
// Compute CRC of the encoded header
var headerCrc = Crc32Stream.Compute(
Crc32Stream.DEFAULT_POLYNOMIAL,
Crc32Stream.DEFAULT_SEED,
encodedHeaderStream.GetBuffer().AsSpan(0, (int)encodedHeaderStream.Length)
);
// Compute the CRC during the copy without materializing the pooled header.
using var crcStream = new Crc32Stream(output);
await encodedHeaderStream
.CopyToAsync(crcStream, Constants.BufferSize, CancellationToken.None)
.ConfigureAwait(false);
// Back-patch signature header
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
var nextHeaderSize = (ulong)encodedHeaderStream.Length;
await SevenZipSignatureHeaderWriter
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, headerCrc)
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, crcStream.Crc)
.ConfigureAwait(false);
// Seek to end
@@ -134,21 +131,18 @@ public partial class SevenZipWriter
// Write raw header directly
var headerStartPos = output.Position;
rawHeaderStream.Position = 0;
await rawHeaderStream.CopyToAsync(output).ConfigureAwait(false);
// Compute CRC of the raw header
var headerCrc = Crc32Stream.Compute(
Crc32Stream.DEFAULT_POLYNOMIAL,
Crc32Stream.DEFAULT_SEED,
rawHeaderStream.GetBuffer().AsSpan(0, (int)rawHeaderStream.Length)
);
// Compute the CRC during the copy without materializing the pooled header.
using var crcStream = new Crc32Stream(output);
await rawHeaderStream
.CopyToAsync(crcStream, Constants.BufferSize, CancellationToken.None)
.ConfigureAwait(false);
// Back-patch signature header
var nextHeaderOffset = (ulong)(headerStartPos - SevenZipSignatureHeaderWriter.HeaderSize);
var nextHeaderSize = (ulong)rawHeaderStream.Length;
await SevenZipSignatureHeaderWriter
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, headerCrc)
.WriteFinalAsync(output, nextHeaderOffset, nextHeaderSize, crcStream.Crc)
.ConfigureAwait(false);
// Seek to end

View File

@@ -4,6 +4,7 @@ using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using SharpCompress.Compressors;
using SharpCompress.Compressors.Deflate;
using SharpCompress.IO;
namespace SharpCompress.Performance.Benchmarks;
@@ -45,6 +46,22 @@ public class GZipBenchmarks
await gzipStream.WriteAsync(_sourceData, 0, _sourceData.Length).ConfigureAwait(false);
}
[Benchmark(Description = "GZip: Compress 100KB (Pooled)")]
public void GZipCompressPooled()
{
using var outputStream = new PooledMemoryStream();
using var gzipStream = new GZipStream(outputStream, CompressionMode.Compress);
gzipStream.Write(_sourceData, 0, _sourceData.Length);
}
[Benchmark(Description = "GZip: Compress 100KB (Async, Pooled)")]
public async Task GZipCompressAsyncPooled()
{
using var outputStream = new PooledMemoryStream();
using var gzipStream = new GZipStream(outputStream, CompressionMode.Compress);
await gzipStream.WriteAsync(_sourceData, 0, _sourceData.Length).ConfigureAwait(false);
}
[Benchmark(Description = "GZip: Decompress 100KB")]
public void GZipDecompress()
{

View File

@@ -7,6 +7,7 @@ using SharpCompress.Archives;
using SharpCompress.Archives.Tar;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.IO;
using SharpCompress.Providers;
using SharpCompress.Providers.System;
using SharpCompress.Readers;
@@ -127,6 +128,38 @@ public class TarBenchmarks : ArchiveBenchmarkBase
}
}
[Benchmark(Description = "Tar: Create archive with small files (Pooled)")]
public void TarCreateSmallFilesPooled()
{
using var outputStream = new PooledMemoryStream();
using var writer = WriterFactory.OpenWriter(
outputStream,
ArchiveType.Tar,
new WriterOptions(CompressionType.None) { LeaveStreamOpen = true }
);
for (var i = 0; i < 10; i++)
{
using var entryStream = new MemoryStream(new byte[1024]);
writer.Write($"file{i}.txt", entryStream);
}
}
[Benchmark(Description = "Tar: Create archive with small files (Async, Pooled)")]
public async Task TarCreateSmallFilesAsyncPooled()
{
using var outputStream = new PooledMemoryStream();
await using var writer = await WriterFactory.OpenAsyncWriter(
outputStream,
ArchiveType.Tar,
new WriterOptions(CompressionType.None) { LeaveStreamOpen = true }
);
for (var i = 0; i < 10; i++)
{
using var entryStream = new MemoryStream(new byte[1024]);
await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false);
}
}
[Benchmark(Description = "Tar: Create archive with small files")]
public void TarCreateSmallFiles()
{

View File

@@ -6,6 +6,7 @@ using BenchmarkDotNet.Attributes;
using SharpCompress.Archives.Zip;
using SharpCompress.Common;
using SharpCompress.Compressors;
using SharpCompress.IO;
using SharpCompress.Providers;
using SharpCompress.Providers.System;
using SharpCompress.Readers;
@@ -114,6 +115,38 @@ public class ZipBenchmarks : ArchiveBenchmarkBase
}
}
[Benchmark(Description = "Zip: Create archive with small files (Pooled)")]
public void ZipCreateSmallFilesPooled()
{
using var outputStream = new PooledMemoryStream();
using var writer = WriterFactory.OpenWriter(
outputStream,
ArchiveType.Zip,
new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = true }
);
for (var i = 0; i < 10; i++)
{
using var entryStream = new MemoryStream(new byte[1024]);
writer.Write($"file{i}.txt", entryStream);
}
}
[Benchmark(Description = "Zip: Create archive with small files (Async, Pooled)")]
public async Task ZipCreateSmallFilesAsyncPooled()
{
using var outputStream = new PooledMemoryStream();
await using var writer = await WriterFactory.OpenAsyncWriter(
outputStream,
ArchiveType.Zip,
new WriterOptions(CompressionType.Deflate) { LeaveStreamOpen = true }
);
for (var i = 0; i < 10; i++)
{
using var entryStream = new MemoryStream(new byte[1024]);
await writer.WriteAsync($"file{i}.txt", entryStream).ConfigureAwait(false);
}
}
[Benchmark(Description = "Zip: Create archive with small files")]
public void ZipCreateSmallFiles()
{

View File

@@ -1,4 +1,5 @@
using System;
using System.Buffers.Binary;
using System.IO;
using System.Linq;
using System.Text;
@@ -6,6 +7,7 @@ using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Archives.SevenZip;
using SharpCompress.Common;
using SharpCompress.Crypto;
using SharpCompress.Test.Mocks;
using SharpCompress.Writers;
using SharpCompress.Writers.SevenZip;
@@ -15,6 +17,57 @@ namespace SharpCompress.Test.SevenZip;
public class SevenZipWriterAsyncTests : TestBase
{
[Theory]
[InlineData(false, 1)]
[InlineData(true, 1)]
[InlineData(false, 256)]
[InlineData(true, 256)]
public async ValueTask SevenZipWriter_Async_HeaderCrc_MatchesWrittenBytes(
bool compressHeader,
int directoryCount
)
{
using var output = new MemoryStream();
await using (
var writer = new SevenZipWriter(
new AsyncOnlyStream(output),
new SevenZipWriterOptions { CompressHeader = compressHeader }
)
)
{
// Large names make the raw header span multiple pooled blocks.
for (var i = 0; i < directoryCount; i++)
{
await writer.WriteDirectoryAsync(
$"dir{i}-{new string('x', 512)}",
null,
CancellationToken.None
);
}
}
Assert.True(output.CanWrite);
var bytes = output.ToArray();
var headerOffset = BinaryPrimitives.ReadUInt64LittleEndian(bytes.AsSpan(12));
var headerSize = BinaryPrimitives.ReadUInt64LittleEndian(bytes.AsSpan(20));
var headerCrc = BinaryPrimitives.ReadUInt32LittleEndian(bytes.AsSpan(28));
Assert.Equal((ulong)bytes.Length, 32 + headerOffset + headerSize);
Assert.Equal(
Crc32Stream.Compute(
Crc32Stream.DEFAULT_POLYNOMIAL,
Crc32Stream.DEFAULT_SEED,
bytes.AsSpan(checked(32 + (int)headerOffset), checked((int)headerSize))
),
headerCrc
);
output.Position = 0;
await using var archive = await SevenZipArchive.OpenAsyncArchive(output);
var entries = await archive.EntriesAsync.ToListAsync();
Assert.Equal(directoryCount, entries.Count);
Assert.All(entries, entry => Assert.True(entry.IsDirectory));
}
[Fact]
public async ValueTask SevenZipWriter_Async_SingleFile_RoundTrip()
{

View File

@@ -103,13 +103,11 @@ public class BufferPoolLifetimeTests
Assert.Equal(1, pool.ReturnCount);
Assert.Single(pool.Active);
#if !LEGACY_DOTNET
if (asyncDispose)
{
await stream.DisposeAsync();
await using (stream.DisposeAsyncScope().ConfigureAwait(false)) { }
}
else
#endif
{
stream.Dispose();
}

View File

@@ -0,0 +1,61 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA;
using Xunit;
namespace SharpCompress.Test.Streams;
public class Lzma2EncoderBufferTests
{
[Theory]
[InlineData(false, false)]
[InlineData(false, true)]
[InlineData(true, false)]
[InlineData(true, true)]
public async Task Pooled_Compressed_Output_RoundTrips(bool useAsync, bool incompressible)
{
var data = new byte[150000];
if (incompressible)
{
new Random(42).NextBytes(data);
}
using var packed = new MemoryStream();
using (var encoder = new Lzma2EncoderStream(packed, 1 << 16, 32))
{
if (useAsync)
{
await encoder.WriteAsync(data, 0, data.Length, CancellationToken.None);
await encoder.DisposeAsync();
}
else
{
encoder.Write(data, 0, data.Length);
}
}
packed.Position = 0;
using var decoded = new MemoryStream();
if (useAsync)
{
await using var decoder = await LzmaStream.CreateAsync(
new byte[] { 8 },
packed,
packed.Length,
data.Length
);
await decoder.CopyToAsync(decoded, 81920, CancellationToken.None);
}
else
{
using var decoder = LzmaStream.Create(
new byte[] { 8 },
packed,
packed.Length,
data.Length
);
decoder.CopyTo(decoded);
}
Assert.Equal(data, decoded.ToArray());
}
}

View File

@@ -92,11 +92,8 @@ public class Lzma2TruncationTests
data[i] = Convert.ToByte(TruncatedXzHex.Substring(i * 2, 2), 16);
}
using var input = new MemoryStream(data);
#if LEGACY_DOTNET
using var decoder = new XZStream(input);
#else
await using var decoder = new XZStream(input);
#endif
var decoder = new XZStream(input);
await using var scope = decoder.DisposeAsyncScope().ConfigureAwait(false);
if (useAsync)
{
await Assert.ThrowsAnyAsync<SharpCompressException>(() =>
@@ -120,11 +117,8 @@ public class Lzma2TruncationTests
await using var archive = await ArchiveFactory.OpenAsyncArchive(input);
await foreach (var entry in archive.EntriesAsync)
{
#if LEGACY_DOTNET
using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#else
await using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#endif
var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await Assert.ThrowsAnyAsync<SharpCompressException>(() =>
stream.CopyToAsync(Stream.Null, 81920, CancellationToken.None)
);

View File

@@ -0,0 +1,67 @@
#if !LEGACY_DOTNET
using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.Compressors.LZMA;
using SharpCompress.Compressors.PPMd;
using Xunit;
namespace SharpCompress.Test.Streams;
public class MemoryEncoderWriteTests
{
[Theory]
[InlineData(false)]
[InlineData(true)]
public async Task Memory_Writes_Match_Array_Writes(bool ppmd)
{
var data = new byte[20000];
new Random(42).NextBytes(data);
using var memory = new TestMemoryManager(data);
using var expected = new MemoryStream();
using var actual = new MemoryStream();
await Encode(expected, false);
await Encode(actual, true);
Assert.Equal(expected.ToArray(), actual.ToArray());
Assert.Equal(data, memory.GetSpan().ToArray());
async Task Encode(Stream output, bool useMemory)
{
await using Stream encoder = ppmd
? PpmdStream.Create(new PpmdProperties(), output, true)
: LzmaStream.Create(new LzmaEncoderProperties(true, 4096), false, output);
for (var offset = 1; offset < data.Length - 1; )
{
var count = Math.Min(137, data.Length - 1 - offset);
if (useMemory)
{
await encoder.WriteAsync(
memory.Memory.Slice(offset, count),
CancellationToken.None
);
}
else
{
await encoder.WriteAsync(data, offset, count, CancellationToken.None);
}
offset += count;
}
await encoder.WriteAsync(ReadOnlyMemory<byte>.Empty, CancellationToken.None);
}
}
// Exercises memory that cannot be unwrapped with MemoryMarshal.TryGetArray.
private sealed class TestMemoryManager(byte[] data) : MemoryManager<byte>
{
public override Span<byte> GetSpan() => data;
public override MemoryHandle Pin(int elementIndex = 0) => throw new NotSupportedException();
public override void Unpin() { }
protected override void Dispose(bool disposing) { }
}
}
#endif

View File

@@ -70,11 +70,8 @@ public class RarLifetimeTests : TestBase
);
await foreach (var entry in archive.EntriesAsync.Where(entry => !entry.IsDirectory))
{
#if LEGACY_DOTNET
using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#else
await using var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#endif
var stream = await entry.OpenEntryStreamAsync(CancellationToken.None);
await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await stream.CopyToAsync(Stream.Null, Constants.BufferSize, CancellationToken.None);
}

View File

@@ -0,0 +1,43 @@
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using SharpCompress.IO;
using Xunit;
namespace SharpCompress.Test.Streams;
public class ReadOnlyMemoryStreamTests
{
[Fact]
public async Task Reads_Borrowed_Slice_Without_Copying()
{
var data = new byte[] { 99, 1, 2, 3, 99 };
using var stream = new ReadOnlyMemoryStream(data.AsMemory(1, 3));
data[1] = 4;
Assert.Equal(4, stream.ReadByte());
var buffer = new byte[4];
Assert.Equal(2, await stream.ReadAsync(buffer, 1, 3, CancellationToken.None));
Assert.Equal(new byte[] { 0, 2, 3, 0 }, buffer);
Assert.Equal(3, stream.Position);
Assert.Equal(0, stream.Read(buffer, 0, buffer.Length));
Assert.Equal(-1, stream.ReadByte());
}
[Fact]
public async Task Cancellation_And_Disposal_Do_Not_Consume_Memory()
{
var data = new byte[] { 1, 2, 3 };
var stream = new ReadOnlyMemoryStream(data);
using var cts = new CancellationTokenSource();
cts.Cancel();
await Assert.ThrowsAnyAsync<OperationCanceledException>(() =>
stream.ReadAsync(new byte[3], 0, 3, cts.Token)
);
Assert.Equal(0, stream.Position);
stream.Dispose();
Assert.False(stream.CanRead);
Assert.Throws<ObjectDisposedException>(() => stream.ReadByte());
Assert.Equal(new byte[] { 1, 2, 3 }, data);
}
}

View File

@@ -46,11 +46,8 @@ public class SevenZipLifetimeTests : TestBase
{
if (!reader.Entry.IsDirectory)
{
#if LEGACY_DOTNET
using var stream = await reader.OpenEntryStreamAsync(CancellationToken.None);
#else
await using var stream = await reader.OpenEntryStreamAsync(CancellationToken.None);
#endif
var stream = await reader.OpenEntryStreamAsync(CancellationToken.None);
await using var scope = stream.DisposeAsyncScope().ConfigureAwait(false);
await stream.CopyToAsync(Stream.Null, Constants.BufferSize, CancellationToken.None);
if (!readAll)
{
@@ -87,11 +84,8 @@ public class SevenZipLifetimeTests : TestBase
// Disposing a borrowed reader must not prevent another extraction.
var entry = await archive.EntriesAsync.Where(entry => !entry.IsDirectory).FirstAsync();
#if LEGACY_DOTNET
using var entryStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#else
await using var entryStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
#endif
var entryStream = await entry.OpenEntryStreamAsync(CancellationToken.None);
await using var scope = entryStream.DisposeAsyncScope().ConfigureAwait(false);
await entryStream.CopyToAsync(
Stream.Null,
Constants.BufferSize,

View File

@@ -188,11 +188,8 @@ public class ZipEncryptedDataDescriptorTests : TestBase
continue;
}
#if LEGACY_DOTNET
using var entryStream = await entry.OpenEntryStreamAsync();
#else
await using var entryStream = await entry.OpenEntryStreamAsync();
#endif
var entryStream = await entry.OpenEntryStreamAsync();
await using var scope = entryStream.DisposeAsyncScope().ConfigureAwait(false);
using var output = new MemoryStream();
await entryStream.CopyToAsync(output);
contents.Add(entry.Key!, output.ToArray());
@@ -335,11 +332,8 @@ public class ZipEncryptedDataDescriptorTests : TestBase
private static async ValueTask<byte[]> ReadAsync(IAsyncReader reader)
{
#if LEGACY_DOTNET
using var entryStream = await reader.OpenEntryStreamAsync();
#else
await using var entryStream = await reader.OpenEntryStreamAsync();
#endif
var entryStream = await reader.OpenEntryStreamAsync();
await using var scope = entryStream.DisposeAsyncScope().ConfigureAwait(false);
using var output = new MemoryStream();
await entryStream.CopyToAsync(output);
return output.ToArray();

View File

@@ -200,11 +200,8 @@ public class ZipWinzipAesReaderTests : TestBase
continue;
}
#if LEGACY_DOTNET
using var entryStream = await entry.OpenEntryStreamAsync();
#else
await using var entryStream = await entry.OpenEntryStreamAsync();
#endif
var entryStream = await entry.OpenEntryStreamAsync();
await using var scope = entryStream.DisposeAsyncScope().ConfigureAwait(false);
Assert.Equal(expected, await ReadInChunksAsync(entryStream, bufferSize));
}
}
@@ -303,11 +300,8 @@ public class ZipWinzipAesReaderTests : TestBase
private static async ValueTask<byte[]> ReadAsync(IAsyncReader reader)
{
#if LEGACY_DOTNET
using var entryStream = await reader.OpenEntryStreamAsync();
#else
await using var entryStream = await reader.OpenEntryStreamAsync();
#endif
var entryStream = await reader.OpenEntryStreamAsync();
await using var scope = entryStream.DisposeAsyncScope().ConfigureAwait(false);
using var output = new MemoryStream();
await entryStream.CopyToAsync(output);
return output.ToArray();