339 lines
8.9 KiB
C#
339 lines
8.9 KiB
C#
using System;
|
|
using System.Reflection;
|
|
using ProtoBuf.Compiler;
|
|
using ProtoBuf.Meta;
|
|
|
|
namespace ProtoBuf.Serializers;
|
|
|
|
internal sealed class TupleSerializer : IProtoTypeSerializer, IProtoSerializer
|
|
{
|
|
private readonly MemberInfo[] members;
|
|
|
|
private readonly ConstructorInfo ctor;
|
|
|
|
private IProtoSerializer[] tails;
|
|
|
|
public Type ExpectedType => ctor.DeclaringType;
|
|
|
|
public bool RequiresOldValue => true;
|
|
|
|
public bool ReturnsValue => false;
|
|
|
|
public TupleSerializer(RuntimeTypeModel model, ConstructorInfo ctor, MemberInfo[] members)
|
|
{
|
|
this.ctor = ctor ?? throw new ArgumentNullException("ctor");
|
|
this.members = members ?? throw new ArgumentNullException("members");
|
|
tails = new IProtoSerializer[members.Length];
|
|
ParameterInfo[] parameters = ctor.GetParameters();
|
|
for (int i = 0; i < members.Length; i++)
|
|
{
|
|
Type parameterType = parameters[i].ParameterType;
|
|
Type itemType = null;
|
|
Type defaultType = null;
|
|
MetaType.ResolveListTypes(model, parameterType, ref itemType, ref defaultType);
|
|
Type type = (((object)itemType == null) ? parameterType : itemType);
|
|
bool asReference = false;
|
|
int num = model.FindOrAddAuto(type, demand: false, addWithContractOnly: true, addEvenIfAutoDisabled: false);
|
|
if (num >= 0)
|
|
{
|
|
asReference = model[type].AsReferenceDefault;
|
|
}
|
|
IProtoSerializer protoSerializer = ValueMember.TryGetCoreSerializer(model, DataFormat.Default, type, out var defaultWireType, asReference, dynamicType: false, overwriteList: false, allowComplexTypes: true);
|
|
if (protoSerializer == null)
|
|
{
|
|
throw new InvalidOperationException("No serializer defined for type: " + type.FullName);
|
|
}
|
|
protoSerializer = new TagDecorator(i + 1, defaultWireType, strict: false, protoSerializer);
|
|
IProtoSerializer protoSerializer2 = (((object)itemType != null) ? ((!parameterType.IsArray) ? ((ProtoDecoratorBase)ListDecorator.Create(model, parameterType, defaultType, protoSerializer, i + 1, writePacked: false, defaultWireType, returnList: true, overwriteList: false, supportNull: false)) : ((ProtoDecoratorBase)new ArrayDecorator(model, protoSerializer, i + 1, writePacked: false, defaultWireType, parameterType, overwriteList: false, supportNull: false))) : protoSerializer);
|
|
tails[i] = protoSerializer2;
|
|
}
|
|
}
|
|
|
|
public bool HasCallbacks(TypeModel.CallbackType callbackType)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void EmitCallback(CompilerContext ctx, Local valueFrom, TypeModel.CallbackType callbackType)
|
|
{
|
|
}
|
|
|
|
void IProtoTypeSerializer.Callback(object value, TypeModel.CallbackType callbackType, SerializationContext context)
|
|
{
|
|
}
|
|
|
|
object IProtoTypeSerializer.CreateInstance(ProtoReader source)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
private object GetValue(object obj, int index)
|
|
{
|
|
if (members[index] is PropertyInfo propertyInfo)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
if (!Helpers.IsValueType(propertyInfo.PropertyType))
|
|
{
|
|
return null;
|
|
}
|
|
return Activator.CreateInstance(propertyInfo.PropertyType);
|
|
}
|
|
return propertyInfo.GetValue(obj, null);
|
|
}
|
|
if (members[index] is FieldInfo fieldInfo)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
if (!Helpers.IsValueType(fieldInfo.FieldType))
|
|
{
|
|
return null;
|
|
}
|
|
return Activator.CreateInstance(fieldInfo.FieldType);
|
|
}
|
|
return fieldInfo.GetValue(obj);
|
|
}
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
public object Read(object value, ProtoReader source)
|
|
{
|
|
object[] array = new object[members.Length];
|
|
bool flag = false;
|
|
if (value == null)
|
|
{
|
|
flag = true;
|
|
}
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
array[i] = GetValue(value, i);
|
|
}
|
|
int num;
|
|
while ((num = source.ReadFieldHeader()) > 0)
|
|
{
|
|
flag = true;
|
|
if (num <= tails.Length)
|
|
{
|
|
IProtoSerializer protoSerializer = tails[num - 1];
|
|
array[num - 1] = tails[num - 1].Read(protoSerializer.RequiresOldValue ? array[num - 1] : null, source);
|
|
}
|
|
else
|
|
{
|
|
source.SkipField();
|
|
}
|
|
}
|
|
if (!flag)
|
|
{
|
|
return value;
|
|
}
|
|
return ctor.Invoke(array);
|
|
}
|
|
|
|
public void Write(object value, ProtoWriter dest)
|
|
{
|
|
for (int i = 0; i < tails.Length; i++)
|
|
{
|
|
object value2 = GetValue(value, i);
|
|
if (value2 != null)
|
|
{
|
|
tails[i].Write(value2, dest);
|
|
}
|
|
}
|
|
}
|
|
|
|
private Type GetMemberType(int index)
|
|
{
|
|
Type memberType = Helpers.GetMemberType(members[index]);
|
|
if ((object)memberType == null)
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
return memberType;
|
|
}
|
|
|
|
bool IProtoTypeSerializer.CanCreateInstance()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
public void EmitWrite(CompilerContext ctx, Local valueFrom)
|
|
{
|
|
using Local local = ctx.GetLocalWithValue(ctor.DeclaringType, valueFrom);
|
|
for (int i = 0; i < tails.Length; i++)
|
|
{
|
|
Type memberType = GetMemberType(i);
|
|
ctx.LoadAddress(local, ExpectedType);
|
|
if (members[i] is FieldInfo)
|
|
{
|
|
ctx.LoadValue((FieldInfo)members[i]);
|
|
}
|
|
else if (members[i] is PropertyInfo)
|
|
{
|
|
ctx.LoadValue((PropertyInfo)members[i]);
|
|
}
|
|
ctx.WriteNullCheckedTail(memberType, tails[i], null);
|
|
}
|
|
}
|
|
|
|
void IProtoTypeSerializer.EmitCreateInstance(CompilerContext ctx)
|
|
{
|
|
throw new NotSupportedException();
|
|
}
|
|
|
|
public void EmitRead(CompilerContext ctx, Local incoming)
|
|
{
|
|
using Local local = ctx.GetLocalWithValue(ExpectedType, incoming);
|
|
Local[] array = new Local[members.Length];
|
|
try
|
|
{
|
|
for (int i = 0; i < array.Length; i++)
|
|
{
|
|
Type memberType = GetMemberType(i);
|
|
bool flag = true;
|
|
array[i] = new Local(ctx, memberType);
|
|
if (Helpers.IsValueType(ExpectedType))
|
|
{
|
|
continue;
|
|
}
|
|
if (Helpers.IsValueType(memberType))
|
|
{
|
|
switch (Helpers.GetTypeCode(memberType))
|
|
{
|
|
case ProtoTypeCode.Boolean:
|
|
case ProtoTypeCode.SByte:
|
|
case ProtoTypeCode.Byte:
|
|
case ProtoTypeCode.Int16:
|
|
case ProtoTypeCode.UInt16:
|
|
case ProtoTypeCode.Int32:
|
|
case ProtoTypeCode.UInt32:
|
|
ctx.LoadValue(0);
|
|
break;
|
|
case ProtoTypeCode.Int64:
|
|
case ProtoTypeCode.UInt64:
|
|
ctx.LoadValue(0L);
|
|
break;
|
|
case ProtoTypeCode.Single:
|
|
ctx.LoadValue(0f);
|
|
break;
|
|
case ProtoTypeCode.Double:
|
|
ctx.LoadValue(0.0);
|
|
break;
|
|
case ProtoTypeCode.Decimal:
|
|
ctx.LoadValue(0m);
|
|
break;
|
|
case ProtoTypeCode.Guid:
|
|
ctx.LoadValue(Guid.Empty);
|
|
break;
|
|
default:
|
|
ctx.LoadAddress(array[i], memberType);
|
|
ctx.EmitCtor(memberType);
|
|
flag = false;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ctx.LoadNullRef();
|
|
}
|
|
if (flag)
|
|
{
|
|
ctx.StoreValue(array[i]);
|
|
}
|
|
}
|
|
CodeLabel label = (Helpers.IsValueType(ExpectedType) ? default(CodeLabel) : ctx.DefineLabel());
|
|
if (!Helpers.IsValueType(ExpectedType))
|
|
{
|
|
ctx.LoadAddress(local, ExpectedType);
|
|
ctx.BranchIfFalse(label, @short: false);
|
|
}
|
|
for (int j = 0; j < members.Length; j++)
|
|
{
|
|
ctx.LoadAddress(local, ExpectedType);
|
|
if (members[j] is FieldInfo)
|
|
{
|
|
ctx.LoadValue((FieldInfo)members[j]);
|
|
}
|
|
else if (members[j] is PropertyInfo)
|
|
{
|
|
ctx.LoadValue((PropertyInfo)members[j]);
|
|
}
|
|
ctx.StoreValue(array[j]);
|
|
}
|
|
if (!Helpers.IsValueType(ExpectedType))
|
|
{
|
|
ctx.MarkLabel(label);
|
|
}
|
|
using (Local local2 = new Local(ctx, ctx.MapType(typeof(int))))
|
|
{
|
|
CodeLabel label2 = ctx.DefineLabel();
|
|
CodeLabel label3 = ctx.DefineLabel();
|
|
CodeLabel label4 = ctx.DefineLabel();
|
|
ctx.Branch(label2, @short: false);
|
|
CodeLabel[] array2 = new CodeLabel[members.Length];
|
|
for (int k = 0; k < members.Length; k++)
|
|
{
|
|
array2[k] = ctx.DefineLabel();
|
|
}
|
|
ctx.MarkLabel(label3);
|
|
ctx.LoadValue(local2);
|
|
ctx.LoadValue(1);
|
|
ctx.Subtract();
|
|
ctx.Switch(array2);
|
|
ctx.Branch(label4, @short: false);
|
|
for (int l = 0; l < array2.Length; l++)
|
|
{
|
|
ctx.MarkLabel(array2[l]);
|
|
IProtoSerializer protoSerializer = tails[l];
|
|
Local valueFrom = (protoSerializer.RequiresOldValue ? array[l] : null);
|
|
ctx.ReadNullCheckedTail(array[l].Type, protoSerializer, valueFrom);
|
|
if (protoSerializer.ReturnsValue)
|
|
{
|
|
if (Helpers.IsValueType(array[l].Type))
|
|
{
|
|
ctx.StoreValue(array[l]);
|
|
}
|
|
else
|
|
{
|
|
CodeLabel label5 = ctx.DefineLabel();
|
|
CodeLabel label6 = ctx.DefineLabel();
|
|
ctx.CopyValue();
|
|
ctx.BranchIfTrue(label5, @short: true);
|
|
ctx.DiscardValue();
|
|
ctx.Branch(label6, @short: true);
|
|
ctx.MarkLabel(label5);
|
|
ctx.StoreValue(array[l]);
|
|
ctx.MarkLabel(label6);
|
|
}
|
|
}
|
|
ctx.Branch(label2, @short: false);
|
|
}
|
|
ctx.MarkLabel(label4);
|
|
ctx.LoadReaderWriter();
|
|
ctx.EmitCall(ctx.MapType(typeof(ProtoReader)).GetMethod("SkipField"));
|
|
ctx.MarkLabel(label2);
|
|
ctx.EmitBasicRead("ReadFieldHeader", ctx.MapType(typeof(int)));
|
|
ctx.CopyValue();
|
|
ctx.StoreValue(local2);
|
|
ctx.LoadValue(0);
|
|
ctx.BranchIfGreater(label3, @short: false);
|
|
}
|
|
for (int m = 0; m < array.Length; m++)
|
|
{
|
|
ctx.LoadValue(array[m]);
|
|
}
|
|
ctx.EmitCtor(ctor);
|
|
ctx.StoreValue(local);
|
|
}
|
|
finally
|
|
{
|
|
for (int n = 0; n < array.Length; n++)
|
|
{
|
|
if (array[n] != null)
|
|
{
|
|
array[n].Dispose();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|