Chapter 4 Create Binary Components
创建二进制组件和创建类有很多相似之处:我们都在尝试区分和分割功能。它们的区别在于二进制组件可以用来描述那些离散的功能。我们可以创建组件程序集来实现简单的逻辑共享,跨语言编程和简单的程序发布。
在.Net中程序集是一个组件包。每个程序集都可以被独立的发布和升级。升级一个已发布程序集的简易程度取决于对程序集中最小化耦合的处理是否得当。最小化耦合意味着我们不仅仅要最小化程序集中复杂的依赖关系,还要考虑便于升级版本。这章的主要内容就是如何创建易于使用、发布和更新的程序集。
.Net的运行环境可以支持由多个二进制组件构成的程序集。我们可以独立更新其中的每一个。我们必须清楚CLR(公共语言运行库)是如何寻找和装载程序集的。在创建组件时我们必须遵循这种规则,否则就不能实现二进制组件的效果。下面的篇幅就是介绍这些的。
CLR装载器并不会在程序开始运行时就装载所有的程序集引用。而是在运行需要时再由装载器处理该程序集引用。这可能是由方法调用或者数据访问。装载器寻找程序集引用并装载,实时编译需要的IL。
当CLR需要装载一个程序集时,第一步时检测需要装载的文件。程序集的元数据中记录了它所有的引用。这些记录有强名称和弱名称之分。对于一个强名称程序集这种记录包含4个部分:程序集的名称,版本号,支持的区域性和公钥。对于非强名称程序集来说就只有程序集的名称。使用强名称可以减少被恶意组件替换的可能性。强名称让我们可以使用配置文件设置所需组件的版本。
在检测到正确的程序集名称和版本号之后,CLR会检测这个程序集是否已经被装载过。如果已经装载,那么就它就可以直接应用。否则CLR会继续寻找该程序集。如果该程序集是强名称的CLR首先在GAC(全局程序集缓存)中寻找。如果不在GAC中,装载器会检查配置文件中指定的文件夹。如果这个文件夹存在,那么只在这个文件夹中搜索需要的程序集。如果没有在这个文件夹中找到所需的程序集,那么装载失败。
如果配置文件中没有指定文件夹,那么装载器将搜索之前已定义的文件夹:
-
应用程序文件夹。也就是主应用程序集所在的位置。
-
特定性区域文件夹。这是在应用程序文件夹下的一个子文件夹。这个文件夹名称和目前的特定性区域相匹配。
-
程序集子文件夹。该文件夹的名称和程序集相匹配。它们以[特定性区域]/[程序集名称]方式组合。
-
私有的bin目录。这个目录是在程序配置文件中定义的私有目录。它以[bin目录]/[程序集名称]、[bin目录]/[特定性区域]或者[bin目录]/[特定性区域]/[程序集名称]来命名。
有三件事我们应当注意。首先,只有强名称程序集可以被储存在GAC中。其次,我们可以使用配置文件来修改强名称程序集中默认的更新行为。最后,强名称程序集可以更好的保护我们的程序免受外来恶意篡改的威胁。
通过介绍CLR如何装载程序集,我们应该考虑如何创建可以升级的程序集。首先我们应当考虑创建强名称程序集,并填写元数据记录。当我们使用VS.Net创建一个项目时,我们应当填写assemblyInfo.cs中的所有属性,包括完整的版本号。这使得组件更容易更新。assemblyInfo.cs中包含三个不同的部分。第一部分是主要信息:
[assembly: AssemblyDescription("This is the sample assembly")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug")]
#else
[assembly: AssemblyConfiguration("Release")]
#endif
[assembly: AssemblyCompany("My Compay")]
[assembly: AssemblyProduct("It is part of a product")]
[assembly: AssemblyCopyright("Insert legal text here")]
[assembly: AssemblyTrademark("More legal text")]
[assembly: AssemblyCulture("en-US")]
最后一项AssemblyCulture只对本地化程序集有效。如果程序集中不包含本地化资源,就应该让该项为空。用来描述区域性名称的字符遵照RFC 1766标准。
第二部分包含了版本号。在VS.Net中的格式是这样的:
AssemblyVersion包含四部分:<主版本>.<次版本>.<内部版本号>.<修订号>。星号标志表示内部版本号和修订号由编译器通过当前日期和时间填充。内部版本号的值是当前日期与2000年1月1日相差的天数。修订号是当前时间和今日0点之间相差的秒数除2这个算法可以保证内部版本号和修订号是不断增长的:每次重新编译的版本号都比之前的大。
这种算法的好处在于不会有两个版本号时相同的。缺点在于当我们发布时需要记录内部版本号和修订号(因为它们不是很规则)。作者在这里推荐让编译器生成内部版本号和修订号。通过记录内部版本号我们就可以得到最终的版本信息。当我们发布新版本程序集时不要忘记修改版本号。
最后一部分包含强名称信息:
[assembly: AssemblyKeyFile("")]
[assembly: AssemblyKeyName("")]
我们应当创建强名称程序集。强名称程序集更加安全,而且独立易于升级。不过我们应当避免在ASP.Net应用程序中使用强名称。这种部分安装不能被恰当的加载。另外强名称程序集需要使用AllowPartiallyTrustedCallers属性,否则它将不能被非强名称程序集访问。
当我们升级组件时,它们的共有和受保护部分的接口必须在IL层兼容。这就意味着不能删除方法、修改参数或者修改返回值。这是为了让所有引用到该组件的组件都不需要重新编译。
我们可以通过配置信息来变更引用的程序集。配置信息可以存储在三个不同的位置,这取决于我们期望升级组件的方式。对于简单的应用程序,我们可以创建应用程序配置文件来对它进行设置。如果要配置所有引用到该组件的应用程序,我们需要在GAC中创建发布者策略文件。如果需要进行全局性的配置,我们需要编辑machine.config文件,它位于.Net运行库安装路径下的Config 目录中。
在实际应用中,我们从不需要修改machine.config来配置程序集。这个文件包含了应用于整个计算机的设置。我们使用应用程序配置文件来升级单独的应用程序。对于被多个应用程序共享的组件,我们使用发布者策略文件来配置升级。
配置文件包含了一段用于描述现有版本号和升级版本号的xml。
<assemblyIdentity name="MyAssembly" publicKeyToken ="a0231341ddcfe32b" culture="neutral" />
<bindingRedirect oldVersion="1.0.1444.20531" newVersion="1.1.1455.20221" />
</dependentAssembly>
我们可以使用配置文件来识别现有的和将要升级的程序集。当我们发布升级版本时我们可以升级或者创建适当的配置文件,应用程序就会使用新版本的组件。
我们可以把软件想像成程序集的集合:我们可以单独升级其中的每一个部分。我们需要预先做一些工作来让软件在第一次安装时包含必要的信息,以支持这种程序集单独升级的方式。
译自 Effective C#:50 Specific Ways to Improve Your C# Bill Wagner著
回到目录