假设这样一种场景,某公司推出了两款软件(分别叫A1和A2),这两款软件均引用了同一个公共的程序集(Common.dll),同时A1和A2均使用到了Common.dll中的GetMessage1()方法,现在用户将这两款软件分别安装到自己的系统中。此时公共的程序集被拷贝到用户的某个路径下,这样它可以被两个软件同时引用。过了一段时间后,该公司对软件A2进行升级,升级过程中需要修改Common.dll中的GetMessage1()的函数名,将其更名为GetMessage2()。当用户下载A2软件进行安装时,Common.dll会同时被下载安装到之前的公用文件夹下并将原来版本的Common.dll覆盖。现在问题来了,A2在用户那里可以正常运行,但A1却不行。因为Common.dll中的GetMessage1()已经不存在,所以在运行A1的时候会抛出异常。
下面用代码模拟上面出现的情况。
一开始我们新建一个Common.dll的程序集:
public static class Tool { public static string GetMessage1() { return "Hello world!"; } }
再新建一个名为T1的Console程序,我们将刚才生成Common.dll拷贝到A1的bin目录,并且让A1添加对其引用:
namespace A1 { class Program { static void Main(string[] args) { Console.WriteLine(Common.Tool.GetMessage1()); } } }
编译改程序后将A1.exe已经Commo.dll拷贝到另一个单独的文件夹(MyFolder)中,此时A1可以正常运行。
然后,我们按照同样的方式新建一个A2的Console程序,同样添加对Common.dll的引用:
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace A2 { class Program { static void Main(string[] args) { Console.WriteLine(Common.Tool.GetMessage1()); Console.ReadLine(); } } }
此时,我们将A2和Common.dll拷贝到MyFolder中。此时在MyFolder下分别运行A1和A2,发现他们均能正常运行。
现在我们要模拟软件A2升级。我们将刚才的Commo.dll程序代码中的GetMessage1()更名为GetMessage2(),同时将A2中的代码改为调用GetMessage2()函数。分别编译Common.dll和A2项目。
Common.dll代码:
namespace Common { public static class Tool { public static string GetMessage2() { return "Hello world!"; } } }
A2代码改为:
namespace A2 { class Program { static void Main(string[] args) { Console.WriteLine(Common.Tool.GetMessage2()); Console.ReadLine(); } } }
分别将Common和A2编译后将Common.dll和A2.exe拷贝/覆盖到MyFolder中。此时在MyFolder下我们会发现A2能正常运行,A1却抛出异常:
原因很简单,因为在对A2升级的时候,GetMessage1已经被改名为GetMessage2,那么A1运行时当然会抛出异常。
那么如何解决这个问题呢?答案是:我们可以对Common进行版本控制,同时对其强类型命名,然后将Common部署到GAC中。每次对Common做改动/升级,我们对增加一个版本号,不同的软件引用不同的版本。虽然Common的名字还是没变,但版本号却在每次发布的时候有变化,对于GAC来说他们就是不同的程序集了。
现在我们回到最开始的状态,此时Common中的函数名仍然为GetMessage1,我们对该程序集进行强类型签名。步骤是:
1)右键程序集选择属性(properties),然后选择签名(Signing)
2)勾选签名这个程序集(Sign the Assembly),并在下拉列表中选择新建
3)在弹出的框中给key起一个名字,在单击ok
这样我们就给Common.dll签上了强类型名称,接下来需要将Common.dll部署到GAC中。我们可以运用VS提供的命令行工具然后用gactuil -i命令将其部署到GAC。
然后按照同样的步骤,让A1和A2均添加对Common.dll的引用,代码和上文中一开始的地方一模一样。
将A1和A2拷贝到MyFolder中,此时因为A1和A2添加了Common.dll的引用,而Common.dll已经部署到GAC中,所以不需要将Common.dll拷贝到MyFolder中,因为A1和A2会从GAC中找到该程序集。此时A1和A2完全能正常运行。
现在我们要做的是,模拟A2升级,并且对Common.dll进行修改。
还是将Common中的GetMessage1改为GetMessage2。然后打开Common的AssemblyInfo,将[assembly: AssemblyVersion("1.0.0.0")]改为[assembly: AssemblyVersion("2.0.0.0")]。这样做的目的是让改Common变为2.0,区别之前的版本。同样将该Common部署到GAC中。
同样在A2中添加对新版本的Common的引用,将A2的代码更新为使用GetMessage2版本。
namespace A2 { class Program { static void Main(string[] args) { Console.WriteLine(Common.Tool.GetMessage2()); Console.ReadLine(); } } }
然后将A2.exe拷贝到MyFolder下。现在在MyFloder下A1.exe引用的是GetMessage1而A2.exe使用的是GetMessage2,但她们却引用的是同名的Common.dll,只是A1和A2引用的不同版本的Common.dll。这是A1和A2均可正常运行。
而在GAC中,我们可以发现有两个版本的Common.dll