Reduce some allocations by pooling or better disposal

This commit is contained in:
Adam Hathcock
2026-05-28 05:53:01 +01:00
parent aa34c22f39
commit 3ddfaaa899
12 changed files with 229 additions and 122 deletions

View File

@@ -1,6 +1,7 @@
#nullable disable
using System;
using System.Buffers;
using System.IO;
namespace SharpCompress.Compressors.LZMA.LZ;
@@ -108,7 +109,12 @@ internal sealed class BinTree : InWindow
var cyclicBufferSize = historySize + 1;
if (_cyclicBufferSize != cyclicBufferSize)
{
_son = new uint[(_cyclicBufferSize = cyclicBufferSize) * 2];
if (_son is not null)
{
ArrayPool<uint>.Shared.Return(_son);
}
_cyclicBufferSize = cyclicBufferSize;
_son = ArrayPool<uint>.Shared.Rent(checked((int)(_cyclicBufferSize * 2)));
}
var hs = K_BT2_HASH_SIZE;
@@ -132,7 +138,27 @@ internal sealed class BinTree : InWindow
}
if (hs != _hashSizeSum)
{
_hash = new uint[_hashSizeSum = hs];
if (_hash is not null)
{
ArrayPool<uint>.Shared.Return(_hash);
}
_hashSizeSum = hs;
_hash = ArrayPool<uint>.Shared.Rent(checked((int)_hashSizeSum));
}
}
public override void Dispose()
{
base.Dispose();
if (_son is not null)
{
ArrayPool<uint>.Shared.Return(_son);
_son = null;
}
if (_hash is not null)
{
ArrayPool<uint>.Shared.Return(_hash);
_hash = null;
}
}

View File

@@ -1,10 +1,12 @@
#nullable disable
using System;
using System.Buffers;
using System.IO;
namespace SharpCompress.Compressors.LZMA.LZ;
internal class InWindow
internal class InWindow : IDisposable
{
public byte[] _bufferBase; // pointer to buffer with data
private Stream _stream;
@@ -78,7 +80,22 @@ internal class InWindow
}
}
private void Free() => _bufferBase = null;
private void Free()
{
if (_bufferBase is null)
{
return;
}
ArrayPool<byte>.Shared.Return(_bufferBase);
_bufferBase = null;
}
public virtual void Dispose()
{
ReleaseStream();
Free();
}
public void Create(uint keepSizeBefore, uint keepSizeAfter, uint keepSizeReserv)
{
@@ -89,7 +106,7 @@ internal class InWindow
{
Free();
_blockSize = blockSize;
_bufferBase = new byte[_blockSize];
_bufferBase = ArrayPool<byte>.Shared.Rent(checked((int)_blockSize));
}
_pointerToLastSafePosition = _blockSize - keepSizeAfter;
_streamEndWasReached = false;

View File

@@ -126,31 +126,36 @@ public sealed partial class LZipStream
)
{
// Read the header
byte[] header = new byte[6];
var n = await stream
.ReadAsync(header, 0, header.Length, cancellationToken)
.ConfigureAwait(false);
// TODO: Handle reading only part of the header?
if (n != 6)
var header = ArrayPool<byte>.Shared.Rent(6);
try
{
return 0;
}
var n = await stream.ReadAsync(header, 0, 6, cancellationToken).ConfigureAwait(false);
if (
header[0] != 'L'
|| header[1] != 'Z'
|| header[2] != 'I'
|| header[3] != 'P'
|| header[4] != 1 /* version 1 */
)
{
return 0;
// TODO: Handle reading only part of the header?
if (n != 6)
{
return 0;
}
if (
header[0] != 'L'
|| header[1] != 'Z'
|| header[2] != 'I'
|| header[3] != 'P'
|| header[4] != 1 /* version 1 */
)
{
return 0;
}
var basePower = header[5] & 0x1F;
var subtractionNumerator = (header[5] & 0xE0) >> 5;
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
}
finally
{
ArrayPool<byte>.Shared.Return(header);
}
var basePower = header[5] & 0x1F;
var subtractionNumerator = (header[5] & 0xE0) >> 5;
return (1 << basePower) - (subtractionNumerator * (1 << (basePower - 4)));
}
#if !LEGACY_DOTNET

