我有一个Memory<byte>
,它保存XML元素的二进制数据。我想将XML元素读入XElement
对象,但这似乎比乍一看容易。使用byte[]
时,我会这样做:
public static XElement GetXElementFromByteArray(byte[] buffer)
{
using var stream = new MemoryStream(buffer);
using var xmlReader = XmlReader.Create(stream);
return XElement.Load(xmlReader);
}
不幸的是,我无法从Stream
或Memory<T>
对象创建Span<T>
对象,并且XmlReader
需要一个流对象。当然,我可以将Memory<byte>
转换为字节数组,但这将复制数据,而我的优化目标是减少集合。
答案 0 :(得分:1)
实际上并没有可以通过Stream
方便地使用的预先罐头XmlReader
实现(或Memory<byte>
实现)。如果目的是使用MemoryStream
,那么最好的办法是 try 并将其作为数组获取,而如果不是,则强制使用合并副本:
ArraySegment<byte> segment = default;
bool leased = false;
try
{
if (!MemoryMarshal.TryGetArray<byte>(memory, out segment))
{
var arr = ArrayPool<byte>.Shared.Rent(memory.Length);
memory.CopyTo(arr);
segment = new ArraySegment<byte>(arr, 0, memory.Length);
leased = true;
}
using (var ms = new MemoryStream(segment.Array, segment.Offset, segment.Count))
{
// ... your usage goes here!
using (var xmlReader = XmlReader.Create(ms))
{
return XElement.Load(xmlReader);
}
}
}
finally
{
if (leased) ArrayPool<byte>.Shared.Return(segment.Array);
}
另一种方法是创建一个新的Stream
实现,该实现可与Memory<byte>
配合使用。这是很多工作。
答案 1 :(得分:1)
我想出了一个自定义流,该流似乎有效,但是我想知道是否存在“官方”实现。我知道这仅适用于Memory<byte>
而不适用于通用类型,但是通常以1个字节为单位完成内存访问。
这是我的实现方式
public class ReadonlyMemByteStream : Stream
{
private readonly ReadOnlyMemory<byte> _buffer;
private int _offset;
public ReadonlyMemByteStream(ReadOnlyMemory<byte> buffer)
{
_buffer = buffer;
_offset = 0;
}
public override void Flush()
{
// NOP
}
public override int ReadByte()
{
return _buffer.Span[_offset++];
}
public override int Read(byte[] buffer, int offset, int count)
{
var bufferLength = _buffer.Length - _offset;
if (count > bufferLength)
count = bufferLength;
_buffer.Span.Slice(_offset).CopyTo(new Span<byte>(buffer, offset, count));
_offset += count;
return count;
}
public override void Write(byte[] buffer, int offset, int count)
{
throw new NotSupportedException();
}
public override long Seek(long offset, SeekOrigin origin)
{
switch (origin)
{
case SeekOrigin.Begin:
_offset = (int)offset;
break;
case SeekOrigin.Current:
_offset += (int)offset;
break;
case SeekOrigin.End:
_offset = _buffer.Length + (int)offset;
break;
default:
throw new ArgumentOutOfRangeException(nameof(origin), origin, null);
}
return _offset;
}
public override void SetLength(long value) => throw new NotSupportedException();
public override bool CanRead => true;
public override bool CanSeek => true;
public override bool CanWrite => false;
public override long Length => _buffer.Length;
public override long Position
{
get => _offset;
set => _offset = (int)value;
}
}
public class MemByteStream : ReadonlyMemByteStream
{
private readonly Memory<byte> _buffer;
public MemByteStream(Memory<byte> buffer) : base(buffer)
{
_buffer = buffer;
}
public override void Write(byte[] buffer, int offset, int count)
{
new Span<byte>(buffer, offset, count).CopyTo(_buffer.Span.Slice((int)Position));
Position += count;
}
public override bool CanWrite => true;
}