《CLR via C#》 之运行时序列化(2)

本文深入解析CLR中的序列化过程,包括格式化器如何序列化和反序列化对象实例,使用SerializableAttribute属性控制序列化行为的方法,以及通过实现ISerializable接口进行更细粒度的控制。同时介绍了StreamingContext的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

现在来说一下,格式化器是如何序列化类型的实例。

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

其实格式化器也没什么了不起的,它在内部调用了FormatterServices类的静态方法。FormatterServices类中只包含静态方法,而且这个类不能实例化。

序列化一个应用了SerializableAttribute attribute的对象,步骤如下:

  1. 格式化器调用FormatterServices.GetSerializableMembers(Type,StreamingContext); 这个方法利用发射获取public和private实例字段(标记NonSerializedAttribute attribute的字段除外)。
  2. 将获取到的MethodInfo对象数组传给FormatterServices.GetObjectData(); 这个方法返回的Object数组的每一个实例,分别对应被序列化的那个对象中的各个字段的值。(Object[]的顺序,与MethodInfo[]元素的顺序一致。object[0]指的是methodInfo[0]的值)
  3. 然后往流写入程序集的标识和类型的完整名称。
  4. 最后就是将两个数组(object[],methodInfo[])中的每一个元素名称和值写入流中。

接下来,我们看下格式化器是如何反序列化一个应用了SerializableAttribute attribute的对象。

  1. 先读取程序集表示和完整的类型名。这时候,如果程序集无法加载则会报错。反序列化依然是调用FormatterServices的静态方法。 FormatterServices.GetTypeFromAssembly(Assembly,String);这个方法返回反序列化的那个对象的类型。
  2. 调用FormatterServices.GetUninitializedObject(Type);这个方法只是给新对象分配内存,并不为对象调用构造器。这时,对象的所有字段都被初始化为null或0。
  3. 依旧通过GetSerializableMembers获取MethodInfo数组。这个数组就是需要反序列化的一组字段。
  4. 根据流中的数据创建并初始化一个Object数组。
  5. 将新分配的对象、MemberInfo数组以及并行Object数组传给FormatterServices.PopulateObjectMembers(Object,MemberInfo[],Object[]);将数据跟对象整合。至此,对象被反序列化好了。

从第4点来看,其实数据流中存放的东西是什么??数据!!当然还存放了一些其他的,例如程序集和类型完整名称。

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

跟序列化相关的Attribute不仅仅有SerializableAttribute,NonSerializedAttribute还有其他一些,OnSerializing,OnSerialized,OnDeserializing,OnDeserialized和OptionalField等attribute。这些列举的attribute可以用来控制序列化和反序列。

    [Serializable]
    internal class Circle
    {
        private double m_radius;

        [NonSerialized]
        private double m_area;

        public Circle(Double radius)
        {
            m_radius = radius;
            m_area = Math.PI * m_radius * m_radius;
        }

        [OnDeserialized]
        private void OnDeserialized(StreamingContext context)
        {
            m_area = Math.PI * m_radius * m_radius;
        }
    }

从上面的例子,我们可以看到,OnDeserialized方法上应用了OnDeserializedAttribute attribute。它告诉构造器,在反序列化结束前,调用这个方法。这个例子还告诉我们,并不是所有字段都需要序列化,如果能够通过计算就可以得到,何必通过反射的方式去获取呢?这是浪费性能的表现。

同时我们讲一下OnDeserialized这个方法。注意以下几点:必须有一个StreamingContext参数;声明为private方法,以免被普通代码调用;而格式化器有充足的权限,所以能够调用私有方法。

