解决MySql布尔型新旧版本兼容问题,采用枚举来表示布尔型的数据表。由正向工程赋值
大石头 authored at 2018-05-15 21:21:05
20.20 KiB
X
using System.Collections.Concurrent;
using System.Diagnostics.CodeAnalysis;
using System.Linq.Expressions;
using System.Reflection;
using NewLife.Buffers;
using NewLife.Collections;
using NewLife.Data;
using NewLife.Reflection;

namespace NewLife.Serialization;

/// <summary>高性能Span二进制序列化器</summary>
/// <remarks>
/// 基于 <see cref="SpanReader"/>/<see cref="SpanWriter"/> 的极速二进制序列化框架,
/// 无需Stream、无处理器链、类型元数据一次缓存,适用于RPC通信和文件读写。
/// <list type="bullet">
/// <item>实现 <see cref="ISpanSerializable"/> 接口的类型获得零反射序列化性能</item>
/// <item>普通类型通过缓存反射自动序列化,支持基础类型、String、DateTime、Guid、Byte[]、枚举和嵌套类</item>
/// <item>不支持列表、字典等集合类型,如需请实现 <see cref="ISpanSerializable"/> 自行处理</item>
/// </list>
/// </remarks>
public static class SpanSerializer
{
    #region 类型缓存
    private static readonly ConcurrentDictionary<Type, PropertySchema[]> _cache = new();

    private static PropertySchema[] GetSchema(Type type) => _cache.GetOrAdd(type, BuildSchema);

    private static PropertySchema[] BuildSchema(Type type)
    {
        var props = type.GetProperties(true);
        var schemas = new PropertySchema[props.Count];
        for (var i = 0; i < props.Count; i++)
        {
            var p = props[i];
            var pType = p.PropertyType;

            // 解析Nullable<T>
            var underlyingType = Nullable.GetUnderlyingType(pType);
            var isNullable = underlyingType != null;
            var actualType = underlyingType ?? pType;

            // 解析Enum,取底层整数类型
            var isEnum = actualType.IsEnum;
            var enumType = isEnum ? actualType : null;
            if (isEnum) actualType = actualType.GetEnumUnderlyingType();

            // 编译属性访问委托,替代反射GetValue/SetValue
            var getter = BuildGetter(type, p);
            var setter = BuildSetter(type, p);

            schemas[i] = new PropertySchema(p, pType, actualType, Type.GetTypeCode(actualType), isNullable, isEnum, enumType, getter, setter);
        }
        return schemas;
    }

    /// <summary>编译属性取值委托:(Object target) => (Object)((T)target).Prop</summary>
    private static Func<Object, Object?> BuildGetter(Type type, PropertyInfo property)
    {
        var target = Expression.Parameter(typeof(Object), "target");
        Expression body = Expression.Property(Expression.Convert(target, type), property);
        if (property.PropertyType.IsValueType)
            body = Expression.Convert(body, typeof(Object));
        return Expression.Lambda<Func<Object, Object?>>(body, target).Compile();
    }

    /// <summary>编译属性赋值委托:(Object target, Object value) => ((T)target).Prop = (TProp)value</summary>
    private static Action<Object, Object?> BuildSetter(Type type, PropertyInfo property)
    {
        var target = Expression.Parameter(typeof(Object), "target");
        var value = Expression.Parameter(typeof(Object), "value");
        // 值类型用Unbox获取可写引用,引用类型用Convert
        var instance = type.IsValueType
            ? Expression.Unbox(target, type)
            : (Expression)Expression.Convert(target, type);
        var assign = Expression.Assign(
            Expression.Property(instance, property),
            Expression.Convert(value, property.PropertyType));
        return Expression.Lambda<Action<Object, Object?>>(assign, target, value).Compile();
    }

    /// <summary>属性元数据缓存结构</summary>
    internal readonly struct PropertySchema(PropertyInfo property, Type propertyType, Type actualType, TypeCode code, Boolean isNullable, Boolean isEnum, Type? enumType, Func<Object, Object?> getter, Action<Object, Object?> setter)
    {
        /// <summary>属性信息</summary>
        public readonly PropertyInfo Property = property;

        /// <summary>原始属性类型</summary>
        public readonly Type PropertyType = propertyType;

        /// <summary>实际值类型(解除Nullable和Enum后)</summary>
        public readonly Type ActualType = actualType;

        /// <summary>类型编码</summary>
        public readonly TypeCode Code = code;

        /// <summary>是否Nullable值类型</summary>
        public readonly Boolean IsNullable = isNullable;

        /// <summary>是否枚举</summary>
        public readonly Boolean IsEnum = isEnum;

        /// <summary>枚举类型(用于Enum.ToObject)</summary>
        public readonly Type? EnumType = enumType;

        /// <summary>编译属性取值委托,替代反射</summary>
        public readonly Func<Object, Object?> Getter = getter;

        /// <summary>编译属性赋值委托,替代反射</summary>
        public readonly Action<Object, Object?> Setter = setter;
    }
    #endregion

