Skip to content

Instantly share code, notes, and snippets.

@glopesdev
Created May 29, 2023 21:39
Show Gist options
  • Select an option

  • Save glopesdev/d9a7c34376ed3cb562cfc8ef52439f41 to your computer and use it in GitHub Desktop.

Select an option

Save glopesdev/d9a7c34376ed3cb562cfc8ef52439f41 to your computer and use it in GitHub Desktop.
Simple binary writer serializer generator using expression trees
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq.Expressions;
using System.Reflection;
static class BinarySerializer
{
public static void Serialize<T>(BinaryWriter writer, T value)
{
BinarySerializer<T>.Serialize(writer, value);
}
public static T Deserialize<T>(BinaryReader reader)
{
return BinarySerializer<T>.Deserialize(reader);
}
}
static class BinarySerializer<T>
{
internal static readonly Action<BinaryWriter, T> Serialize = CreateSerializer();
internal static readonly Func<BinaryReader, T> Deserialize = CreateDeserializer();
static Action<BinaryWriter, T> CreateSerializer()
{
var writer = Expression.Parameter(typeof(BinaryWriter));
var value = Expression.Parameter(typeof(T));
var body = CreateSerializer(writer, value);
var lambda = Expression.Lambda<Action<BinaryWriter, T>>(body, writer, value);
return lambda.Compile();
}
static Expression CreateSerializer(Expression writer, Expression value)
{
var typeCode = Type.GetTypeCode(value.Type);
switch (typeCode)
{
case TypeCode.Boolean:
case TypeCode.Char:
case TypeCode.SByte:
case TypeCode.Byte:
case TypeCode.Int16:
case TypeCode.UInt16:
case TypeCode.Int32:
case TypeCode.UInt32:
case TypeCode.Int64:
case TypeCode.UInt64:
case TypeCode.Single:
case TypeCode.Double:
case TypeCode.Decimal:
case TypeCode.String:
if (value.Type.IsEnum)
{
value = Expression.Convert(value, value.Type.GetEnumUnderlyingType());
}
return Expression.Call(writer, nameof(BinaryWriter.Write), null, value);
case TypeCode.Object:
if (value.Type.GetConstructor(Type.EmptyTypes) == null)
{
throw new NotSupportedException(
$"Types without a default constructor are not supported by {nameof(BinarySerializer)}"
);
}
var members = value.Type.GetMembers();
var memberSerializers = new List<Expression>();
for (int i = 0; i < members.Length; i++)
{
if ((members[i].MemberType & (MemberTypes.Property | MemberTypes.Field)) == 0 ||
members[i] is PropertyInfo property && !property.CanWrite)
{
continue;
}
var memberSelector = Expression.PropertyOrField(value, members[i].Name);
var memberSerializer = CreateSerializer(writer, memberSelector);
}
return Expression.Block(memberSerializers);
case TypeCode.Empty:
case TypeCode.DateTime:
case TypeCode.DBNull:
default:
throw new NotSupportedException(
$"The specified type is not supported by {nameof(BinarySerializer)}."
);
}
}
static Func<BinaryReader, T> CreateDeserializer()
{
var reader = Expression.Parameter(typeof(BinaryReader));
var body = CreateDeserializer(reader, typeof(T));
var lambda = Expression.Lambda<Func<BinaryReader, T>>(body, reader);
return lambda.Compile();
}
static Expression CreateDeserializer(Expression reader, Type type)
{
if (type.IsEnum)
{
var enumDeserializer = CreateDeserializer(reader, type.GetEnumUnderlyingType());
return Expression.Convert(enumDeserializer, type);
}
var typeCode = Type.GetTypeCode(type);
switch (typeCode)
{
case TypeCode.Boolean: return Expression.Call(reader, nameof(BinaryReader.ReadBoolean), null);
case TypeCode.Char: return Expression.Call(reader, nameof(BinaryReader.ReadChar), null);
case TypeCode.SByte: return Expression.Call(reader, nameof(BinaryReader.ReadSByte), null);
case TypeCode.Byte: return Expression.Call(reader, nameof(BinaryReader.ReadByte), null);
case TypeCode.Int16: return Expression.Call(reader, nameof(BinaryReader.ReadInt16), null);
case TypeCode.UInt16: return Expression.Call(reader, nameof(BinaryReader.ReadUInt16), null);
case TypeCode.Int32: return Expression.Call(reader, nameof(BinaryReader.ReadInt32), null);
case TypeCode.UInt32: return Expression.Call(reader, nameof(BinaryReader.ReadUInt32), null);
case TypeCode.Int64: return Expression.Call(reader, nameof(BinaryReader.ReadInt64), null);
case TypeCode.UInt64: return Expression.Call(reader, nameof(BinaryReader.ReadUInt64), null);
case TypeCode.Single: return Expression.Call(reader, nameof(BinaryReader.ReadSingle), null);
case TypeCode.Double: return Expression.Call(reader, nameof(BinaryReader.ReadDouble), null);
case TypeCode.Decimal: return Expression.Call(reader, nameof(BinaryReader.ReadDecimal), null);
case TypeCode.String: return Expression.Call(reader, nameof(BinaryReader.ReadString), null);
case TypeCode.Object:
var constructor = type.GetConstructor(Type.EmptyTypes);
if (constructor == null)
{
throw new NotSupportedException(
$"Types without a default constructor are not supported by {nameof(BinarySerializer)}"
);
}
var members = type.GetMembers();
var value = Expression.Variable(type);
var memberBuilders = new List<Expression>();
memberBuilders.Add(Expression.Assign(value, Expression.New(constructor)));
for (int i = 0; i < members.Length; i++)
{
if ((members[i].MemberType & (MemberTypes.Property | MemberTypes.Field)) == 0 ||
members[i] is PropertyInfo property && !property.CanWrite)
{
continue;
}
var memberSelector = Expression.PropertyOrField(value, members[i].Name);
var memberDeserializer = CreateDeserializer(reader, memberSelector.Type);
memberBuilders.Add(Expression.Assign(memberSelector, memberDeserializer));
}
memberBuilders.Add(value);
return Expression.Block(type, new[] { value }, memberBuilders);
case TypeCode.Empty:
case TypeCode.DateTime:
case TypeCode.DBNull:
default:
throw new NotSupportedException(
$"The specified type is not supported by {nameof(BinarySerializer)}."
);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment