can serialize private(anonymous) type.

This commit is contained in:
neuecc 2017-03-11 16:05:57 +09:00
Родитель 723be25b05
Коммит 1048ba5b75
6 изменённых файлов: 192 добавлений и 4 удалений

Просмотреть файл

@ -18,11 +18,13 @@
<dependencies>
<group targetFramework=".NETFramework4.5">
<dependency id="System.Reflection.Emit" version="4.3.0" />
<dependency id="System.Reflection.Emit.Lightweight" version="4.3.0" />
<dependency id="System.ValueTuple" version="4.3.0" />
<dependency id="System.Threading.Tasks.Extensions" version="4.3.0" />
</group>
<group targetFramework=".NETStandard1.4">
<dependency id="System.Reflection.Emit" version="4.3.0" />
<dependency id="System.Reflection.Emit.Lightweight" version="4.3.0" />
<dependency id="System.ValueTuple" version="4.3.0" />
<dependency id="System.Threading.Tasks.Extensions" version="4.3.0" />
</group>

Просмотреть файл

@ -101,10 +101,12 @@ namespace Sandbox
}
[MessagePackObject()]
[MessagePackObject(true)]
public class Takox
{
public int MyProperty { get; set; }
public int hoga { get; set; }
public int huga { get; set; }
public int tako { get; set; }
}
class Program

Просмотреть файл

@ -30,6 +30,7 @@
<ItemGroup>
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
<PackageReference Include="System.Threading.Tasks.Extensions" Version="4.3.0" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" />
</ItemGroup>

Просмотреть файл