    private static readonly DateTime _dt1970 = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    /// <summary>协议头部预留空间。序列化时在缓冲区前方预留该字节数,方便各种协议向前扩展头部,默认32</summary>
    public static Int32 HeaderReserve { get; set; } = 32;

    #region 快捷方法
    /// <summary>将ISpanSerializable对象序列化为数据包,支持大对象自动溢出到流</summary>
    /// <remarks>
    /// 池化缓冲区 + 池化流双路径设计:小数据零拷贝返回,大数据自动 Flush 到 MemoryStream。
    /// 缓冲区前方预留 <paramref name="reserve"/> 字节,方便协议层向前扩展头部。
    /// </remarks>
    /// <param name="value">实现 <see cref="ISpanSerializable"/> 的对象</param>
    /// <param name="bufferSize">初始缓冲区大小,默认8192</param>
    /// <param name="reserve">头部预留空间,默认32</param>
    /// <returns>数据包,调用方负责释放</returns>
    public static IOwnerPacket ToPacket(this ISpanSerializable value, Int32 bufferSize = 8192, Int32 reserve = 32)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));

        // 池化缓冲区和流,两个都从池里借
        using var pk = new OwnerPacket(bufferSize);
        var ms = Pool.MemoryStream.Get();
        ms.Position = reserve;
        var writer = new SpanWriter(pk.GetSpan()[reserve..], ms);

        value.Write(ref writer);

        // 小数据:数据全在缓冲区中,流中无数据
        if (writer.TotalWritten == writer.WrittenCount)
        {
            var count = writer.WrittenCount;
            Pool.MemoryStream.Return(ms);

            // 共享切片:返回窗口获得独立引用(拥有句柄),序列化缓冲的引用在方法退出时释放
            return pk.Slice(reserve, count);
        }

        // 大数据:Flush 剩余到流后包装
        writer.Flush();

        ms.Position = reserve;
        return new OwnerPacket(ms);
    }

    /// <summary>序列化为带帧头的数据包,自动预留并扩展头部区域</summary>
    /// <remarks>
    /// 在 <see cref="ToPacket"/> 基础上调用 <c>new OwnerPacket(owner, headerSize)</c> 向前扩展头部。
    /// 返回的数据包 <c>GetSpan()[..headerSize]</c> 为帧头区域(未填充),由调用方写入协议帧头。
    /// 消息正文长度 = <c>Length - headerSize</c>。
    /// </remarks>
    /// <param name="value">实现 <see cref="ISpanSerializable"/> 的对象</param>
    /// <param name="headerSize">帧头字节数,必须大于0</param>
    /// <param name="bufferSize">初始缓冲区大小,默认8192</param>
    /// <returns>包含帧头区域和消息正文的数据包,调用方负责释放</returns>
    public static IOwnerPacket ToFrame(this ISpanSerializable value, Int32 headerSize, Int32 bufferSize = 8192)
    {
        if (headerSize <= 0) throw new ArgumentOutOfRangeException(nameof(headerSize));

        var body = value.ToPacket(bufferSize, headerSize);
        return new OwnerPacket((OwnerPacket)body, headerSize);
    }

    /// <summary>从二进制数据反序列化填充ISpanSerializable对象</summary>
    /// <remarks>
    /// 与 <see cref="ToPacket"/> 对称的反序列化扩展,直接在已有实例上填充成员。
    /// 适用于对象池复用场景,避免反复创建新实例。
    /// </remarks>
    /// <param name="value">实现 <see cref="ISpanSerializable"/> 的目标对象</param>
    /// <param name="data">二进制数据</param>
    /// <returns>实际消费的字节数</returns>
    public static Int32 FromSpan(this ISpanSerializable value, ReadOnlySpan<Byte> data)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));

        var reader = new SpanReader(data);
        value.Read(ref reader);
        return reader.Position;
    }

    /// <summary>从数据包反序列化填充ISpanSerializable对象</summary>
    /// <remarks>
    /// 与 <see cref="ToPacket"/> 对称的反序列化扩展。
    /// 单包直接取Span零拷贝读取,链式包通过SpanReader内部流模式处理。
    /// </remarks>
    /// <param name="value">实现 <see cref="ISpanSerializable"/> 的目标对象</param>
    /// <param name="data">数据包</param>
    /// <returns>实际消费的字节数</returns>
    public static Int32 FromPacket(this ISpanSerializable value, IPacket data)
    {
        if (value == null) throw new ArgumentNullException(nameof(value));
        if (data == null) throw new ArgumentNullException(nameof(data));

        var reader = new SpanReader(data);
        value.Read(ref reader);
        return reader.Position;
    }

    #endregion

    #region 写入
    /// <summary>写入引用类型对象(含null标记)</summary>
    /// <typeparam name="T">对象类型。AOT 下该类型的公开属性与字段将按需保留</typeparam>
    /// <param name="writer">Span写入器</param>
    /// <param name="value">目标对象,可为null</param>
    public static void Write<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] T>(ref SpanWriter writer, T? value) where T : class
    {
        if (value == null)
        {
            writer.Write((Byte)0);
            return;
        }
        writer.Write((Byte)1);
        WriteObject(ref writer, value, typeof(T));
    }

    /// <summary>写入对象成员到SpanWriter(不含null标记)</summary>
    /// <param name="writer">Span写入器</param>
    /// <param name="value">目标对象,不可为null</param>
    /// <param name="type">对象类型。AOT 下该类型的公开属性与字段将按需保留</param>
    public static void WriteObject(ref SpanWriter writer, Object value, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields)] Type type)
    {
        // ISpanSerializable 零反射快速路径
        if (value is ISpanSerializable ss)
        {
            ss.Write(ref writer);
            return;
        }

        var schemas = GetSchema(type);
        for (var i = 0; i < schemas.Length; i++)
        {
            var val = schemas[i].Getter(value);
            WriteValue(ref writer, val, in schemas[i]);
        }
    }

    private static void WriteValue(ref SpanWriter writer, Object? value, in PropertySchema schema)
    {
        // Nullable值类型:先写标记
        if (schema.IsNullable)
        {
            if (value == null)
            {
                writer.Write((Byte)0);
                return;
            }
            writer.Write((Byte)1);
        }

        switch (schema.Code)
        {
            case TypeCode.Boolean:
                writer.Write((Byte)(value is Boolean bv && bv ? 1 : 0));
                break;
            case TypeCode.SByte:
                writer.Write(unchecked((Byte)(value is SByte sbv ? sbv : Convert.ToSByte(value))));
                break;
            case TypeCode.Byte:
                writer.Write(value is Byte byv ? byv : Convert.ToByte(value));
                break;
            case TypeCode.Char:
                writer.Write(Convert.ToByte(value));
                break;
            case TypeCode.Int16:
                writer.Write(value is Int16 i16 ? i16 : Convert.ToInt16(value));
                break;
            case TypeCode.UInt16:
                writer.Write(value is UInt16 u16 ? u16 : Convert.ToUInt16(value));
                break;
            case TypeCode.Int32:
                writer.Write(value is Int32 i32 ? i32 : Convert.ToInt32(value));
                break;
            case TypeCode.UInt32:
                writer.Write(value is UInt32 u32 ? u32 : Convert.ToUInt32(value));
                break;
            case TypeCode.Int64:
                writer.Write(value is Int64 i64 ? i64 : Convert.ToInt64(value));
                break;
            case TypeCode.UInt64:
                writer.Write(value is UInt64 u64 ? u64 : Convert.ToUInt64(value));
                break;
            case TypeCode.Single:
                writer.Write(value is Single fv ? fv : Convert.ToSingle(value));
                break;
            case TypeCode.Double:
                writer.Write(value is Double dv ? dv : Convert.ToDouble(value));
                break;
            case TypeCode.Decimal:
                var dcBits = Decimal.GetBits(value is Decimal dcv ? dcv : Convert.ToDecimal(value));
                for (var j = 0; j < 4; j++)
                    writer.Write(dcBits[j]);
                break;
            case TypeCode.DateTime:
                var dt = value is DateTime dtv ? dtv : Convert.ToDateTime(value);
                writer.Write(dt > DateTime.MinValue ? (UInt32)(dt - _dt1970).TotalSeconds : (UInt32)0);
                break;
            case TypeCode.String:
                writer.Write(value as String, 0);
                break;
            case TypeCode.Object:
                WriteComplexValue(ref writer, value, schema.PropertyType);
                break;
        }
    }

    [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "复杂值类型来自序列化对象图(运行时来源);AOT 元数据由调用方泛型入口或 SystemJson 上下文负责")]
    private static void WriteComplexValue(ref SpanWriter writer, Object? value, Type type)
    {
        // Guid - 16字节结构体
        if (type == typeof(Guid))
        {
            writer.Write(value is Guid gv ? gv : default);
            return;
        }

        // Byte[] - 7位长度前缀 + 数据
        if (type == typeof(Byte[]))
        {
            if (value is not Byte[] buf)
            {
                writer.WriteEncodedInt(0);
            }
            else
            {
                writer.WriteEncodedInt(buf.Length);
                if (buf.Length > 0) writer.Write(buf);
            }
            return;
        }

        // 引用类型(嵌套对象) - 与Binary兼容,不写null标记
        if (!type.IsValueType)
        {
            if (value != null)
                WriteObject(ref writer, value, type);
            return;
        }

        // 值类型结构体 - 直接写成员
        if (value != null)
            WriteObject(ref writer, value, type);
    }
    #endregion

    #region 读取
    /// <summary>读取引用类型对象(含null标记)</summary>
    /// <typeparam name="T">对象类型。AOT 下该类型的公开属性、字段与无参构造将按需保留</typeparam>
    /// <param name="reader">Span读取器</param>
    /// <returns>反序列化的对象,可能为null</returns>
    public static T? Read<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicConstructors)] T>(ref SpanReader reader) where T : class
    {
        var flag = reader.ReadByte();
        if (flag == 0) return null;

        return (T)ReadObject(ref reader, typeof(T));
    }

    /// <summary>从SpanReader读取对象成员(不含null标记)</summary>
    /// <param name="reader">Span读取器</param>
    /// <param name="type">对象类型。AOT 下该类型的公开属性、字段与无参构造将按需保留</param>
    /// <returns>反序列化的对象实例</returns>
    public static Object ReadObject(ref SpanReader reader, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties | DynamicallyAccessedMemberTypes.PublicFields | DynamicallyAccessedMemberTypes.PublicConstructors)] Type type)
    {
        var obj = type.CreateInstance()!;

        // ISpanSerializable 零反射快速路径
        if (obj is ISpanSerializable ss)
        {
            if (reader.Available > 0)
                ss.Read(ref reader);
            return obj;
        }

        var schemas = GetSchema(type);
        for (var i = 0; i < schemas.Length; i++)
        {
            // 数据流不足时放弃读取,与Binary的EndOfStream行为一致
            if (reader.Available <= 0) break;

            var val = ReadValue(ref reader, in schemas[i]);
            schemas[i].Setter(obj, val);
        }

        return obj;
    }

    private static Object? ReadValue(ref SpanReader reader, in PropertySchema schema)
    {
        // Nullable值类型:先读标记
        if (schema.IsNullable)
        {
            if (reader.ReadByte() == 0) return null;
        }

        Object? result;
        switch (schema.Code)
        {
            case TypeCode.Boolean:
                result = reader.ReadByte() != 0;
                break;
            case TypeCode.SByte:
                result = unchecked((SByte)reader.ReadByte());
                break;
            case TypeCode.Byte:
                result = reader.ReadByte();
                break;
            case TypeCode.Char:
                result = Convert.ToChar(reader.ReadByte());
                break;
            case TypeCode.Int16:
                result = reader.ReadInt16();
                break;
            case TypeCode.UInt16:
                result = reader.ReadUInt16();
                break;
            case TypeCode.Int32:
                result = reader.ReadInt32();
                break;
            case TypeCode.UInt32:
                result = reader.ReadUInt32();
                break;
            case TypeCode.Int64:
                result = reader.ReadInt64();
                break;
            case TypeCode.UInt64:
                result = reader.ReadUInt64();
                break;
            case TypeCode.Single:
                result = reader.ReadSingle();
                break;
            case TypeCode.Double:
                result = reader.ReadDouble();
                break;
            case TypeCode.Decimal:
                var rdBits = new Int32[4];
                for (var j = 0; j < 4; j++)
                    rdBits[j] = reader.ReadInt32();
                result = new Decimal(rdBits);
                break;
            case TypeCode.DateTime:
                var sec = reader.ReadUInt32();
                result = sec == 0 ? DateTime.MinValue : _dt1970.AddSeconds(sec);
                break;
            case TypeCode.String:
                result = reader.ReadString();
                break;
            case TypeCode.Object:
                result = ReadComplexValue(ref reader, schema.PropertyType);
                break;
            default:
                result = null;
                break;
        }

        // 枚举类型还原
        if (schema.IsEnum && result != null)
            result = Enum.ToObject(schema.EnumType!, result);

        return result;
    }

    [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = "复杂值类型来自序列化对象图(运行时来源);AOT 元数据由调用方泛型入口或 SystemJson 上下文负责")]
    private static Object? ReadComplexValue(ref SpanReader reader, Type type)
    {
        // Guid - 16字节结构体
        if (type == typeof(Guid))
            return reader.Read<Guid>();

        // Byte[] - 7位长度前缀 + 数据
        if (type == typeof(Byte[]))
        {
            var len = reader.ReadEncodedInt();
            if (len <= 0) return len == 0 ? new Byte[0] : null;
            return reader.ReadBytes(len).ToArray();
        }

        // 引用类型(嵌套对象) - 与Binary兼容,不读null标记
        if (!type.IsValueType)
            return ReadObject(ref reader, type);

        // 值类型结构体
        return ReadObject(ref reader, type);
    }
    #endregion
}