View File

@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
@@ -26,11 +27,12 @@ internal sealed class Lzma2EncoderStream : Stream
private readonly int _dictionarySize;
private readonly int _numFastBytes;
private readonly byte[] _buffer;
private readonly byte[] _properties;
private readonly Encoder _encoder;
private int _bufferPosition;
private bool _isFirstChunk = true;
private bool _isDisposed;
private byte _lzmaPropertiesByte;
private bool _lzmaPropertiesKnown;
/// <summary>
/// Creates a new LZMA2 encoder stream.
@@ -43,14 +45,23 @@ internal sealed class Lzma2EncoderStream : Stream
_output = output;
_dictionarySize = dictionarySize;
_numFastBytes = numFastBytes;
_buffer = new byte[MAX_UNCOMPRESSED_CHUNK_SIZE];
_buffer = ArrayPool<byte>.Shared.Rent(MAX_UNCOMPRESSED_CHUNK_SIZE);
_bufferPosition = 0;
var encoderProps = new LzmaEncoderProperties(eos: false, _dictionarySize, _numFastBytes);
_encoder = new Encoder();
_encoder.SetCoderProperties(encoderProps.PropIDs, encoderProps.Properties);
Span<byte> lzmaProperties = stackalloc byte[5];
_encoder.WriteCoderProperties(lzmaProperties);
_lzmaPropertiesByte = lzmaProperties[0];
_properties = [EncodeDictionarySize(_dictionarySize)];
}
/// <summary>
/// Gets the 1-byte LZMA2 properties (encoded dictionary size).
/// </summary>
public byte[] Properties => [EncodeDictionarySize(_dictionarySize)];
public byte[] Properties => _properties;
public override bool CanRead => false;
public override bool CanSeek => false;
@@ -153,6 +164,8 @@ internal sealed class Lzma2EncoderStream : Stream
// Write LZMA2 end marker
_output.WriteByte(0x00);
_encoder.Dispose();
ArrayPool<byte>.Shared.Return(_buffer);
}
base.Dispose(disposing);
}
@@ -179,6 +192,9 @@ internal sealed class Lzma2EncoderStream : Stream
#else
await _output.WriteAsync(_endMarker, 0, 1).ConfigureAwait(false);
#endif
_encoder.Dispose();
ArrayPool<byte>.Shared.Return(_buffer);
}
#if !LEGACY_DOTNET || NETSTANDARD2_1
@@ -193,14 +209,15 @@ internal sealed class Lzma2EncoderStream : Stream
return;
}
var uncompressedData = _buffer.AsSpan(0, _bufferPosition);
var uncompressedSize = _bufferPosition;
var uncompressedData = _buffer.AsSpan(0, uncompressedSize);
_bufferPosition = 0;
// Try compressing the data
byte[] compressed;
(byte[] Buffer, int Length) compressed;
try
{
compressed = CompressBlock(uncompressedData);
compressed = CompressBlock(_buffer, uncompressedSize);
}
catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException)
{
@@ -215,7 +232,7 @@ internal sealed class Lzma2EncoderStream : Stream
&& compressed.Length < uncompressedData.Length
)
{
WriteCompressedChunk(uncompressedData.Length, compressed);
WriteCompressedChunk(uncompressedData.Length, compressed.Buffer, compressed.Length);
}
else
{
@@ -230,13 +247,14 @@ internal sealed class Lzma2EncoderStream : Stream
return;
}
var uncompressedData = _buffer.AsMemory(0, _bufferPosition);
var uncompressedSize = _bufferPosition;
var uncompressedData = _buffer.AsMemory(0, uncompressedSize);
_bufferPosition = 0;
byte[] compressed;
(byte[] Buffer, int Length) compressed;
try
{
compressed = CompressBlock(uncompressedData.Span);
compressed = CompressBlock(_buffer, uncompressedSize);
}
catch (Exception ex) when (ex is not OutOfMemoryException and not StackOverflowException)
{
@@ -250,7 +268,12 @@ internal sealed class Lzma2EncoderStream : Stream
&& compressed.Length < uncompressedData.Length
)
{
await WriteCompressedChunkAsync(uncompressedData.Length, compressed, cancellationToken)
await WriteCompressedChunkAsync(
uncompressedData.Length,
compressed.Buffer,
compressed.Length,
cancellationToken
)
.ConfigureAwait(false);
}
else
@@ -260,45 +283,26 @@ internal sealed class Lzma2EncoderStream : Stream
}
}
private byte[] CompressBlock(ReadOnlySpan<byte> data)
private (byte[] Buffer, int Length) CompressBlock(byte[] data, int length)
{
var encoderProps = new LzmaEncoderProperties(eos: false, _dictionarySize, _numFastBytes);
var encoder = new Encoder();
encoder.SetCoderProperties(encoderProps.PropIDs, encoderProps.Properties);
// Capture the LZMA properties byte (pb/lp/lc encoding) for the chunk header
if (!_lzmaPropertiesKnown)
{
var propBytes = new byte[5];
encoder.WriteCoderProperties(propBytes);
_lzmaPropertiesByte = propBytes[0];
_lzmaPropertiesKnown = true;
}
using var inputMs = new MemoryStream(data.ToArray(), writable: false);
using var inputMs = new MemoryStream(data, 0, length, writable: false);
using var outputMs = new PooledMemoryStream();
encoder.Code(inputMs, outputMs, data.Length, -1, null);
_encoder.Code(inputMs, outputMs, length, -1, null);
var fullCompressed = outputMs.ToArray();
// The LZMA range encoder flush writes trailing bytes the decoder doesn't consume.
// Trial-decode to find the exact byte count the decoder needs, so the LZMA2
// chunk header reports a compressed size that matches what the decoder reads.
var consumed = FindConsumedBytes(fullCompressed, data.Length);
if (consumed < fullCompressed.Length)
{
return fullCompressed.AsSpan(0, consumed).ToArray();
}
return fullCompressed;
var consumed = FindConsumedBytes(fullCompressed, fullCompressed.Length, length);
return (fullCompressed, consumed);
}
private int FindConsumedBytes(byte[] compressedData, int uncompressedSize)
private int FindConsumedBytes(byte[] compressedData, int compressedSize, int uncompressedSize)
{
// Build 5-byte LZMA property header: [pb/lp/lc byte] [dictSize as LE int32]
var props = new byte[5];
Span<byte> props = stackalloc byte[5];
props[0] = _lzmaPropertiesByte;
props[1] = (byte)_dictionarySize;
props[2] = (byte)(_dictionarySize >> 8);
@@ -308,9 +312,8 @@ internal sealed class Lzma2EncoderStream : Stream
var decoder = new Decoder();
decoder.SetDecoderProperties(props);
using var input = new MemoryStream(compressedData);
using var output = new PooledMemoryStream();
decoder.Code(input, output, compressedData.Length, uncompressedSize, null);
using var input = new MemoryStream(compressedData, 0, compressedSize, writable: false);
decoder.Code(input, Stream.Null, compressedSize, uncompressedSize, null);
return (int)input.Position;
}
@@ -319,10 +322,14 @@ internal sealed class Lzma2EncoderStream : Stream
/// Writes a compressed LZMA2 chunk.
/// Header: [control] [uncompSize_hi] [uncompSize_lo] [compSize_hi] [compSize_lo] [props?]
/// </summary>
private void WriteCompressedChunk(int uncompressedSize, byte[] compressedData)
private void WriteCompressedChunk(
int uncompressedSize,
byte[] compressedData,
int compressedSize
)
{
var uncompSizeMinus1 = uncompressedSize - 1;
var compSizeMinus1 = compressedData.Length - 1;
var compSizeMinus1 = compressedSize - 1;
// Each chunk is compressed independently with a fresh LZMA encoder,
// so we must use 0xE0 (full reset: dictionary + state + properties) every time.
@@ -341,32 +348,38 @@ internal sealed class Lzma2EncoderStream : Stream
// 0xE0 (>= 0xC0) requires properties byte
_output.WriteByte(_lzmaPropertiesByte);
_output.Write(compressedData, 0, compressedData.Length);
_output.Write(compressedData, 0, compressedSize);
}
private async ValueTask WriteCompressedChunkAsync(
int uncompressedSize,
byte[] compressedData,
int compressedSize,
CancellationToken cancellationToken = default
)
{
var uncompSizeMinus1 = uncompressedSize - 1;
var compSizeMinus1 = compressedData.Length - 1;
var compSizeMinus1 = compressedSize - 1;
var control = (byte)(0xE0 | ((uncompSizeMinus1 >> 16) & 0x1F));
_isFirstChunk = false;
var header = new[]
var header = ArrayPool<byte>.Shared.Rent(6);
try
{
control,
(byte)((uncompSizeMinus1 >> 8) & 0xFF),
(byte)(uncompSizeMinus1 & 0xFF),
(byte)((compSizeMinus1 >> 8) & 0xFF),
(byte)(compSizeMinus1 & 0xFF),
_lzmaPropertiesByte,
};
await _output.WriteAsync(header, 0, header.Length, cancellationToken).ConfigureAwait(false);
header[0] = control;
header[1] = (byte)((uncompSizeMinus1 >> 8) & 0xFF);
header[2] = (byte)(uncompSizeMinus1 & 0xFF);
header[3] = (byte)((compSizeMinus1 >> 8) & 0xFF);
header[4] = (byte)(compSizeMinus1 & 0xFF);
header[5] = _lzmaPropertiesByte;
await _output.WriteAsync(header, 0, 6, cancellationToken).ConfigureAwait(false);
}
finally
{
ArrayPool<byte>.Shared.Return(header);
}
await _output
.WriteAsync(compressedData, 0, compressedData.Length, cancellationToken)
.WriteAsync(compressedData, 0, compressedSize, cancellationToken)
.ConfigureAwait(false);
}
@@ -426,20 +439,37 @@ internal sealed class Lzma2EncoderStream : Stream
control = 0x02;
}
var header = new[]
var header = ArrayPool<byte>.Shared.Rent(3);
try
{
control,
(byte)((sizeMinus1 >> 8) & 0xFF),
(byte)(sizeMinus1 & 0xFF),
};
await _output
.WriteAsync(header, 0, header.Length, cancellationToken)
.ConfigureAwait(false);
header[0] = control;
header[1] = (byte)((sizeMinus1 >> 8) & 0xFF);
header[2] = (byte)(sizeMinus1 & 0xFF);
await _output.WriteAsync(header, 0, 3, cancellationToken).ConfigureAwait(false);
}
finally
{
ArrayPool<byte>.Shared.Return(header);
}
var chunk = data.Slice(offset, chunkSize).ToArray();
#if !LEGACY_DOTNET || NETSTANDARD2_1
await _output
.WriteAsync(chunk, 0, chunk.Length, cancellationToken)
.WriteAsync(data.Slice(offset, chunkSize), cancellationToken)
.ConfigureAwait(false);
#else
var chunk = ArrayPool<byte>.Shared.Rent(chunkSize);
try
{
data.Slice(offset, chunkSize).CopyTo(chunk);
await _output
.WriteAsync(chunk, 0, chunkSize, cancellationToken)
.ConfigureAwait(false);
}
finally
{
ArrayPool<byte>.Shared.Return(chunk);
}
#endif
offset += chunkSize;
}
}

