第五节:控制序列化和反序列化的数据

本章前面讨论过,控制序列化和反序列化过程的最佳方式就是使用OnSerializing、OnSerialized、OnDeserializing、OnDeserialized、NonSerialized、OptionalField等attribute。然而,在一些极少见的情况下,这些attribute不能提供你希望的全部控制。除此之外,格式化器在内部使用了反射,而反射的速度比较慢,这会增大序列化和反序列化对象所化的时间。为了序列化/反序列化的数据进行完全的控制,并避免使用反射,你的类型可实现ISerializable接口:

public interface ISerializable

{

void GetObjectData(SerializationInfo info, StreamingContext context);

}

这个接口只有一个方法,即GetObjectData,但是,实现这个接口的大多数类型还实现了一个特殊的构造器,稍后会详细描述它。

重要提示:ISerializable接口大问题在于,一旦类型实现了它,所有派生类型都必须实现他,而且派生类型必须能保证调用基类的GetObjectData方法和特殊构造器。除此之外,一旦类型实现了该接口,变永远不能删除它,否则会失去与派生类型的兼容性。Sealed类型实现了ISerializable接口总是可行的。

重要提示:ISerializable接口和特殊的构造器旨在由格式化器使用。然而,其他代码可能调用GetObjectData,后者可能返回敏感的数据。另外,其他代码可能构造一个对象,并传入损坏的数据,因此,建议将以下attribute应用于GetObjectData方法和特殊指令

