CLR Via C# 读书笔记-第24章(运行时序列化)


第 24 章 运行时序列化

  • 序列化是什么? 对象转化为字节流的过程,字节流转化为对象叫反序列化
  • 应用程序状态可以很轻松保存在磁盘文件或数据库中,并在下次运行应用程序时恢复
  • 一组对象可以复制到系统粘贴板,并复制到同一个或其他应用程序中
  • 一组对象可以通过网络发送到另一台机器运行的进程中
下面是简单的序列化和反序列化
class MainClass
{
    public static void Main()
    {
        var objs = new List<string> { "a", "b", "c" };
        Stream stream = SerializeToMemory(objs);

        objs = null;
        Console.WriteLine($"{stream.Position}");


        var dObj = DeserializeFromMemory(stream);
        foreach (var item in (List<string>)dObj)
        {
            Console.WriteLine(item);
        }
    }

    static MemoryStream SerializeToMemory(Object objectGraph)
    {
        MemoryStream memoryStream = new MemoryStream();
        BinaryFormatter bf = new BinaryFormatter();
        bf.Serialize(memoryStream, objectGraph);
        return memoryStream;
    }

    static Object DeserializeFromMemory(Stream stream)
    {
        stream.Seek(0, SeekOrigin.Begin);
        BinaryFormatter bf = new BinaryFormatter();
        return bf.Deserialize(stream);
    }
}
  • Stream对象标识序列化的字节放在哪里
  • BinaryFormatter实现了IFormatter使其知道如何序列化和反序列化对象图
  • DeserializeFromMemory检查流的内容,构造出所有对象的实例,并且初始化所有字段,使其与序列化前对象拥有相同的值,可以利用此特性进行深拷贝,之前List<T>进行深拷贝就利用过这个特性
private static Object DeepClone(Object original) {
// 构造临时内存流
	using (MemoryStream stream = new MemoryStream()) {
	
	    // 构造序列化格式化器来执行所有实际工作
	    BinaryFormatter formatter = new BinaryFormatter();
	
	    // 值一行在本章 24.6 节“流上下文” 解释
	    formatter.Context = new StreamingContext(StreamingContextStates.Clone);
	
	    // 将对象图序列化到内存流中
	    formatter.Serialize(stream, original);
	
	    // 反序列化前,定位到内存流的起始位置
	    stream.Position = 0;
	    
	    // 将对象图反序列化成一组新对象,
	    // 向调用者返回对象图(深拷贝)的根
	    return formatter.Deserialize(stream);
	}
}
  • 可以将多个对象图序列化到一个流中,并根据相同顺序反序列化
[Serializable] public class A
    {
        public int val;
    }

    [Serializable]
    public class B
    {
        public string val;
    }

    public sealed class Program
    {
        public static void Main()
        {
            var aList = new List<A>();
            aList.Add(new A() { val = 1 });
            aList.Add(new A() { val = 2 });

            var bList = new List<B>();
            bList.Add(new B() { val = "a" });
            bList.Add(new B() { val = "b" });

            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, aList);
            bf.Serialize(ms, bList);

            ms.Seek(0, SeekOrigin.Begin);
            var daList = (List<A>)bf.Deserialize(ms);
            var dbList = (List<B>)bf.Deserialize(ms);

            foreach (var item in daList)
            {
                Console.WriteLine(item.val);
            }
            foreach (var item in dbList)
            {
                Console.WriteLine(item.val);
            }


            // 
            Console.WriteLine(Assembly.GetEntryAssembly().FullName);
        }
    }

使类型可序列化

  • 对象图可能一部分可以序列化、一部分不能序列化,当序列化抛出异常,字节流中可能存在一部分序列化的数据,所以应该应该序列化到MemoryStream中,然后再将字节复制到目标字节流,可以使用CopyTo,CopyTo拷贝的字节是剩余字节(从Position开始算)
  • SerializableAttribute默认序列化枚举和委托类型,并且是不可被派生类继承的

控制序列化和反序列化

  • 如果某字段很容易计算,可以使用[NonSerialized]将字段不序列化,减少传输量和性能
  • 应用[NonSerialized],当进行反序列化后,对象这个字段将为默认值,可以利用[OnDeserialized]标识,将会在序列化完成后调用
  • [NonSerialized]和[OnDeserialized]会被派生类继承(如果B继承了A,那么当B反序列化后同样会执行[OnDeserialized]标记的方法),基类被[NonSerialized]字段还是不纳入序列化
  • 最后除了上面的标识还有很多控制序列化和反序列化的标识 e.g. OnDeserializing、OnSerializing、OnSerialized
[Serializable] 
    public class A
    {
        public int a;
        public int b;

        [NonSerialized]
        public int sum;

        public A(int a, int b)
        {
            this.a = a;
            this.b = b;
            this.sum = a + b;
        }

        [OnDeserialized]
        void OnDeserialized_ClassA(StreamingContext context)
        {
            sum = a + b;
        }
    }

    public sealed class Program
    {
        public static void Main()
        {
            A a = new A(1, 2);
            MemoryStream ms = new MemoryStream();
            BinaryFormatter bf = new BinaryFormatter();
            bf.Serialize(ms, a);

            ms.Seek(0, SeekOrigin.Begin);
            A da = (A) bf.Deserialize(ms);

            Console.WriteLine($"Deserialize sum {da.sum}");

            // 
            Console.WriteLine("...");
        }
    }

格式化器如何序列化类型实例

  • 如何序列化对象的字段
    FormatterServices.GetSerializableMembers方法通过反射可以获得所有可序列化字段(除了[NonSerialized]标记的字段)
    FormatterServices.GetObjectData方法获得被序列化的字段的值(这个和上面方法返回的MemberInfo是一一对应的)
    – 格式化器将程序集标识和类型的完整名称写入流中
    – 格式化器然后遍历两个数组中的元素,将每个成员的名称和值写入流中

  • 如何反序列化对象的字段
    – 首先读取程序集标识和类型完整名称,如果程序集没有加载到AppDomain中,那么就去加载,无法加载就抛出异常,程序集存在就使用FormatterServices.GetTypeFromAssembly获得反序列化类型
    – 然后将类型传入FormatterServices.GetUninitializedObject方法,这个方法分配一个新对象,但是不调用构造方法,对象所有字节为Null或0
    – 格式化器创建一个MemberInfo数组,利用FormatterServices.GetSerializableMembers方法获得,然后根据流中的数据初始化一个Object数组
    – 最后利用FormatterServices.PopulateObjectMembers 一一对应填充字段

控制序列化/反序列化的数据

  • 前面提到的例如OnSerialized、OnDeserialized不能满足对序列化全部控制,并且在上面发现序列化和反序化使用了反射,反射速度是很慢的
  • 后面用到再看

流上下文

  • 一组序列化好的对象有很多目的地,使用时只需设置IFormatterContext

序列化代理等剩余内容,用到再看


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值