当使用 BinaryFormatter 时,将启用 VTS 功能。VTS 功能尤其是为应用了 SerializableAttribute 特性的类(包括泛型类型)而启用的。 VTS 允许向这些类添加新字段,而不破坏与该类型其他版本的兼容性。
序列化与反序列化过程中如果遇到客户端与服务端程序集不同的情况下,.NET会尽量的进行兼容,所以平时使用过程中对此基本没有太大的感触,甚至有习以为常的感觉。
要确保版本管理行为正确,修改类型版本时请遵循以下规则:
切勿移除已序列化的字段。
如果未在以前版本中将 NonSerializedAttribute 特性应用于某个字段,则切勿将该特性应用于该字段。
切勿更改已序列化字段的名称或类型。
添加新的已序列化字段时,请应用 OptionalFieldAttribute 特性。
从字段(在以前版本中不可序列化)中移除 NonSerializedAttribute 特性时,请应用 OptionalFieldAttribute 特性。
对于所有可选字段,除非可接受 0 或 null 作为默认值,否则请使用序列化回调设置有意义的默认值。
要确保类型与将来的序列化引擎兼容,请遵循以下准则:
始终正确设置 OptionalFieldAttribute 特性上的 VersionAdded 属性。
避免版本管理分支。
六 二进制序列化数据的结构通过前文已经了解了二进制序列化以及版本兼容性的理论知识。接下来有必要对于平时所用的二进制序列化结果进行直观的学习,消除对于二进制序列化结果的陌生感。
6.1 远程调用过程中发送的数据目前我们所使用的.NET微服务框架所使用的正是二进制的数据序列化方式。当进行远程调用的过程中,客户端发给服务端的数据到底是什么样子的呢?
引用文档中一个现成的例子(参考资料4):
上图表示的是客户端远程调用服务端的SendAddress方法,并且发送的是名为Address的类对象,该类有四个属性:(Street = "One Microsoft Way", City = "Redmond", State = "WA" and Zip = "98054") 。服务端回复的是一个字符串“Address Received”。
客户端实际发送的数据如下:
0000 00 01 00 00 00 FF FF FF FF 01 00 00 00 00 00 00 .....ÿÿÿÿ....... 0010 00 15 14 00 00 00 12 0B 53 65 6E 64 41 64 64 72 ........SendAddr 0020 65 73 73 12 6F 44 4F 4A 52 65 6D 6F 74 69 6E 67 ess.oDOJRemoting 0030 4D 65 74 61 64 61 74 61 2E 4D 79 53 65 72 76 65 Metadata.MyServe 0040 72 2C 20 44 4F 4A 52 65 6D 6F 74 69 6E 67 4D 65 r, DOJRemotingMe 0050 74 61 64 61 74 61 2C 20 56 65 72 73 69 6F 6E 3D tadata, Version= 0060 31 2E 30 2E 32 36 32 32 2E 33 31 33 32 36 2C 20 1.0.2622.31326, 0070 43 75 6C 74 75 72 65 3D 6E 65 75 74 72 61 6C 2C Culture=neutral, 0080 20 50 75 62 6C 69 63 4B 65 79 54 6F 6B 65 6E 3D PublicKeyToken= 0090 6E 75 6C 6C 10 01 00 00 00 01 00 00 00 09 02 00 null............ 00A0 00 00 0C 03 00 00 00 51 44 4F 4A 52 65 6D 6F 74 .......QDOJRemot 00B0 69 6E 67 4D 65 74 61 64 61 74 61 2C 20 56 65 72 ingMetadata, Ver 00C0 73 69 6F 6E 3D 31 2E 30 2E 32 36 32 32 2E 33 31 sion=1.0.2622.31 00D0 33 32 36 2C 20 43 75 6C 74 75 72 65 3D 6E 65 75 326, Culture=neu 00E0 74 72 61 6C 2C 20 50 75 62 6C 69 63 4B 65 79 54 tral, PublicKeyT 00F0 6F 6B 65 6E 3D 6E 75 6C 6C 05 02 00 00 00 1B 44 oken=null......D 0100 4F 4A 52 65 6D 6F 74 69 6E 67 4D 65 74 61 64 61 OJRemotingMetada 0110 74 61 2E 41 64 64 72 65 73 73 04 00 00 00 06 53 ta.Address.....S 0120 74 72 65 65 74 04 43 69 74 79 05 53 74 61 74 65 treet.City.State 0130 03 5A 69 70 01 01 01 01 03 00 00 00 06 04 00 00 .Zip............ 0140 00 11 4F 6E 65 20 4D 69 63 72 6F 73 6F 66 74 20 ..One Microsoft 0150 57 61 79 06 05 00 00 00 07 52 65 64 6D 6F 6E 64 Way......Redmond 0160 06 06 00 00 00 02 57 41 06 07 00 00 00 05 39 38 ......WA......98 0170 30 35 34 0B 054.上文的数据是二进制的,能看出来序列化后的结果中包含程序集信息,被调用的方法、使用的参数类、属性及各个属性的值等信息。对于上述的序列化后数据进行详细解读的分析可以参考资料4。
6.2 类对象二进制序列化结果对于类对象进行序列化后的结果没有现成的例子,针对此专门设计了一个简单的场景,将序列化后的数据保存到本地文件中。
/// <summary> /// 自定义序列化对象 /// </summary> [Serializable] public class MyObject { public bool BoolMember { get; set; } public int IntMember { get; set; } } /// <summary> /// 程序入口 /// </summary> class Program { static void Main(string[] args) { var obj = new MyObject(); obj.BoolMember = true; obj.IntMember = 10000; IFormatter formatter = new BinaryFormatter(); Stream stream = new FileStream("data.dat", FileMode.Create, FileAccess.Write, FileShare.None); formatter.Serialize(stream, obj); stream.Close(); } }