View File

@@ -446,7 +446,10 @@ public partial class Decoder : ICoder, ISetDecoderProperties, IDisposable
return false;
}
public void SetDecoderProperties(byte[] properties)
public void SetDecoderProperties(byte[] properties) =>
SetDecoderProperties(properties.AsSpan());
internal void SetDecoderProperties(ReadOnlySpan<byte> properties)
{
if (properties.Length < 1)
{

View File

@@ -10,7 +10,7 @@ using SharpCompress.Compressors.LZMA.RangeCoder;
namespace SharpCompress.Compressors.LZMA;
internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties
internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderProperties, IDisposable
{
private enum EMatchFinderType
{
@@ -574,6 +574,12 @@ internal partial class Encoder : ICoder, ISetCoderProperties, IWriteCoderPropert
}
}
public void Dispose()
{
_matchFinder?.Dispose();
_matchFinder = null;
}
private void SetWriteEndMarkerMode(bool writeEndMarker) => _writeEndMark = writeEndMarker;
private void Init()

View File

@@ -83,11 +83,11 @@ public partial class LzmaStream
private async ValueTask DecodeChunkHeaderAsync(CancellationToken cancellationToken = default)
{
var controlBuffer = new byte[1];
var headerBuffer = GetAsyncHeaderBuffer();
await _inputStream!
.ReadExactAsync(controlBuffer, 0, 1, cancellationToken)
.ReadExactAsync(headerBuffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
var control = controlBuffer[0];
var control = headerBuffer[0];
_inputPosition++;
if (control == 0x00)
@@ -112,26 +112,25 @@ public partial class LzmaStream
_uncompressedChunk = false;
_availableBytes = (control & 0x1F) << 16;
var buffer = new byte[2];
await _inputStream!
.ReadExactAsync(buffer, 0, 2, cancellationToken)
.ReadExactAsync(headerBuffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_availableBytes += (buffer[0] << 8) + buffer[1] + 1;
_availableBytes += (headerBuffer[0] << 8) + headerBuffer[1] + 1;
_inputPosition += 2;
await _inputStream!
.ReadExactAsync(buffer, 0, 2, cancellationToken)
.ReadExactAsync(headerBuffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_rangeDecoderLimit = (buffer[0] << 8) + buffer[1] + 1;
_rangeDecoderLimit = (headerBuffer[0] << 8) + headerBuffer[1] + 1;
_inputPosition += 2;
if (control >= 0xC0)
{
_needProps = false;
await _inputStream!
.ReadExactAsync(controlBuffer, 0, 1, cancellationToken)
.ReadExactAsync(headerBuffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
Properties[0] = controlBuffer[0];
Properties[0] = headerBuffer[0];
_inputPosition++;
_decoder = new Decoder();
@@ -156,11 +155,10 @@ public partial class LzmaStream
else
{
_uncompressedChunk = true;
var buffer = new byte[2];
await _inputStream!
.ReadExactAsync(buffer, 0, 2, cancellationToken)
.ReadExactAsync(headerBuffer, 0, 2, cancellationToken)
.ConfigureAwait(false);
_availableBytes = (buffer[0] << 8) + buffer[1] + 1;
_availableBytes = (headerBuffer[0] << 8) + headerBuffer[1] + 1;
_inputPosition += 2;
}
}
@@ -436,6 +434,7 @@ public partial class LzmaStream
if (_encoder != null)
{
_position = await _encoder.CodeAsync(null, true).ConfigureAwait(false);
_encoder.Dispose();
}
if (!_leaveOpen)
@@ -450,6 +449,7 @@ public partial class LzmaStream
}
}
await _outWindow.DisposeAsync().ConfigureAwait(false);
ReturnAsyncHeaderBuffer();
#if !LEGACY_DOTNET || NETSTANDARD2_1
await base.DisposeAsync().ConfigureAwait(false);

View File

@@ -1,4 +1,5 @@
using System;
using System.Buffers;
using System.Buffers.Binary;
using System.IO;
using System.Threading;
@@ -33,6 +34,7 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
private bool _needProps = true;
private readonly Encoder? _encoder;
private byte[]? _asyncHeaderBuffer;
private bool _isDisposed;
private LzmaStream(
@@ -205,16 +207,31 @@ public partial class LzmaStream : Stream, IStreamStack, IAsyncDisposable
if (_encoder != null)
{
_position = _encoder.Code(null, true);
_encoder.Dispose();
}
if (!_leaveOpen)
{
_inputStream?.Dispose();
}
_outWindow.Dispose();
ReturnAsyncHeaderBuffer();
}
base.Dispose(disposing);
}
private byte[] GetAsyncHeaderBuffer() => _asyncHeaderBuffer ??= ArrayPool<byte>.Shared.Rent(6);
private void ReturnAsyncHeaderBuffer()
{
if (_asyncHeaderBuffer is null)
{
return;
}
ArrayPool<byte>.Shared.Return(_asyncHeaderBuffer);
_asyncHeaderBuffer = null;
}
public override long Length => _position + _availableBytes;
public override long Position

View File

@@ -9,6 +9,8 @@ namespace SharpCompress.Compressors.LZMA.RangeCoder;
internal partial class Encoder
{
private byte[] SingleByteBuffer => _singleByteBuffer ??= new byte[1];
public async ValueTask ShiftLowAsync(CancellationToken cancellationToken = default)
{
if ((uint)_low < 0xFF000000 || (uint)(_low >> 32) == 1)
@@ -17,7 +19,8 @@ internal partial class Encoder
do
{
var b = (byte)(temp + (_low >> 32));
var buffer = new[] { b };
var buffer = SingleByteBuffer;
buffer[0] = b;
await _stream.WriteAsync(buffer, 0, 1, cancellationToken).ConfigureAwait(false);
temp = 0xFF;
} while (--_cacheSize != 0);
@@ -78,13 +81,15 @@ internal partial class Encoder
internal partial class Decoder
{
private byte[] SingleByteBuffer => _singleByteBuffer ??= new byte[1];
public async ValueTask InitAsync(Stream stream, CancellationToken cancellationToken = default)
{
_stream = stream;
_code = 0;
_range = 0xFFFFFFFF;
var buffer = new byte[1];
var buffer = SingleByteBuffer;
for (var i = 0; i < 5; i++)
{
var read = await _stream
@@ -103,7 +108,7 @@ internal partial class Decoder
{
while (_range < K_TOP_VALUE)
{
var buffer = new byte[1];
var buffer = SingleByteBuffer;
var read = await _stream
.ReadAsync(buffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
@@ -121,7 +126,7 @@ internal partial class Decoder
{
if (_range < K_TOP_VALUE)
{
var buffer = new byte[1];
var buffer = SingleByteBuffer;
var read = await _stream
.ReadAsync(buffer, 0, 1, cancellationToken)
.ConfigureAwait(false);
@@ -143,7 +148,7 @@ internal partial class Decoder
var range = _range;
var code = _code;
uint result = 0;
var buffer = new byte[1];
var buffer = SingleByteBuffer;
for (var i = numTotalBits; i > 0; i--)
{
range >>= 1;

View File

@@ -15,8 +15,7 @@ internal partial class Encoder
public uint _range;
private uint _cacheSize;
private byte _cache;
//long StartPosition;
private byte[] _singleByteBuffer;
public void SetStream(Stream stream) => _stream = stream;
@@ -88,6 +87,7 @@ internal partial class Decoder
public Stream _stream;
public long _total;
private byte[] _singleByteBuffer;
public void Init(Stream stream)
{
@@ -180,6 +180,4 @@ internal partial class Decoder
}
public bool IsFinished => _code == 0;
// ulong GetProcessedSize() {return Stream.GetProcessedSize(); }
}

View File

@@ -286,9 +286,9 @@
"net10.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[10.0.8, )",
"resolved": "10.0.8",
"contentHash": "dVbSXGIFNR5nZcv2tOLoWI+a9T4jtFd77IYjuND+QVe360qWgAF7H0WtoopYhRw/+SgpGUTyrkrh+65+ClNnfw=="
"requested": "[10.0.6, )",
"resolved": "10.0.6",
"contentHash": "QKuvS0LWX4fjFqeDkyM7Kqt8P3wYTiPD4nwU+9y59n0sCiG714fxDgbbN82vDnzq89AF/PiHl92TP2C4aFDUQA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",
@@ -388,9 +388,9 @@
"net8.0": {
"Microsoft.NET.ILLink.Tasks": {
"type": "Direct",
"requested": "[8.0.27, )",
"resolved": "8.0.27",
"contentHash": "rQi9TxifHRnXP7lVRZH05DxD2/XGbJp12q0ozcbrlBlBnyyzssFTH/2vLhtKWUp2CT1qVscTrcYTFiwTyKPKRg=="
"requested": "[8.0.26, )",
"resolved": "8.0.26",
"contentHash": "o7/yVssM2r9Wyln2s9edBd5ANZXqdSdBI+g7JqXkyJmXrhs2WsJp25K5yPnYrTgdKBCjKB8bg+O2oew4sgzFaA=="
},
"Microsoft.NETFramework.ReferenceAssemblies": {
"type": "Direct",