XGenPlus - 一个灵活的工具,用于为您的 .NET 应用程序生成类型化的 XML 序列化器






4.70/5 (10投票s)
XGenPlus 是一个灵活的工具,用于为您的 .NET 应用程序生成类型化的 XML 序列化器。它比 sgen.exe 工具提供了更大的灵活性,同时结合了 Mvp.Xml.Xgen 库提供的效率。
引言
如果您曾在项目中大量使用 XML 序列化,那么您可能在将事情弄对之前抓耳挠腮过几回。
内存泄漏
当您使用 XmlSeializer
来序列化或反序列化对象时,XmlSerializer
会即时创建一个动态程序集,其中包含针对该对象类型的特定序列化代码。例如,当您执行类似以下操作时:
XmlSerializer ser=new XmlSerializer(typeof(Customer))
现在,在后台
XmlSerializer
构造函数将反射您传递到XmlSerializer
构造函数的客户类型,并为之生成一个类型化序列化器的代码。- 类型化序列化器
Customer
类型的代码通过在运行时调用编译器服务进行编译。 - 包含类型化序列化器
Customer
类型的缓存程序集被加载到应用程序域,并被缓存以供将来使用。
然而,XmlSerializer
有一个已知的问题——一些 XMLSerializer
构造函数(除简单的构造函数外)会每次重新生成类型化序列化器程序集,而不是从缓存中检索。只有这两个构造函数在后续调用时会从缓存中获取序列化器:
-
System.Xml.Serialization.XmlSerializer(Type)
-
System.Xml.Serialization.XmlSerializer(Type,String)
换句话说,XmlSerializer
并非在所有构造函数中都使用缓存机制。例如,假设您在 Web 应用程序中调用 XmlSerializer
。如果您使用 XmlSerializer
中任何一个重载的、功能丰富的构造函数,您将会耗尽内存。
XmlSerializer serializer =
new XmlSerializer(typeof(Customer), new XmlRootAttribute(""));
每次调用都会创建一个新的 Serializer
,如果您在 Web 应用程序中有这段代码,该应用程序预计会扩展到相当数量的用户,您很快就会耗尽内存。用微软自己的话说,“如果您使用其他任何构造函数,相同程序集的多个版本将被生成且永不卸载,从而导致内存泄漏和性能低下”(引用自 XmlSerializer
的 MSDN 文档)。我们仍然不知道这是否是“按设计”的,或者这是 XmlSerializer
的一个 bug。
糟糕的启动性能
即使您计划使用简单的构造函数,仍然会导致运行时生成类型化序列化器,至少在 XmlSerializer
首次使用特定类型初始化时是这样。解决方案是什么?在编译应用程序之前生成类型化序列化器——这样您就不必担心运行时类型化序列化器的生成和内存泄漏了。
正确处理
当我们遇到非类型化 XML 序列化问题时,坦白说,我没想到我们会开发一个用于创建类型化序列化器的实用工具。我们当时正在处理一个涉及大量 XML 序列化和反序列化项目的性能增强。最初,我们决定使用 Microsoft Sgen 来创建类型化序列化器,但我们遇到了一些问题。
- 您无法为选定的一组类型生成类型化序列化器。
- 生成类型化序列化器程序集后,您需要从项目中创建对它们的强引用。
- 未能处理某些场景。
我们遇到的另一个工具是 Mvp.Xml.Xgen
,它可以帮助您在设计时创建类型化序列化器。您可以将其配置为从 Visual Studio 作为自定义任务运行。然而,
- 我们不希望将序列化器作为主程序集的一部分。
- 我们需要更大的灵活性来选择生成序列化器的类型。
- 我们希望以一种松耦合的方式调用序列化器,而无需对现有代码进行任何重大更改。
XGenPlus 简介
结果,我们推出了一款名为 XGenPlus
的小型工具,它主要结合了 SGen
和 Mvp.Xml.Xgen
的优点。以下是一些特性:
- 提供一组命令行选项,用于为程序集中的所有类型或选定类型的类型生成类型化序列化器库。
- 允许程序员在不实际直接引用类型化序列化器库的情况下创建类型化序列化器。为此,可以使用
XGenPlus.SerializerLib
。 - 提供了一个
MSBuild
任务,用于将XGenPlus
集成到您的生成脚本中。 - 您可以使用配置文件运行
XGenPlus
。
从命令行使用 XGenPlus
以下是它功能的简要概述:
Usage: XGenPlus /assembly:assemblyname [/exclude:namespace1,namespace2]
[/include:namespace1,namespace2] [/reference:assembly1,assembly2]
[/copyto:path] [/nocompile] [/nogenerate] [/getconfig:filename]
[/putconfig:filename] [/serializeall] [/from:namespace]
• /assembly:assemblyname - To specify the assembly
• /exclude:namespace1,namespace2 – Exclude types in the specified namespaces
• /include:namespace1,namespace2 - Include types in the specified namespaces
• /reference:assembly1,assembly2 - Specify reference assemblies
• /nocompile - Won't compile the source files generated
• /nogen - Won't generate any source files
• /getconfig:filename - Run the application using the configuration specified
in the filename
• /putconfig:filename - Write the current configuration
(passed over command line) to the filename
• /copyto:path - Copy the generated assembly to the path
• /serializeall - Generate serializers for all types,
not only for classes with System.Serializable attribute applied
• /from:namespace - Generate serializers for types in namespaces
starting from the specified namespace.
Generated assembly name will be the namespace.dll
大多数开关都可以用短名称表示。例如,您可以使用 /a
代替 assembly
,使用 /i
代替 include
。
使用示例
以下是一些示例:
案例 1:以下命令将为以命名空间 SomeName.Objects.DTO
开头的类型生成序列化器。生成的程序集名称将是 SomeName.Objects.DTO.dll。
xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll
/i:SomeName.Objects.DTO.CalcUI
案例 2:以下命令将为以命名空间 SomeName.Objects.DTO
开头的类型生成序列化器,并排除其他所有内容。这还将生成一个名为 SomeName.Objects.config
的配置文件。
xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll
/i:SomeName.Objects.DTO. CalcUI /putconfig:SomeName.Objects.config
案例 3:一旦 config 文件就位,以下命令将与上述命令具有相同效果。参数将从 config 文件中获取。
xgenplus /getconfig:SomeName.Objects.config
案例 4:以下命令将仅根据命令行参数生成配置文件。
xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll
/e:SomeName.Objects.DTO.State /putconfig:SomeName.Objects.config /nogen /nocomp
案例 5:默认情况下,仅为标记为 System.Serializable
特性的类型生成序列化器。您可以使用 /serializeall
开关来为所有类生成序列化器。
xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll
/i:SomeName.Objects.DTO.CalcUI /serializeall
案例 6:以下命令将生成一个名为 SomeName.Objects.DTO.CalcUI.dll 的序列化器 DLL。
xgenplus /a:SomeName.Objects.DTO.dll /r:SomeName.Objects.Messages.dll
/f:SomeName.Objects.DTO.CalcUI
将 XGenPlus 用作 MSBuild 任务
将 XGenPlus
用作 Microsoft Build 的任务非常简单。XGenPlus.exe 中的 XGenPlusTask
类可以直接从生成配置中使用。用以下信息修改您的项目文件:
确保 UsingTask
声明引用了 XGenPlus
所在的正确路径。
<UsingTask TaskName="XGenPlusTask" AssemblyFile="YourPath\XGenPlus.exe" />
然后在适当的部分(在此例中为 BeforeBuild
)定义任务:
<Target Name="BeforeBuild">
<XGenPlusTask AssemblyName="bin\debug\SomeName.Objects.DTO.dll"
NoGenerate="false" NoCompile="false"
IncludeList="SomeName.Objects.dto.calcui;SomeName.Objects.dto.cobrowse"
ReferenceList="bin\debug\SomeName.Objects.messages.dll;System.Data.dll" /> </Target>
如何使用类型化序列化器
第 1 步 - 使用 XGenPlus
为您的对象生成序列化器程序集,并将生成的 *.Serializer.dll 文件放置在应用程序的 bin 文件夹中。
第 2 步 - 一旦您的序列化器库位于 bin 文件夹中,以下对 XGenPlus.SerializerLib
中 FactoryProxy
类的调用将返回您类型的类型化序列化器(确保您的项目中有一个对 XGenPlus.SerializerLib
的引用)。
XmlSerializer ser = FactoryProxy.GetSerializer(typeof(yourtype));
在后台,XGenPlus.SerializerLib
将完成所有其他工作。
XGenPlus 内部
很棒,不是吗?现在让我们非常简要地看一下 XGenPlus
项目。当您调用 XGenPlus
时,它会做什么?它将类型传递给 XmlSerializer
的实例,然后窃取 XmlSerializer
生成的代码 :)。很简单,不是吗?然而,为了让事情如我们所愿地工作,还需要其他一些东西。
XGenPlus
Runner
类有一个 static
方法 InvokeRunnerInOwnAppDomain
,它实际上使用反射创建一个 Runner
类的实例——然后逐个迭代类型以调用 XmlSerializer
,并将该类型传递给它——以“窃取”由 XmlSerializer
生成的代码。
请注意,为了窃取 XmlSerializer
生成的类型化序列化器的代码,我们应该做一些“坏事”。我们应该修改 config 文件以添加一个开关 XmlSerialization.Compilation
,以告知 XmlSerializer
在生成类型化序列化器的过程中,应保留其生成的临时文件!!
<configuration> <system.diagnostics>
<switches>
<add name='XmlSerialization.Compilation' value='4'/>
</switches>
</system.diagnostics>
</configuration>
InvokeRunnerInOwnAppDomain
实际上是通过 Program
类(如果您从命令行调用 XGenPlus
)或通过 XGenPlusTask
类(如果您将 XGenPlus
作为 MSBuild 任务调用)来调用的。Runner
类中的 GenerateAndCompile
方法实际上完成了基础工作(创建文件夹、将引用库加载到应用程序域等),并调用 XmlSerializerGenerator
类中的 GenerateCode
方法。
除了窃取 XmlSerializer
创建的代码之外,我们还在生成的序列化器库中为工厂类生成代码。我们从 XmlSerializer
窃取的代码以及我们为工厂类生成的代码被一起编译,形成序列化器库。
XGenPlus.SerializerLib
我们生成的所有工厂都实现了 XGenPlus.SerializerLib
库中的 ISerializerFactory
接口。此外,XGenPlus.SerializerLib
库提供了一种便捷的方式,让您无需直接引用 XGenPlus
生成的序列化器程序集即可创建序列化器。(您的项目中可能包含多个序列化器程序集,不是吗?)
这里的关键是 FactoryCache
类——它有一个 static
构造函数,该构造函数会从应用程序的执行路径中的 *.Serializer.dll 文件加载所有工厂。您可以选择使用 config 文件中的 SerializerDllPath
设置来指定已生成序列化器程序集的位置。最后,XGenPlus.SerializerLib.FactoryProxy
中的 GetFactory
方法将根据您的类型名称找到相应的工厂,调用相应的类型化序列化器,并可能将其返回给您。
请随意下载本文附带的源代码和二进制文件。
XGenPlus
项目维护在 CodePlex 上,并根据 GPL 分发。请访问此链接获取更新。
历史
- 2007年11月12日:初始发布