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


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

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

    其实格式化器也没什么了不起的,它在内部调用了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里面到底有什么:

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

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

    2013-06-06 ………

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

  • 相关阅读:
    随机数
    质数
    猜数
    失败
    判断质数
    2019.7.21记录
    9*9乘法表
    小人
    奔跑的字母

  • 原文地址:https://www.cnblogs.com/OliverZh/p/CLRViaCSharp2.html
Copyright © 2020-2023  润新知