了解了以上一些知识后,我们来看另外一种控制序列化/反序列化数据的方式——继承实现System.Runtime.Serialization.ISerializable接口。

    public class MyClass : ISerializable, IDeserializationCallback
    {
        private SerializationInfo m_siInfo; // only for Deserialization

        // Special constructor for deserialization(this is required for ISerializable)
        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        protected MyClass(SerializationInfo info, StreamingContext context)
        {
            // When deserializing, save serializationInfo for OnDeserialization
            m_siInfo = info;
        }

        [SecurityCritical]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // call AddValue method
            info.AddValue("Values", false);
        }

        public virtual void IDeserializationCallback.OnDeserialization(object sender)
        {
            if (m_siInfo == null)
            {
                return; // if m_siInfo is null, return
            }

            // call GetXXX method to get values
            m_siInfo.GetBoolean("Value");
        }
    }
View Code

上面的代码想大家展示了如何实现ISerializable接口。

现在是说明。如果继承了ISerializable接口,那么应用在这个类上的attribute将被忽略。从上面的代码中,我们可以看到一个SerializationInfo的对象。这个对象不仅仅在GetObjectData中使用,还在OnDserialization方法中使用。SerializationInfo就是核心。通过AddValue与GetValue来往SerializationInfo中添加和获取数据。构造一个SerializationInfo时,格式化器要传递两个参数:Type和IFormatterConverter。Type中包含了程序集和类型全名。代码中特殊的构造函数是必须的,并且参数与GetObjectData一致,同时这个特殊的构造函数是为反序列添加的。

接下来我们来看下IFormatterConverter类型参数。假设,如果GetObjectData中AddValue方法传递的是Int32值,那么在反序列化对象时,应该为同一个值调用GetInt32方法。如果AddValue传递并非普通类型,那么我们要怎么办呢?没错,通过IFormatterConverter这个参数来将流中的值“转型”成为指定的类型。

ISerializable接口的功能非常强大,可以完全控制一个类型的序列化和反序列化。然而,这个能力是有代价的:该类型还要负责它的基类型的所有字段的序列化。如果基类型也实现了ISerializable接口,那么对基类型的字段进行序列化是很容易的。只需要调用类型的GetObjectData方法即可。但是如果,基类型没有实现ISerializable接口,则要手序列化基类的字段,将它们的值添加到SerializationInfo中。

    [Serializable]
    internal class Derived : Base, ISerializable
    {
        private DateTime m_date = DateTime.Now;

        public Derived()
        {
        }

        // if no constructor, it will cause SerializationException
        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        private Derived(SerializationInfo info, StreamingContext context)
        {
            // get methodInfo from base class
            Type baseType = this.GetType().BaseType;
            MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);

            // get field from base class
            for (int i = 0; i < mi.Length; i++)
            {
                FieldInfo fi = (FieldInfo)mi[i];
                fi.SetValue(this,info.GetValue(baseType.FullName+"+"+fi.Name,fi.FieldType);
            }

            // deserialize class's field
            m_date = info.GetDateTime("Date");
        }

        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        public void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            // class serialize field
            info.AddValue("Date", m_date);

            Type baseType = this.GetType().BaseType;
            MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);

            // serialize data into info
            for (int i = 0; i < mi.Length; i++)
            {
                info.AddValue(baseType.FullName + "+" + mi[i].Name, ((FieldInfo)mi[i]).GetValue(this));
            }
        }

        public override string ToString()
        {
            return string.Format("Name = {0}, Date = {1}", m_name, m_date);
        }
    }
View Code

流上下文

仔细的同学们,发现本文多次提到,并在代码中用到StreamingContext,但却没有说明这个到底是个什么东西。

我们先看一下StreamingContext里面到底有什么:

 
成员名称成员类型说明
StateStreamingContextStates一组位标志(bit flag),指定要序列化/反序列化的对象的来源或目的地
ContextObject对一个对象的引用,对象中包含了用户希望的上下问信息

 

 

 

State可以表示来源或目的地是不是同一台机器,是不是同一个文件,是不是一个进程等。。。这一切都是为了保证序列话中涉及的信号量的字符串名称进行序列化与否。

2013-06-06 ………

《CLR via C#》 之运行时序列化

转载于:https://www.cnblogs.com/OliverZh/archive/2013/06/06/CLRViaCSharp2.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值