Подкласс TextReader
довольно просто читать из массива символов или его эквивалента. Вот версия, которая принимает ReadOnlyMemory<char>
, которая может представлять фрагмент массива символов string
или 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));
}
Затем используйте его с одним из следующие методы расширения:
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);
Примечания:
A char []
Массив символов имеет в основном то же содержимое, что и Поток памяти в кодировке UTF-16 без BOM , поэтому можно создать пользовательскую реализацию Stream
, напоминающую MemoryStream
, которая представляет каждый char
в виде двух байтов, как это делается в этот ответ на Как мне сгенерировать поток из строки? от György Kőszeg . Тем не менее, выглядит немного сложно сделать это правильно, поскольку правильное использование всех методов async
кажется нетривиальным.
После этого XmlReader
все равно потребуется обернуть пользовательский поток в StreamReader
, который «декодирует» поток в последовательность символов, правильно выводя кодировку в процессе (что, как я наблюдал, иногда может быть сделано неправильно, например, когда заявлено кодирование, объявление XML не соответствует фактическому кодированию).
Я решил создать пользовательский TextReader
, а не пользовательский Stream
, чтобы избежать ненужного шага декодирования, и поскольку реализация async
казалась менее обременительной.
Представление каждый char
в виде одного байта через усечение (например, (byte)str[i]
) повредит XML, содержащий любые многобайтовые символы.
Я не выполнял никаких настроек производительности в вышеупомянутой реализации .
Демонстрационная скрипка здесь .