[SecurityPermissionAttribute(SecurityAction.Demand,SerializationFormatter=true]

格式化器序列化一个对象图时,会检查每个对象。如果发现一个对象的类型实现了ISerializable接口,格式化器就会忽略所有定制的attribute,该为构造一个新的SerializationInfo对象,这个 对象包含了要实际为对象序列化的值的集合。

构造一个SerializationInfo对象时,格式化器要传递两个参数:Type和IFormatterConverter。Type参数标识了要序列化的对象。为了唯一的标识一个类型,需要两部分信息:类型的字符串名称以及程序集的标识(包括程序集名称、版本、语言文化和公钥)。一个SerializationInfo对象构造好之后,会包含类型的全名(通过查询Type的FullName属性),并将这个额字符串存储到一个私有字段中。为了获取类型的全名可查询SerializationInfo的FullTypeName属性。类似的,构造器获取类型的定义程序集,并将这个字符串存储到一个私有字段中。为了获取这个程序集标识,可查询SerializationInfo的AssemblyName属性。

构造好并初始化好SerializationInfo对象后,格式化器调用类型的GetObjectData方法,向它传递对SerializationInfo对象的引用。GetObjectData方法负责决定需要哪些信息来序列化对象,并将这些信息添加到SerializationInfo对象中。GetObjectData调用SerializationInfo类型提供的AddValue方法的众多重载版本之一来指定要序列化的信息。针对添加的每个数据,都要调用一次AddValue。

以下代码展示了Dictionary<TKey,Tvalue>类型如何实现ISerializable和IDeserializationCallback接口控制序列化和反序列化:

[Serializable]
    public class Dictionary<TKey, TValue> : ISerializable, IDeserializationCallback
    {
        private SerializationInfo m_siInfo;//只用于反射
        //用于控制反序列化的特殊构造器
        [SecurityPermissionAttribute(SecurityAction.Demand, SerializationFormatter = true)]
        protected Dictionary(SerializationInfo info, StreamingContext context)
        {
            //反序列化期间,为OnDeserialization保存SerializationInfo
            m_siInfo = info;
        }
        //用于控制序列化的方法
        [SecurityCritical]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Version", m_version);
            info.AddValue("Comparer", m_comparer, typeof(IEqualityComparer<TKey>));
            info.AddValue("HashSize", (m_buckets == null) ? 0 : m_buckets.Length);
            if (m_buckets != null)
            {
                KeyValuePair<TKey, TValue> array = new KeyValuePair<TKey, TValue>(count);
                CopyTo(array, 0);
                info.AddValue("KeyValuePairs", array, typeof(KeyValuePair<TKey, TValue>[]));
            }
        }
        //所有key/value对象都反序列化好之后调用的方法
        public virtual void IDeserializationCallback.OnDeserialization(Object sender)
        {
            if (m_siInfo == null) return;
            Int32 num = m_siInfo.GetInt32("Version");
            Int32 num2 = m_siInfo.GetInt32("HashSize");
            m_siInfo.GetValue("Comparer", typeof(IEqualityComparer<TKey>));
            if (num2 != 0)
            {
                m_buckets = new Int32[num2];
                for (Int32 i = 0; i < m_buckets.Length; i++) m_buckets[i] = -1;
                m_entries=new Entry<TKey,TValue>[num2];
                m_freeList = -1;
                KeyValuePair<TKey, TValue>[] pairArray =(KeyValuePair<TKey, TValue>[]);
                m_siInfo.GetValue("KeyValuePairs", typeof(KeyValuePair<TKey, TValue> []));
。。。。。。。。
            }
        }

每个AddValue方法都回去一个String名称和一些数据。数据一般是简单的值类型,比如Boolen,char,byte,sbyte,int16,uint32,int64,uint64,single,double,decimal,datetime.然而,还可以在调用AddValue时向它传递一个Object的引用。GetObjectData添加好所有的序列化信息之后,会返回至格式化器。

现在,格式化器获取已经添加到SerializationInfo对象的所有值,并把他们序列化到流中。注意,我们还向GetObjectData方法传递了另一个参数,也就是对一个StreamingContext对象的引用。大多数的GetObjectData都会忽略这个参数。后面讲讨论它。

知道了如何设置序列化所需的信息之后,再来看看反序列化。格式化器从流中读取一个对象时,会为新对象分配内存(FormatterServices.GetUninitializedObject)。最初,这个对象的所有字段都设为0或null。然后,格式化器检查类型是否实现了ISerializable接口。如果存在这个接口,格式化器就尝试调用一个特殊构造器,它的参数和GetObjectData方法完全一致。

如果你的类是密封类,强烈建议将这个特殊构造器声明为private。这样可防止任何代码不慎调用它,从而提升安全性。如果不是密封类,应该将这个特殊构造器声明为protect,确保之后派生类才能调用它。注意,无论这个特殊的构造函数时如何声明的,格式化器都能调用它。

构造器获取对一个SerializationInfo对象引用。在这个SerializationInfo对象中,包含了对象序列化时添加的所有值。特殊构造器可调用GetBoolean,GetChar,GetBtye,GetSByte,GetUInt16,GetInt32,GetUInt32,GetInt64,GetUInt64,GetSingle,GetDouble,GetDecimal,GetDateTime,GetString和GetValue等任何一个方法,向它传递与序列化一个值所用的名称对用的一个字符串。上述每个方法返回的值再用于初始化新对象的各个字段。

反序列化一个对象字段时,应调用和对象序列化时传递给AddValue方法的值的类型匹配的一个Get方法。换言之,如果GetObjectData方法调用AddValue时传递的一个Int32值,那么在反序列化对象时,应该为同一个值调用GetInt32方法。如果值再流中的类型和你试图获取的类型不符合,格式化器会尝试用一个IFormatterConverter对象将流中的值转型成你指定的类型。

前面说过,构造SerializationInfo对象时,要向它传递类型实现了IFormatterConverter接口的一个对象。由于格式化器负责构造SerializationInfo对象,所以要有他选择它想要的IFormatterConverter类型。微软的  BinaryFormatter,SoapFormatter类型总是构造FormatterConverter类型的一个实例,微软的格式化器没有提供任何方法让你选择一个不同的IFormatterConverter类型。

FormatterConverter类型调用System.Convert类的各种静态方法在不同的核心类型之间对值进行转换,比如将一个Int32转换成Int64。然而,为了在其他任何类型之间转换一个值,FormatterConverter要调用Convert的ChangeType方法将序列化好的类型转型为Iconvertible接口,在调用恰当的接口方法。所以,要允许一个可序列化类型的对象反序列化成一个不同的类型,可考虑让自己的类型实现Iconvertible接口。注意,只有在反序列化对象时调用一个Get方法,但发现它的类型和流中的值的类型不合符时才会使用FormatterConverter对象。

特殊构造器也可以不调用上面列出的各个Get方法,而是调用GetEnumerator。该方法会返回一个System.Runtime.Serialization.SerializationInfoEnumerator对象,可用该对象遍历SerializationInfo对象中包含的所有值。枚举的每个值都是一个System.Runtime.Serialization.SerializationEntry。

当然,你完全可以定义自己的一个类型,让他从实现了ISerializable的GetObjectData方法和特殊构造器一个属性派生。如果你的类型也实现了ISerializable,那么在你的实现的GetObjectData方法和特殊的构造函数中,必须调用基类中的同名方法,确保对象能正确序列化和反序列化,这一点务必记牢,否则对象是不能正确序列化和反序列化的。

如果你的派生类中没有任何额外的字段,因而没有特殊的序列化和反序列化需求,就完全不必实现ISerializable。和所有接口成员相似,GetObjectData是virtual的,调用它可以正确的序列化对象。除此之外,格式化器将特殊的构造函数已虚拟化。换言之,反序列化期间,格式化器会检查要实例化的类型。如果那个类型没有提供特殊构造器,格式化器就会扫描基类,知道它找到了实现特殊构造器的一个类。

重要提示:特殊构造器中的代码一般会从传给他的SerializationInfo对象中提取字段。提取了字段后,不能保证对象以完全序列化,所有特殊构造器中的代码不应尝试操纵它提取的对象。

如果你的类型必须访问提取的一个对象中的成员(比如调用方法),建议你的类型提供一个应用了OnDeserialized的方法,或者让你的类型实现了IDeserializationCallback.OnDeserialization。调用该方法时,所有对象的字段都以设置好,然而,对于多个对象来说,他们额OnDeserialized或OnDeserialization方法的调用顺序是没有保障的。所以,虽然字段可能已经初始化,但你仍然不知道被应用的对象是否已完全序列化好.

如何在基类没有实现ISerializable的前提下定义一个实现他的类型

前面讲过,ISerializable接口的功能非常强大,它允许一个类型完全控制如何对类型的实例进行序列化和反序列化。然而,这个功能是由代价的;现在,该类型还是负责它的基类型的所有字段的序列化。如果基类型也实现了ISerializable接口,那么对基类型的字段进行序列化时很容易的。只需要调用基类型的GetObjectData方法即可。

但有朝一日,你可能要定义一个类型来控制它的序列化,但它的基类没有实现ISerializable接口,在这种情况下,派生类必须手动序列化基类的字段,具体的做法是获取他们的值,并把这些值添加到SerializationInfo集合中。然后,在你的特殊构造器中还必须从结合中取出这些值,并以某种方式设置基类的字段。如果基类的字段时public或protected字段,那么这一切都很容易实现。但是,如果基类字段时private字段,就很难或根本不可能实现。

下面代码演示了正确实现ISerializable接口的GetObjectData方法和它的隐含的构造器,使基类的字段被序列化:

[Serializable]
    internal class Base
    {
        protected String m_name = "Jeff";
        public Base() { }
    }
    [Serializable]
    internal class Derived : Base, ISerializable
    {
        private DateTime m_date = DateTime.Now;
        public Derived()
        {

        }
        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        private Derived(SerializationInfo info, StreamingContext context)
        {
            //为我们的类和基类获取可序列化的成员集合
            Type baseType = this.GetType().BaseType;
            MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);

            //从info对象反序列化基类的字段
            for (Int32 i = 0; i < mi.Length; i++)
            {
                FieldInfo fi = (FieldInfo)mi[i];
                fi.SetValue(this, info.GetValue(baseType.FullName + "+" + fi.Name, fi.FieldType));
            }
            m_date = info.GetDateTime("Date");
        }
        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            //为这个类型希望序列化的值
            info.AddValue("Date", m_date);
            Type baseType = this.GetType().BaseType;
            MemberInfo[] mi = FormatterServices.GetSerializableMembers(baseType, context);
            for (Int32 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);
        }
    }