@ -122,6 +122,12 @@ namespace MessagePack.Resolvers
return;
}
if (!ti.IsPublic && ti.IsClass)
{
formatter = (IMessagePackFormatter<T>)DynamicPrivateFormatterBuilder.BuildFormatter(typeof(T));
return;
}
var formatterTypeInfo = DynamicObjectTypeBuilder.BuildType(assembly, typeof(T), true); // true.
if (formatterTypeInfo == null) return;
@ -197,7 +203,7 @@ namespace MessagePack.Internal
}
// int Serialize([arg:1]ref byte[] bytes, [arg:2]int offset, [arg:3]T value, [arg:4]IFormatterResolver formatterResolver);
static void BuildSerialize(Type type, ObjectSerializationInfo info, MethodBuilder method, ILGenerator il)
static void BuildSerialize(Type type, ObjectSerializationInfo info, MethodInfo method, ILGenerator il)
{
// if(value == null) return WriteNil
if (type.GetTypeInfo().IsClass)
@ -712,7 +718,7 @@ namespace MessagePack.Internal
static readonly ConstructorInfo objectCtor = typeof(object).GetTypeInfo().DeclaredConstructors.First(x => x.GetParameters().Length == 0);
static class MessagePackBinaryTypeInfo
internal static class MessagePackBinaryTypeInfo
{
public static TypeInfo TypeInfo = typeof(MessagePackBinary).GetTypeInfo();
@ -747,6 +753,160 @@ namespace MessagePack.Internal
}
}
internal static class DynamicPrivateFormatterBuilder
{
static readonly Type refByte = typeof(byte[]).MakeByRefType();
static readonly MethodInfo getFormatterWithVerify = typeof(FormatterResolverExtensions).GetRuntimeMethods().First(x => x.Name == "GetFormatterWithVerify");
static readonly Func<Type, MethodInfo> getSerialize = t => typeof(IMessagePackFormatter<>).MakeGenericType(t).GetRuntimeMethod("Serialize", new[] { refByte, typeof(int), t, typeof(IFormatterResolver) });
// Private type formatter can not create by DynamicAssembly but sometimes needs it(anonymous type, etc...)
// use DynamicMethod(skipVisibility:true) can avoid it so use delegation formatter.
public static object BuildFormatter(Type type)
{
var info = ObjectSerializationInfo.CreateOrNull(type, true);
var serialize = new DynamicMethod("Serialize", typeof(int), new[] { typeof(byte[]).MakeByRefType(), typeof(int), type, typeof(IFormatterResolver) }, type, true);
var il = serialize.GetILGenerator();
// Build Serialize(same as DynamicObjectTypeBuilder.BuildSerialize but argument - 1)
{
// if(value == null) return WriteNil
var elseBody = il.DefineLabel();
il.EmitLdarg(2);
il.Emit(OpCodes.Brtrue_S, elseBody);
il.EmitLdarg(0);
il.EmitLdarg(1);
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.WriteNil);
il.Emit(OpCodes.Ret);
il.MarkLabel(elseBody);
// var startOffset = offset;
var startOffsetLocal = il.DeclareLocal(typeof(int)); // [loc:0]
il.EmitLdarg(1);
il.EmitStloc(startOffsetLocal);
// use only Map!
var writeCount = info.Members.Count(x => x.IsReadable);
EmitOffsetPlusEqual(il, null, () =>
{
il.EmitLdc_I4(writeCount);
if (writeCount <= MessagePackRange.MaxFixMapCount)
{
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.WriteFixedMapHeaderUnsafe);
}
else
{
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.WriteMapHeader);
}
});
foreach (var item in info.Members.Where(x => x.IsReadable))
{
// offset += writekey
if (info.IsStringKey)
{
EmitOffsetPlusEqual(il, null, () =>
{
// embed string and bytesize
il.Emit(OpCodes.Ldstr, item.StringKey);
il.EmitLdc_I4(StringEncoding.UTF8.GetByteCount(item.StringKey));
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.WriteStringUnsafe);
});
}
// offset += serialzie
EmitSerializeValue(il, type.GetTypeInfo(), item);
}
// return startOffset- offset;
il.EmitLdarg(1);
il.EmitLdloc(startOffsetLocal);
il.Emit(OpCodes.Sub);
il.Emit(OpCodes.Ret);
}
var method = serialize.CreateDelegate(typeof(SerializeDelegate<>).MakeGenericType(type));
var formatter = Activator.CreateInstance(typeof(AnonymousFormatter<>).MakeGenericType(type), new object[] { method });
return formatter;
}
static void EmitOffsetPlusEqual(ILGenerator il, Action loadEmit, Action emit)
{
il.EmitLdarg(1);
if (loadEmit != null) loadEmit();
il.EmitLdarg(0);
il.EmitLdarg(1);
emit();
il.Emit(OpCodes.Add);
il.EmitStarg(1);
}
static void EmitSerializeValue(ILGenerator il, TypeInfo type, ObjectSerializationInfo.EmittableMember member)
{
var t = member.Type;
if (MessagePackBinary.IsMessagePackPrimitive(t))
{
EmitOffsetPlusEqual(il, null, () =>
{
il.EmitLoadArg(type, 2);
member.EmitLoadValue(il);
if (t == typeof(byte[]))
{
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.WriteBytes);
}
else
{
il.EmitCall(DynamicObjectTypeBuilder.MessagePackBinaryTypeInfo.TypeInfo.GetDeclaredMethod("Write" + t.Name));
}
});
}
else
{
EmitOffsetPlusEqual(il, () =>
{
il.EmitLdarg(3);
il.Emit(OpCodes.Call, getFormatterWithVerify.MakeGenericMethod(t));
}, () =>
{
il.EmitLoadArg(type, 2);
member.EmitLoadValue(il);
il.EmitLdarg(3);
il.EmitCall(getSerialize(t));
});
}
}
}
internal delegate int SerializeDelegate<T>(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver);
internal class AnonymousFormatter<T> : IMessagePackFormatter<T>
{
readonly SerializeDelegate<T> serialize;
public AnonymousFormatter(SerializeDelegate<T> serialize)
{
this.serialize = serialize;
}
public int Serialize(ref byte[] bytes, int offset, T value, IFormatterResolver formatterResolver)
{
return serialize(ref bytes, offset, value, formatterResolver);
}
public T Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize)
{
throw new NotSupportedException("Anonymous Formatter does not support Deserialize.");
}
}
internal class ObjectSerializationInfo
{
public bool IsIntKey { get; set; }

Просмотреть файл

@ -0,0 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
namespace MessagePack.Tests
{
public class AnonymousTypeTest
{
[Fact]
public void SerializeAndToJson()
{
var testData = new { Hoge = 100, Huga = true, Yaki = new { Rec = 1, T = 10 }, Nano = "nanoanno" };
var data = MessagePackSerializer.Serialize(testData, MessagePack.Resolvers.DynamicContractlessObjectResolver.Instance);
MessagePackSerializer.ToJson(data).Is(@"{""Hoge"":100,""Huga"":true,""Yaki"":{""Rec"":1,""T"":10},""Nano"":""nanoanno""}");
}
}
}

Просмотреть файл

@ -100,6 +100,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="AnonymousTypeTest.cs" />
<Compile Include="CollectionTest.cs" />
<Compile Include="DictionaryTest.cs" />
<Compile Include="ExtensionTests\ImmutableCollectionTest.cs" />