I am building some classes to serialize (in an async
/ await
way) potentially pretty long IEnumerable<T>
over the network via the Stream
class, I am wondering whether my code is easily readable et maintainable.
For example one purpose is that the serializer classes can be used in some ASP.NET controllers and write to the output stream a lot of data without wasting too many threads (hence the async
/ await
approach).
In my example of implementation the item serialization leverages the ServiceStack.Text
serializers.
using System;
using System.Collections.Generic;
using System.IO;
using System.Numerics;
using System.Text;
using System.Threading.Tasks;
using ServiceStack;
namespace ConsoleApp
{
public static class Program
{
public static async Task Main(params string[] args)
{
var source = new[] {1, 2 ,3, 4};
var serializer = new JsonLineBasedEnumerableStreamSerializer();
using (var stream = new MemoryStream())
{
await serializer.SerializeToStreamAsync(source, stream, 1024, true);
stream.Position = 0;
using (var streamReader = new StreamReader(stream))
{
var serialization = streamReader.ReadToEnd();
Console.WriteLine(serialization);
}
}
}
}
public static class EnumerableExtensions
{
public static IEnumerable<Iteration<TSource>> Detailed<TSource>(this IEnumerable<TSource> source)
{
using (var enumerator = source.GetEnumerator())
{
var isFirst = true;
var hasNext = enumerator.MoveNext();
var index = BigInteger.Zero;
while (hasNext)
{
var current = enumerator.Current;
hasNext = enumerator.MoveNext();
yield return new Iteration<TSource>(index, current, isFirst, !hasNext);
isFirst = false;
index++;
}
}
}
}
public readonly struct Iteration<T>
{
public readonly BigInteger Index;
public readonly bool IsFirst;
public readonly bool IsLast;
public readonly T Value;
public Iteration(BigInteger index, T value, bool isFirst, bool isLast)
{
Index = index;
IsFirst = isFirst;
IsLast = isLast;
Value = value;
}
}
public interface IEnumerableStreamSerializer
{
Task SerializeToStreamAsync<T>(IEnumerable<T> source, Stream stream, ushort bufferSize = 1024, bool leaveOpen = false);
}
public abstract class EnumerableStreamSerializer : IEnumerableStreamSerializer
{
public abstract Task SerializeToStreamAsync<T>(IEnumerable<T> source, Stream stream, ushort bufferSize = 1024, bool leaveOpen = false);
}
public abstract class LineBasedEnumerableStreamSerializer : EnumerableStreamSerializer
{
public async Task SerializeToStreamAsync<T>(IEnumerable<T> source, Stream stream, Encoding encoding, ushort bufferSize = 1024, bool leaveOpen = false)
{
using (var streamWriter = new StreamWriter(stream, encoding, bufferSize, leaveOpen))
{
var firstLine = await GetFirstLineAsync();
await streamWriter.WriteLineAsync(firstLine);
foreach (var iteration in source.Detailed())
{
var itemLine = await GetItemLineAsync(iteration.Index, iteration.Value, iteration.IsFirst, iteration.IsLast);
await streamWriter.WriteLineAsync(itemLine);
}
var lastLine = await GetLastLineAsync();
await streamWriter.WriteLineAsync(lastLine);
}
}
public override async Task SerializeToStreamAsync<T>(IEnumerable<T> source, Stream stream, ushort bufferSize = 1024, bool leaveOpen = false)
{
await SerializeToStreamAsync(source, stream, Encoding.Default, bufferSize, leaveOpen);
}
protected abstract Task<string> GetFirstLineAsync();
protected abstract Task<string> GetItemLineAsync<T>(BigInteger index, T item, bool isFirst, bool isLast);
protected abstract Task<string> GetLastLineAsync();
}
public class JsonLineBasedEnumerableStreamSerializer : LineBasedEnumerableStreamSerializer
{
protected override async Task<string> GetFirstLineAsync() =>
"[";
protected override async Task<string> GetItemLineAsync<T>(BigInteger index, T item, bool isFirst, bool isLast)
{
var result = $" {item.ToJson()}";
return !isLast
? $"{result},"
: result;
}
protected override async Task<string> GetLastLineAsync() =>
"]";
}
public class XmlLineBasedEnumerableStreamSerializer : LineBasedEnumerableStreamSerializer
{
protected override async Task<string> GetFirstLineAsync() =>
"<Array>";
protected override async Task<string> GetItemLineAsync<T>(BigInteger index, T item, bool isFirst, bool isLast) =>
$" <Item>{item.ToXml()}</Item>";
protected override async Task<string> GetLastLineAsync() =>
"</Array>";
}
}
What do you think, is there anything I can improve in my implementation to make it better?