C# 将char[]缓冲区传递给XmlSerializer

C# 将char[]缓冲区传递给XmlSerializer,c#,xmlserializer,C#,Xmlserializer,我有一个存储在char数组中的XML-char[],我有一个int变量中的数据内容长度。我需要用XmlSerializer反序列化数据 出于性能方面的原因,我需要避免分配字符串对象,因为数据通常大于85kb,并且会导致Gen2对象 有没有办法将char[]传递给XmlSerializer,而不将其转换为字符串?它接受流或文本阅读器,但我找不到从char[]构造一个的方法 我在想象这样的事情(除了C#没有Chararray流或Chararray Reader): 正如更多信息一样,我们在分析现有代

我有一个存储在char数组中的XML-
char[]
,我有一个int变量中的数据内容长度。我需要用XmlSerializer反序列化数据

出于性能方面的原因,我需要避免分配字符串对象,因为数据通常大于85kb,并且会导致Gen2对象

有没有办法将char[]传递给
XmlSerializer
,而不将其转换为字符串?它接受
文本阅读器
,但我找不到从
char[]
构造一个的方法

我在想象这样的事情(除了C#没有Chararray流或Chararray Reader):


正如更多信息一样,我们在分析现有代码时发现了一个难点,因此这不是“过早优化”或“XY问题”。

我重新编写了@György Kőszeg链接到类ChararyStream的代码。到目前为止,这在我的测试中有效:

public class CharArrayStream : Stream
{
    private readonly char[] str;
    private readonly int n;

    public override bool CanRead => true;
    public override bool CanSeek => true;
    public override bool CanWrite => false;
    public override long Length => n;
    public override long Position { get; set; } // TODO: bounds check

    public CharArrayStream(char[] str, int n)
    {
        this.str = str;
        this.n = n;
    }

    public override long Seek(long offset, SeekOrigin origin)
    {
        switch (origin)
        {
            case SeekOrigin.Begin:
                Position = offset;
                break;
            case SeekOrigin.Current:
                Position += offset;
                break;
            case SeekOrigin.End:
                Position = Length - offset;
                break;
        }

        return Position;
    }

    private byte this[int i] => (byte)str[i];

    public override int Read(byte[] buffer, int offset, int count)
    {
        // TODO: bounds check
        var len = Math.Min(count, Length - Position);
        for (int i = 0; i < len; i++)
        {
            buffer[offset++] = this[(int)(Position++)];
        }
        return (int)len;
    }

    public override int ReadByte() => Position >= Length ? -1 : this[(int)Position++];
    public override void Flush() { }
    public override void SetLength(long value) => throw new NotSupportedException();
    public override void Write(byte[] buffer, int offset, int count) => throw new NotSupportedException();
    public override string ToString() => throw new NotSupportedException();
}

谢谢,@György Kőszeg

TextReader
子类化以读取字符数组或等效字符非常简单。这是一个采用a的版本,它可以表示
字符串
char[]
字符数组的一部分:

public sealed class CharMemoryReader : TextReader
{
    private ReadOnlyMemory<char> chars;
    private int position;

    public CharMemoryReader(ReadOnlyMemory<char> chars)
    {
        this.chars = chars;
        this.position = 0;
    }

    void CheckClosed()
    {
        if (position < 0)
            throw new ObjectDisposedException(null, string.Format("{0} is closed.", ToString()));
    }

    public override void Close() => Dispose(true);

    protected override void Dispose(bool disposing)
    {
        chars = ReadOnlyMemory<char>.Empty;
        position = -1;
        base.Dispose(disposing);
    }

    public override int Peek()
    {
        CheckClosed();
        return position >= chars.Length ? -1 : chars.Span[position];
    }

    public override int Read()
    {
        CheckClosed();
        return position >= chars.Length ? -1 : chars.Span[position++];
    }

    public override int Read(char[] buffer, int index, int count)
    {
        CheckClosed();
        if (buffer == null)
            throw new ArgumentNullException(nameof(buffer));
        if (index < 0)
            throw new ArgumentOutOfRangeException(nameof(index));
        if (count < 0)
            throw new ArgumentOutOfRangeException(nameof(count));
        if (buffer.Length - index < count)
            throw new ArgumentException("buffer.Length - index < count");

        return Read(buffer.AsSpan().Slice(index, count));
    }

    public override int Read(Span<char> buffer)
    {
        CheckClosed();

        var nRead = chars.Length - position;
        if (nRead > 0)
        {
            if (nRead > buffer.Length)
                nRead = buffer.Length;
            chars.Span.Slice(position, nRead).CopyTo(buffer);
            position += nRead;
        }
        return nRead;
    }

    public override string ReadToEnd()
    {
        CheckClosed();
        var s = position == 0 ? chars.ToString() : chars.Slice(position, chars.Length - position).ToString();
        position = chars.Length;
        return s;
    }

    public override string ReadLine()
    {
        CheckClosed();
        var span = chars.Span;
        var i = position;
        for( ; i < span.Length; i++)
        {
            var ch = span[i];
            if (ch == '\r' || ch == '\n')
            {
                var result = span.Slice(position, i - position).ToString();
                position = i + 1;
                if (ch == '\r' && position < span.Length && span[position] == '\n')
                    position++;
                return result;
            }
        }
        if (i > position)
        {
            var result = span.Slice(position, i - position).ToString();
            position = i;
            return result;
        }
        return null;
    }

    public override int ReadBlock(char[] buffer, int index, int count) => Read(buffer, index, count);
    public override int ReadBlock(Span<char> buffer) => Read(buffer);

    public override Task<String> ReadLineAsync() => Task.FromResult(ReadLine());
    public override Task<String> ReadToEndAsync() => Task.FromResult(ReadToEnd());
    public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(ReadBlock(buffer, index, count));
    public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(Read(buffer, index, count));
    public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
        cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(ReadBlock(buffer.Span));
    public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
        cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(Read(buffer.Span)); 
}
公共密封类CharMemoryReader:TextReader
{
私人只读存储器;
私人职位;
公共角色MemoryReader(只读内存角色)
{
this.chars=chars;
这个位置=0;
}
void CheckClosed()
{
如果(位置<0)
抛出新的ObjectDisposedException(null,string.Format(“{0}已关闭。”,ToString());
}
public override void Close()=>Dispose(true);
受保护的覆盖无效处置(布尔处置)
{
chars=ReadOnlyMemory.Empty;
位置=-1;
基地。处置(处置);
}
公共覆盖int Peek()
{
CheckClosed();
返回位置>=字符长度?-1:字符跨度[位置];
}
公共覆盖int Read()
{
CheckClosed();
返回位置>=字符长度?-1:字符跨度[position++];
}
公共重写整型读取(字符[]缓冲区,整型索引,整型计数)
{
CheckClosed();
if(buffer==null)
抛出新ArgumentNullException(nameof(buffer));
如果(指数<0)
抛出新ArgumentOutOfRangeException(nameof(index));
如果(计数<0)
抛出新ArgumentOutOfRangeException(nameof(count));
if(buffer.Length-索引<计数)
抛出新的ArgumentException(“buffer.Length-index0)
{
如果(nRead>buffer.Length)
nRead=缓冲区长度;
字符跨度切片(位置,nRead).CopyTo(缓冲区);
位置+=nRead;
}
返回nRead;
}
公共重写字符串ReadToEnd()
{
CheckClosed();
var s=position==0?chars.ToString():chars.Slice(position,chars.Length-position).ToString();
位置=字符长度;
返回s;
}
公共重写字符串读取行()
{
CheckClosed();
var span=字符span;
var i=位置;
对于(;i位置)
{
var result=span.Slice(位置,i-position).ToString();
位置=i;
返回结果;
}
返回null;
}
公共重写int ReadBlock(char[]缓冲区,int索引,int计数)=>Read(缓冲区,索引,计数);
公共覆盖int ReadBlock(Span buffer)=>读取(buffer);
公共重写任务ReadLineSync()=>Task.FromResult(ReadLine());
公共重写任务ReadToEndAsync()=>Task.FromResult(ReadToEnd());
public override Task ReadBlockAsync(char[]buffer,int index,int count)=>Task.FromResult(ReadBlock(buffer,index,count));
public override Task ReadAsync(char[]buffer,int index,int count)=>Task.FromResult(Read(buffer,index,count));
public override ValueTask ReadBlockAsync(内存缓冲区,CancellationToken CancellationToken=default)=>
cancellationToken.IsCancellationRequested?新值任务(Task.FromCancelled(cancellationToken)):新值任务(ReadBlock(buffer.Span));
public override ValueTask ReadAsync(内存缓冲区,CancellationToken CancellationToken=default)=>
cancellationToken.IsCancellationRequested?新的ValueTask(Task.FromCancelled(cancellationToken)):新的ValueTask(Read(buffer.Span));
}
然后将其与以下扩展方法之一一起使用:

public static partial class XmlSerializationHelper
{
    public static T LoadFromXml<T>(this char [] xml, int contentLength, XmlSerializer serial = null) => 
        new ReadOnlyMemory<char>(xml, 0, contentLength).LoadFromXml<T>(serial);

    public static T LoadFromXml<T>(this ReadOnlyMemory<char> xml, XmlSerializer serial = null)
    {
        serial = serial ?? new XmlSerializer(typeof(T));
        using (var reader = new CharMemoryReader(xml))
            return (T)serial.Deserialize(reader);
    }
}
公共静态部分类XmlSerializationHelper
{
公共静态T LoadFromXml(此字符[]xml,int contentLength,XmlSerializer serial=null)=>
新的ReadOnlyMemory(xml,0,contentLength);
公共静态T LoadFromXml(此只读内存xml,XmlSerializer serial=null)
{
串行=串行??新的XmlSerializer(typeof(T));
使用(var reader=new CharMemoryReader(xml))
返回(T)串行。反序列化(读取器);
}
}
例如

var result=buffer.LoadFromXml(contentLength,\u xmlSerializer);
注:

  • char[]
    字符数组的内容基本上与不带字符的UTF-16编码内存流相同,因此可以创建一个自定义
    实现
    public sealed class CharMemoryReader : TextReader
    {
        private ReadOnlyMemory<char> chars;
        private int position;
    
        public CharMemoryReader(ReadOnlyMemory<char> chars)
        {
            this.chars = chars;
            this.position = 0;
        }
    
        void CheckClosed()
        {
            if (position < 0)
                throw new ObjectDisposedException(null, string.Format("{0} is closed.", ToString()));
        }
    
        public override void Close() => Dispose(true);
    
        protected override void Dispose(bool disposing)
        {
            chars = ReadOnlyMemory<char>.Empty;
            position = -1;
            base.Dispose(disposing);
        }
    
        public override int Peek()
        {
            CheckClosed();
            return position >= chars.Length ? -1 : chars.Span[position];
        }
    
        public override int Read()
        {
            CheckClosed();
            return position >= chars.Length ? -1 : chars.Span[position++];
        }
    
        public override int Read(char[] buffer, int index, int count)
        {
            CheckClosed();
            if (buffer == null)
                throw new ArgumentNullException(nameof(buffer));
            if (index < 0)
                throw new ArgumentOutOfRangeException(nameof(index));
            if (count < 0)
                throw new ArgumentOutOfRangeException(nameof(count));
            if (buffer.Length - index < count)
                throw new ArgumentException("buffer.Length - index < count");
    
            return Read(buffer.AsSpan().Slice(index, count));
        }
    
        public override int Read(Span<char> buffer)
        {
            CheckClosed();
    
            var nRead = chars.Length - position;
            if (nRead > 0)
            {
                if (nRead > buffer.Length)
                    nRead = buffer.Length;
                chars.Span.Slice(position, nRead).CopyTo(buffer);
                position += nRead;
            }
            return nRead;
        }
    
        public override string ReadToEnd()
        {
            CheckClosed();
            var s = position == 0 ? chars.ToString() : chars.Slice(position, chars.Length - position).ToString();
            position = chars.Length;
            return s;
        }
    
        public override string ReadLine()
        {
            CheckClosed();
            var span = chars.Span;
            var i = position;
            for( ; i < span.Length; i++)
            {
                var ch = span[i];
                if (ch == '\r' || ch == '\n')
                {
                    var result = span.Slice(position, i - position).ToString();
                    position = i + 1;
                    if (ch == '\r' && position < span.Length && span[position] == '\n')
                        position++;
                    return result;
                }
            }
            if (i > position)
            {
                var result = span.Slice(position, i - position).ToString();
                position = i;
                return result;
            }
            return null;
        }
    
        public override int ReadBlock(char[] buffer, int index, int count) => Read(buffer, index, count);
        public override int ReadBlock(Span<char> buffer) => Read(buffer);
    
        public override Task<String> ReadLineAsync() => Task.FromResult(ReadLine());
        public override Task<String> ReadToEndAsync() => Task.FromResult(ReadToEnd());
        public override Task<int> ReadBlockAsync(char[] buffer, int index, int count) => Task.FromResult(ReadBlock(buffer, index, count));
        public override Task<int> ReadAsync(char[] buffer, int index, int count) => Task.FromResult(Read(buffer, index, count));
        public override ValueTask<int> ReadBlockAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(ReadBlock(buffer.Span));
        public override ValueTask<int> ReadAsync(Memory<char> buffer, CancellationToken cancellationToken = default) =>
            cancellationToken.IsCancellationRequested ? new ValueTask<int>(Task.FromCanceled<int>(cancellationToken)) : new ValueTask<int>(Read(buffer.Span)); 
    }
    
    public static partial class XmlSerializationHelper
    {
        public static T LoadFromXml<T>(this char [] xml, int contentLength, XmlSerializer serial = null) => 
            new ReadOnlyMemory<char>(xml, 0, contentLength).LoadFromXml<T>(serial);
    
        public static T LoadFromXml<T>(this ReadOnlyMemory<char> xml, XmlSerializer serial = null)
        {
            serial = serial ?? new XmlSerializer(typeof(T));
            using (var reader = new CharMemoryReader(xml))
                return (T)serial.Deserialize(reader);
        }
    }
    
    var result = buffer.LoadFromXml<MyEntity>(contentLength, _xmlSerializer);