在上述代码中,有一个名为Base的基类,它只用Serializable定制的attribute进行了标识。从Base派生的是Derived类,它出了应用Serializable标识,还实现了ISerializable接口。为了使局面变的更加有趣,两个类都定义了m_name的一个String字段。调用SerializationInfo的AddValue方法时,不能添加多个同名的值。在上述代码中,解决这个问题的方案实在字段名前附加其类名作为前缀,从而对每个字段进行标识。例如,当GetObjectData方法调用AddValue来序列化Base的m_name字段时,写入的值的名称是Base+m_name。

时间: 2024-10-10 02:22:41

第五节:控制序列化和反序列化的数据的相关文章

Jackson序列化和反序列化Json数据完整示例

Jackson序列化和反序列化Json数据 Web技术发展的今天,Json和XML已经成为了web数据的事实标准,然而这种格式化的数据手工解析又非常麻烦,软件工程界永远不缺少工具,每当有需求的时候就会出现各种类库,框架以及工具来解决这些基础的问题,Jackson就是这些工具中的一个,使用这个工具开发者完全可以从手工结束Json数据的重复劳动中解放出来.使用Jackson首先需要下载相应的类库,如下的Maven dependency列出了完整的POM dependency. 1 <dependen

第三节:控制序列化和反序列化

