private static DeserializationFunc <T> CreateUnionDeserializationFunc <T>(Type[] types)
        {
            var deserialzationFunc = new DynamicMethod(string.Empty, typeof(T), new[] { typeof(byte[]), typeof(int), typeof(int), typeof(int), typeof(int), typeof(int).MakeByRefType() }, true);
            var il = deserialzationFunc.GetILGenerator();

            var index      = il.DeclareLocal(typeof(int));
            var indexSize  = il.DeclareLocal(typeof(int));
            var objectSize = il.DeclareLocal(typeof(int));
            var result     = il.DeclareLocal(typeof(T));

            il.EmitLoadArgument(0);
            il.EmitLoadArgument(1);
            il.EmitLoadArgument(2);
            il.EmitLoadLocalAddress(indexSize.LocalIndex);
            il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalRead7BitEncodedInt32), BindingFlagsEx.Static));
            il.EmitStoreLocal(index.LocalIndex);

            {
                var validIndex = il.DefineLabel();

                il.EmitLoadLocal(index.LocalIndex);
                il.EmitLoadDeclaredConstant(types.Length);
                il.Emit(OpCodes.Ble_S, validIndex);
                il.Emit(OpCodes.Ldstr, "Failed to deserialize the object, because its union index was out of bounds.");
                il.Emit(OpCodes.Newobj, typeof(SerializationException).GetConstructor(new[] { typeof(string) }));
                il.Emit(OpCodes.Throw);

                il.MarkLabel(validIndex);
            }

            {
                var valueNotNull = il.DefineLabel();

                il.EmitLoadLocal(result.LocalIndex);
                il.Emit(OpCodes.Brtrue_S, valueNotNull);

                il.EmitLoadArgument(5);
                il.EmitLoadLocal(indexSize.LocalIndex);
                il.Emit(OpCodes.Stind_I4);

                il.Emit(OpCodes.Ldnull);
                il.Emit(OpCodes.Ret);

                il.MarkLabel(valueNotNull);
            }

            il.EmitLoadArgument(1);
            il.EmitLoadLocal(indexSize.LocalIndex);
            il.Emit(OpCodes.Add);
            il.EmitStoreArgument(1);

            il.EmitLoadArgument(2);
            il.EmitLoadLocal(indexSize.LocalIndex);
            il.Emit(OpCodes.Sub);
            il.EmitStoreArgument(2);

            il.EmitLoadLocal(index.LocalIndex);
            il.EmitLoadDeclaredConstant(1);
            il.Emit(OpCodes.Sub);
            il.EmitStoreLocal(index.LocalIndex);

            var jumpTable   = new Label[types.Length];
            var endOfSwitch = il.DefineLabel();

            for (var i = 0; i < types.Length; i++)
            {
                jumpTable[i] = il.DefineLabel();
            }

            il.EmitLoadLocal(index.LocalIndex);
            il.Emit(OpCodes.Switch, jumpTable);

            for (var i = 0; i < types.Length; i++)
            {
                var type = types[i];

                il.MarkLabel(jumpTable[i]);

                GenericFormatter.EmitLoadCachedInstance(il, type);
                il.EmitLoadArgument(0);
                il.EmitLoadArgument(1);
                il.EmitLoadArgument(2);
                il.EmitLoadArgument(3);
                il.EmitLoadArgument(4);
                il.EmitLoadLocalAddress(objectSize.LocalIndex);
                GenericFormatter.EmitDeserialize(il, type);

                if (type.IsValueType)
                {
                    il.Emit(OpCodes.Box, type);
                }

                il.EmitStoreLocal(result.LocalIndex);
                il.Emit(OpCodes.Br, endOfSwitch);
            }

            il.MarkLabel(endOfSwitch);

            il.EmitLoadArgument(5);
            il.EmitLoadLocal(indexSize.LocalIndex);
            il.EmitLoadLocal(objectSize.LocalIndex);
            il.Emit(OpCodes.Add);
            il.Emit(OpCodes.Stind_I4);

            il.EmitLoadLocal(result.LocalIndex);
            il.Emit(OpCodes.Ret);

            return((DeserializationFunc <T>)deserialzationFunc.CreateDelegate(typeof(DeserializationFunc <T>)));
        }
        private static void DeserializeField(ILGenerator il, FieldInfo field, LocalBuilder size)
        {
            var type = PrimitiveTypes.GetPrimitiveType(field.FieldType);

            if (type == PrimitiveType.NotPrimitive)
            {
                GenericFormatter.EmitLoadCachedInstance(il, field.FieldType);
            }

            il.EmitLoadArgument(0);
            il.EmitLoadArgument(1);
            il.EmitLoadArgument(2);
            il.EmitLoadLocalAddress(size.LocalIndex);

            switch (type)
            {
            case PrimitiveType.NotPrimitive:
                il.EmitLoadArgument(4);
                il.EmitLoadArgument(5);
                GenericFormatter.EmitDeserialize(il, field.FieldType);
                break;

            case PrimitiveType.SByte:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadSByte), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Byte:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadByte), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Int16:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadInt16), BindingFlagsEx.Static));
                break;

            case PrimitiveType.UInt16:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadUInt16), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Int32:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalRead7BitEncodedInt32), BindingFlagsEx.Static));
                break;

            case PrimitiveType.UInt32:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalRead7BitEncodedUInt32), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Int64:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalRead7BitEncodedInt64), BindingFlagsEx.Static));
                break;

            case PrimitiveType.UInt64:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalRead7BitEncodedUInt64), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Boolean:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadBoolean), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Char:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadChar), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Single:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadSingle), BindingFlagsEx.Static));
                break;

            case PrimitiveType.Double:
                il.Emit(OpCodes.Call, typeof(Binary).GetMethod(nameof(Binary.InternalReadDouble), BindingFlagsEx.Static));
                break;

            default:
                Debug.Assert(false);
                break;
            }
        }