Created
May 29, 2023 21:39
-
-
Save glopesdev/d9a7c34376ed3cb562cfc8ef52439f41 to your computer and use it in GitHub Desktop.
Simple binary writer serializer generator using expression trees
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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