CompactFormatterPlus:适用于 Full 和 Compact Framework 的通用序列化器
适用于 Full 和 Compact Framework 的通用序列化器
引言
.NET Framework 的出现是过去 10 年编程界最重要的事件,而且我认为它仍然被低估了。它是迄今为止最稳定、最全面的框架。然而,没有人和事物是完美的。本文将讨论 .NET Framework 通信,特别是紧凑框架通信(序列化)。
通信系统模型
什么是通信框架?通常,通信框架是一个对象传递系统。仅此而已。如果该系统实现了 RPC/RMI,则会在其之上构建另一个层。对象传递系统反过来又只包含两个子层:
- 传输层(物理上将字节流传输到目的地)
- 从字节流到对象的转换器(序列化器)仅此而已。
其余的都可以隐藏在这个理想的通信框架中。
最后一句仅适用于以相同术语描述对象的系统(相同的操作系统、相同的 .NET Framework)。
最小(最佳)通信系统
最小通信系统只有一个方法 Send(Object)
。程序员是否需要关心对象如何转换为字节流,如何由线程管理,如何排队、分片等?不,这是通信框架的职责。
WCF
当我第一次看到 WCF 时,我感觉我错过了什么。这不可能是真的:自动通用序列化消失了!取而代之的是一种半手动、非常受限的序列化。您自己判断:
public class Person
{
public string FirstName;
public string LastName;
public int ID;
ArrayList alist = new ArrayList();
public ClassB _b;
}
在 WCF 中,这样一个原始对象的序列化装饰如下:
[DataContract(Name = "Customer", Namespace = "http://www.contoso.com")]
class Person : IExtensibleDataObject
{
[DataMember()]
public string FirstName;
[DataMember]
public string LastName;
[DataMember()] public int ID;
[DataMember()]
ArrayList alist = new ArrayList();
[DataMember()] public ClassB _b;
private ExtensionDataObject extensionData_Value;
public ExtensionDataObject ExtensionData
{
get
{
return extensionData_Value;
}
set
{
extensionData_Value = value;
}
}
}
大家都喜欢这样吗?显然不是。网络上充斥着博客,建议了许多“聪明”的解决方案来克服 WCF 的限制性。有限的 WCF 序列化被“成功”地通过使用 BinaryFormatter
然后将字节数组作为参数来规避。这是互操作性付出的代价吗?微软显然牺牲了通用性以换取互操作性,仅仅是通过限制功能。这种方法的逻辑极限将是回归到老式套接字通信。因此,大多数 WCF 程序都在 Windows 上运行,拥有通用序列化器作为默认设置,而其余作为选项是否更明智?
什么使通信框架特定于操作系统?
使系统特定于操作系统的唯一因素是格式化器(序列化器)。它是系统中将流转换为对象的唯一组件。流总是相同的。正如雕塑由石头(任何石头)制成一样,对象由流制成。事实上,WCF 拥有所有这些零碎的东西,但系统的组合方式远非完美。
互操作性
正如我之前提到的,序列化器使框架特定于操作系统。反过来,序列化器存在于框架中 [例如 .NET]。如果操作系统不支持框架,甚至没有序列化器的概念怎么办?在这种特定情况下,手动(或半手动)处理字节流是合适的,但这应该是可选的,而不是默认的。
如果你喜欢一切自动化,为什么不使用 Remoting?
标准 Remoting 基于简单的同步通信模型。它是单向的。尝试使用回调进行双工通信在客户端位于代理或 NAT 后面时不起作用,因为 Remoting 会为事件打开第二个连接。由于防火墙是标准的网络组件,我们可以忘记使用 Remoting 进行双向通信。
全部回忆
.NET 标准通信遭受着与 10 年前的 Windows OS 相同的疾病。核心 Windows API 与 10 年前相同,但 10 年前编写 Windows Form 应用程序是一件非常困难的事情。为什么?因为程序员必须手动提供所有参数。为了提供所有参数,他需要了解所有的 Windows 内部机制以及如何使用它们。在 .NET 环境中,所有默认设置都由框架设置。我们从工具箱拖动控件,其余的会自动完成。如果我们想要额外功能,我们可以从属性网格中选择它们或在代码中键入它们。
在现实生活中,我们将对象(信件或包裹)发送给收件人。我们真的关心信件是如何递送的,邮递员的名字或他宠物的名字是什么吗?不,我们不关心。通常我们把信放进邮箱,其余的就由系统默认完成。如果我们想要一些额外功能,例如更好的安全性或送达确认,我们可以额外获得。如果信件递送看起来像 .NET 通信(尤其是 WCF),那么信件递送过程肯定会是这样的:我们从目录中选择运送信件的汽车的车牌号、司机的名字、航班号、飞机的类型、信封的颜色、品牌……。很可能这会阻止我们发送信件。
替代方案
当 IT 行业的高僧们说一切都比以往更好时,通信框架(主要是基于 .NET 套接字的)却不断增多。
The Code Project 网站至少托管了 10 个这样的框架。设计一个功能有限的原始通信类(通常已经足够了)并不难,但主要的障碍是序列化器,特别是对于紧凑框架。
紧凑框架 Remoting
不幸的是,Remoting 和 Binary formatter 没有(而且似乎也不会)为紧凑框架实现。唯一的选择是使用有限的 XMLSerializer
(它不是通用的),并且无法在技术上实现您可以在一端简单地放入对象,在另一端取出的系统。除了序列化器,其他组件如通道或线程在 CF 中都很容易获得。
紧凑框架序列化
CompactFormatterPlus:用于紧凑框架的通用二进制序列化器
这项工作基于 Angelo Scotto 精彩的 CompactFormatter,适用于完整框架和紧凑框架。原始的 CompactFormatter
是为 .NET 1.1 编写的。
然而,岁月流逝,改进的机会也随之出现。重新设计的根本原因如下:
- 紧凑框架 2.0 具有可序列化属性。此属性仅为与完整框架兼容而引入。除此之外,此属性的实际价值是可疑的,因为在此属性引入之前构建的类显然没有它。仅仅依赖此属性会使完全可序列化的类变得不可序列化。原始的
CompactFormatter
具有类似的属性,但它与完整框架不兼容。检查此属性(Angelo 的可序列化属性)已被移除。这使得为完整框架和紧凑框架创建的类兼容。尽管存在一些缺点——完全不可序列化的对象将被尝试序列化。 CompactFormatter
中DataSet
和DataTable
的序列化不完整。SimmoTech 设计的DataSet
序列化要先进得多。Simotech 序列化和代码可以在 The Code Project 文章中找到。这段代码被用作代理整合到CompactFormatter
中。- 为了性能原因,原始
CompactFormatter
将类的索引放入流中,而不是类名。如果序列化器在通信会话期间一致存在,这很好。实际上并非总是如此。序列化器可能会动态实例化,而CompactFormatter
肯定会失败。 - 反序列化时实例化类的方式:
CompactFormatter
使用不完整的程序集名称或完全限定的程序集名称。如果通信平台不同,这两种方法都会失败。例如,当对象在完整框架上序列化而在紧凑框架上反序列化时,完全限定的程序集名称没有意义。只有原始类型和序列化器内部定义的类型才能工作。 - 现在可以使用自动代理生成工具来生成代理。
极致性能和序列化工作室
原始 CompactFormatter
使用代理、重写和自定义序列化来实现更好的性能。这当然是有效的;然而,即使对经验丰富的程序员来说,编写代理也总是一项棘手且繁琐的工作。为了使这项任务更容易,移除了自定义序列化和代理。
现在只有一个自定义序列化——重写。
重写(根据 Angelo Scotto 的说法)是允许为特定类注入自定义序列化模块的原始机制。此外,CompactFormatter
已经被重新设计为包含自动生成的二进制序列化器。序列化工作室是生成超快二进制序列化器(比 BinaryFormatter
快 10-60 倍)的工具。
该工具是免费的,可以从 这里下载。该工具专门针对 CompactFormatterPlus
生成代理进行了优化。
将快速序列化器添加到 CompactFormatterPlus
CompactFormatterPlus cfp = new CompactFormatterPlus();
MyFastSerializer mfs = new MyFastSerializer();
cfp.addFastSerializer(mfs);
如果快速序列化器无法序列化对象怎么办?这种情况的例子可以用下面的代码来说明:
public class MyClass1
{
public int SomeInteger;
public object UnknownObject;
}
如果为该类生成了快速序列化器,则必须在运行时序列化 UnknownObject
,因为它的类型在编译时未知。如果发生这种情况,通用序列化器将接管控制并继续序列化。反过来,如果 CompactFormatterPlus
检测到可以被快速序列化器序列化的字段(类),则控制将递归地返回到快速序列化器。实际上,程序员不必了解序列化的这些复杂性。快速序列化器(如果有)只需添加到 CompactFormatterPlus
即可。快速序列化器的运行时由 FSWriter
和 FSReader
组成。与原始的 Binary reader 和 writer 相比,它有一些额外的方法。它看起来有点丑陋,因为而不是继承 Binary reader(writer)的所有内容,它有自己的所有重载实现。原因是:CF 编译器似乎有一个 bug。重载的 Write (byte) 被评估为 [可能是] Write(double)。[8 字节而不是 1]
Using the Code
在 PC 端,需要序列化的类应作为源代码或库包含在解决方案中。对于库,它必须在 PC 项目中引用。您的类必须具有与 PDA 上的命名空间和类名相同的命名空间名称和类名。以上所有内容均适用于 PDA。
解决方案是独立的。PC 文件不应在 PDA 端使用或引用,反之亦然。通用的是名称。
有时可以在 PC 上使用 CF 文件(EXE 和 DLL),在 PDA 上使用 PC 文件。但这是一种不良做法。它可能会起作用,也可能会出现奇怪且不相关的消息而失败。
典型场景:在调试会话期间,如果在移动解决方案中有 PC 生成的程序集,该文件将把所有引用的程序集拖到 PDA 上,您将耗尽内存。
CompactFormatterPlus 可序列化类型
- 所有原始类型
- 原始类型数组
日期时间
ArrayList
哈希表
List<T>
Dictionary<T Key, T1 Value>
DataSet
DataTable
- 由以上类型组成的复杂对象
致谢
用户 Fabien Castell 改进了类型数据集的序列化以及对象的实例化,如果它们定义在不同的程序集中。代码可以从原始网站下载。
新版本
如果发布了新版本,可以从 这里 下载。