使用SerializableAttribute这个定制attribute应用于一个类型时,所有实例字段都会被序列化.然而,类型可能定义了一些不应序列化的实例字段.一般情况下,有两个原因促使我们不想对类型的部分实例字段进行序列化. 字段含有反序列化变得无效的信息.例如,假定一个对象包含到一个Window内核对象(如文件.进程.线程.互斥体.事件.信号量等)的句柄,那么在序列化到另一个进程或另一台机器之后,就会失去意义.因为Windows内核对象时跟进程相关的值. 字段含有很容易计算的信息.在这种情

第一节:序列化和反序列化快速入门

static void Main(string[] args) { var objectGraph = new List<string> { "Jeff", "Kristin", "Aidan", "Grant" }; Stream stream = SerializerToMemory(objectGraph); //为了演示,将一切都重置 stream.Position = 0; objectGraph = n

访问修饰限定符的简单总结、final/abstruct/interface对类的限制、自动加载机制、序列化与反序列化【数据持久化和对象的序列化问题】、对象的拷贝(按引用是因为对象标识)和克隆(__clone方法中的this指向)

1.针对访问修饰限定符的理解只需要两点:(1)针对的是类的概念和访问代码的位置来确定是否能够访问(2)对访问修饰限定符的使用时只需要对该成员的使用场景注意即可[也就是内部,继承类,外部进行访问的权限] 不需要对内部进行太多理解[需要对php底层理解时进行理解] [重点][用途]通过访问修饰限定符将内部成员的权限合理的限制,然后再使用公共接口来调用这个基本服务,保证外部不能访问其内部的构件[这样既能够通过类内的设置,将内部的功能实现更好的限制,只有最外层的接口可以正常被访问到,而不了解内部的业务]

序列化和反序列化uint64_t数据

void serializeu64(unsigned long long i,char buf[]) { unsigned long long mask = 0xff00000000000000;//字节掩码位 for(int l = 0;l<8;l++) { auto move = 8-l-1; auto f = i&mask;//取对应字节掩码位的字节数据 char res = (char)(f>>(8*move)); buf[l]=res; mask = mask >

Serializable 指示一个类可以序列化;ICloneable支持克隆,即用与现有实例相同的值创建类的新实例(接口);ISerializable允许对象控制其自己的序列化和反序列化过程(接口)

Serializable : 序列化是指将对象实例的状态存储到存储媒体的过程.在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流.在随后对对象进行反序列化时,将创建出与原对象完全相同的副本. 在 面向对象的环境中实现序列化机制时,必须在易用性和灵活性之间进行一些权衡.只要您对此过程有足够的控制能力,就可以使该过程在很大程度上自动进行.例 如,简单的二进制序列化不能满足需要,或者,由于特定原因需要确定类中那些字段需要序列化. 基本序列化

Java序列化1:序列化、反序列化和transient关键字的作用

网上讲Java序列化的文章很多,感觉很多都讲得不全,这篇文章希望可以全面地剖析Java的序列化机制.为什么要进行序列化和反序列化?我们写了一个Object,但那是Java虚拟机堆内存里面的东西,利用Object进行网络通信.IO操作的时候怎么会认识Java堆内存里面的东西?所以,需要序列化和反序列化机制的保障. 序列化:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来达到持久化的目的. 反序列化:将字节数组重新构造成对象. 默认序列化 序列化只需要实现java.io.Ser

序列化和反序列化[转]

http://tech.meituan.com/serialization_vs_deserialization.html #摘要序列化和反序列化几乎是工程师们每天都要面对的事情,但是要精确掌握这两个概念并不容易:一方面,它们往往作为框架的一部分出现而湮没在框架之中:另一方面,它们会以其他更容易理解的概念出现,例如加密.持久化.然而,序列化和反序列化的选型却是系统设计或重构一个重要的环节,在分布式.大数据量系统设计里面更为显著.恰当的序列化协议不仅可以提高系统的通用性.强健性.安全性.优化系统性

java对象序列化、反序列化

平时我们在Java内存中的对象,是无法进行IO操作或者网络通信的,因为在进行IO操作或者网络通信的时候,人家根本不知道内存中的对象是个什么东西,因此必须将对象以某种方式表示出来,即存储对象中的状态.一个Java对象的表示有各种各样的方式,Java本身也提供给了用户一种表示对象的方式,那就是序列化.换句话说,序列化只是表示对象的一种方式而已.OK,有了序列化,那么必然有反序列化,我们先看一下序列化.反序列化是什么意思. 序列化:将一个对象转换成一串二进制表示的字节数组,通过保存或转移这些字节数据来