public static Action <Packer, DasherContext, object> Build(Type type, DasherContext context) { // Create a collection for errors, initially with zero capacity (minimal allocation) var errors = new List <string>(0); // Validate that the type is suitable as a top-level type context.ValidateTopLevelType(type, errors); // Throw if there were any errors if (errors.Any()) { throw new SerialiserGenerationException(errors, type); } // Initialise code gen var method = new DynamicMethod( $"Generated{type.Name}Serialiser", returnType: null, parameterTypes: new[] { typeof(Packer), typeof(DasherContext), typeof(object) }, restrictedSkipVisibility: true); var ilg = method.GetILGenerator(); // Convert args to locals, so we can pass them around var packer = ilg.DeclareLocal(typeof(Packer)); ilg.Emit(OpCodes.Ldarg_0); // packer ilg.Emit(OpCodes.Stloc, packer); var contextLocal = ilg.DeclareLocal(typeof(DasherContext)); ilg.Emit(OpCodes.Ldarg_1); // context ilg.Emit(OpCodes.Stloc, contextLocal); var value = ilg.DeclareLocal(type); ilg.Emit(OpCodes.Ldarg_2); // value ilg.Emit(type.GetTypeInfo().IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, type); ilg.Emit(OpCodes.Stloc, value); var throwBlocks = new ThrowBlockGatherer(ilg); if (!TryEmitSerialiseCode(ilg, throwBlocks, errors, value, packer, context, contextLocal, isRoot: true)) { Debug.Assert(errors.Any()); throw new SerialiserGenerationException(errors, type); } // Return the newly constructed object! ilg.Emit(OpCodes.Ret); // Write all the exception handling blocks out of line throwBlocks.Flush(); // Return a delegate that performs the above operations return((Action <Packer, DasherContext, object>)method.CreateDelegate(typeof(Action <Packer, DasherContext, object>))); }
/// <summary> /// Initialises an instance of the deserialiser. /// </summary> /// <remarks> /// This constructor attempts to retrieve generated deserialisation code from <paramref name="context"/>. /// If unavailable, it will generate the relevant deserialisation code and cache it in <paramref name="context"/> for future use. /// </remarks> /// <param name="unexpectedFieldBehaviour">Controls how deserialisation behaves when unexpected data is received. Default is <see cref="UnexpectedFieldBehaviour.Throw"/>.</param> /// <param name="context">An optional context for deserialisation. If none is provided, a new context will be created for use by this deserialiser alone.</param> public Deserialiser(UnexpectedFieldBehaviour unexpectedFieldBehaviour = UnexpectedFieldBehaviour.Throw, DasherContext context = null) { _context = context ?? new DasherContext(); _func = _context.GetOrCreateDeserialiseFunc(typeof(T), unexpectedFieldBehaviour); }
/// <summary> /// Initialises an instance of the serialiser. /// </summary> /// <remarks> /// This constructor attempts to retrieve generated serialisation code from <paramref name="context"/>. /// If unavailable, it will generate the relevant serialisation code and cache it in <paramref name="context"/> for future use. /// </remarks> /// <param name="context">An optional context for serialisation. If none is provided, a new context will be created for use by this serialiser alone.</param> public Serialiser(DasherContext context = null) { _context = context ?? new DasherContext(); _action = _context.GetOrCreateSerialiseAction(typeof(T)); }
public static bool TryEmitDeserialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, string name, Type targetType, LocalBuilder value, LocalBuilder unpacker, DasherContext context, LocalBuilder contextLocal, UnexpectedFieldBehaviour unexpectedFieldBehaviour, bool isRoot = false) { if (!context.TryGetTypeProvider(value.LocalType, errors, out ITypeProvider provider)) { return(false); } if (!isRoot && provider is ComplexTypeProvider) { ilg.Emit(OpCodes.Ldloc, contextLocal); ilg.LoadType(value.LocalType); ilg.Emit(OpCodes.Ldc_I4, (int)unexpectedFieldBehaviour); ilg.Emit(OpCodes.Call, Methods.DasherContext_GetOrCreateDeserialiseFunc); ilg.Emit(OpCodes.Ldloc, unpacker); ilg.Emit(OpCodes.Ldloc, contextLocal); ilg.Emit(OpCodes.Call, Methods.DasherDeserialiseFunc_Invoke); ilg.Emit(value.LocalType.GetTypeInfo().IsValueType ? OpCodes.Unbox_Any : OpCodes.Castclass, value.LocalType); ilg.Emit(OpCodes.Stloc, value); } else { var end = ilg.DefineLabel(); if (provider.UseDefaultNullHandling(value.LocalType)) { // check for null var nonNullLabel = ilg.DefineLabel(); ilg.Emit(OpCodes.Ldloc, unpacker); ilg.Emit(OpCodes.Call, Methods.Unpacker_TryReadNull); ilg.Emit(OpCodes.Brfalse_S, nonNullLabel); { ilg.Emit(OpCodes.Ldnull); ilg.Emit(OpCodes.Stloc, value); ilg.Emit(OpCodes.Br, end); } ilg.MarkLabel(nonNullLabel); } if (!provider.TryEmitDeserialiseCode(ilg, throwBlocks, errors, name, targetType, value, unpacker, contextLocal, context, unexpectedFieldBehaviour)) { return(false); } ilg.MarkLabel(end); } return(true); }
public static bool TryEmitSerialiseCode(ILGenerator ilg, ThrowBlockGatherer throwBlocks, ICollection <string> errors, LocalBuilder value, LocalBuilder packer, DasherContext context, LocalBuilder contextLocal, bool isRoot = false) { if (!context.TryGetTypeProvider(value.LocalType, errors, out ITypeProvider provider)) { return(false); } if (!isRoot && provider is ComplexTypeProvider) { // prevent endless code generation for recursive types by delegating to a method call ilg.Emit(OpCodes.Ldloc, contextLocal); ilg.LoadType(value.LocalType); ilg.Emit(OpCodes.Call, Methods.DasherContext_GetOrCreateSerialiseAction); ilg.Emit(OpCodes.Ldloc, packer); ilg.Emit(OpCodes.Ldloc, contextLocal); ilg.Emit(OpCodes.Ldloc, value); if (value.LocalType.GetTypeInfo().IsValueType) { ilg.Emit(OpCodes.Box, value.LocalType); } ilg.Emit(OpCodes.Call, Methods.DasherSerialiseAction_Invoke); } else { var end = ilg.DefineLabel(); if (provider.UseDefaultNullHandling(value.LocalType)) { var nonNull = ilg.DefineLabel(); ilg.Emit(OpCodes.Ldloc, value); ilg.Emit(OpCodes.Brtrue_S, nonNull); ilg.Emit(OpCodes.Ldloc, packer); ilg.Emit(OpCodes.Call, Methods.Packer_PackNull); ilg.Emit(OpCodes.Br, end); ilg.MarkLabel(nonNull); } if (!provider.TryEmitSerialiseCode(ilg, throwBlocks, errors, value, packer, contextLocal, context)) { return(false); } ilg.MarkLabel(end); } return(true); }