在默认情况下如果某个程序集引用了另一个具有强签名的程序集CLR在执行的时候总是会根据程序集文件名、版本和公钥令牌去定位目标程序集。如果无法找到一个与之完全匹配的程序集一般情况下会抛出一个FileNotFoundException类型的异常。如果当前引用的是一个Retargetable程序集则意味着CLR在定位目标程序集的时候可以 “放宽” 匹配的要求即指要求目标程序集具有相同的文件名即可。如下图所示我们的应用程序App引用了具有强签名的程序集“Foobar, Version1.0.0.0, Cultureneutral, PublicKeyTokenb03f5f7f11d50a3a”所以对于编译后生成的程序集App.exe来说对应的程序集引用将包含目标程序集的文件名、版本和公钥令牌。如果在运行的时候只提供了一个有效名称为“Foobar, Version2.0.0.0, Cultureneutral, PublicKeyTokend7fg7asdf7asd7aer”的程序集除了文件名后者的版本号和公钥令牌都与程序集引用元数据描述的都不一样。在默认情况下系统此时总是会抛出一个FileNotFoundException类型的异常倘若Foobar是一个Retargetable程序集我们提供的将作为目标程序集被加载并使用。除了定义程序集的元数据多了如下一个retargetable标记之外Retargetable程序集与普通程序集并没有本质区别。普通程序集span stylecolor:#000000span stylebackground-color:#ffffff.assembly Foobar/span/spanRetargetable程序集span stylecolor:#000000span stylebackground-color:#ffffff.assembly span stylecolor:#ff0000retargetable/span Foobar/span/span这样一个retargetable标记可以通过按照如下所示的方式在程序集上应用AssemblyFlagsAttribute特性来添加。不过这样的重定向仅仅是针对.NET Framework自身提供的基础程序集有效虽然我们也可以通过使用AssemblyFlagsAttribute特性为自定义的程序集添加这样一个retargetable标记但是CLR并不会赋予它重定向的能力。span stylecolor:#000000span stylebackground-color:#ffffff[assembly:AssemblyFlags(AssemblyNameFlags.Retargetable)] /span/span如果某个程序集引用了一个Retargetable程序集自身清单文件针对该程序集的引用元数据同样具有如下所示的retargetable标记。CLR正式利用这个标记确定它引用的是否是一个Retargetable程序集进而确定针对该程序集的加载策略即采用针对文件名、版本和公钥令牌的完全匹配策略还是采用只针对文件名的降级匹配策略。针对普通程序集的引用span stylecolor:#000000span stylebackground-color:#ffffff 1: 针对普通程序集的引用/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: .assembly extern Foobar/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: .publickeytoken (B7 7A 5C 56 19 34 E0 89 ) /span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: .ver 1:0:0:0/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: }/span/span针对Retargetable程序集的引用span stylecolor:#000000span stylebackground-color:#ffffff 1: .assembly extern span stylecolor:#ff0000retargetable/span Foobar/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: .publickeytoken (B7 7A 5C 56 19 34 E0 89) /span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: .ver 1:0:0:0/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: }/span/span类型的转移在进行框架或者产品升级过程我们经常会遇到针对程序集的合并和拆分的场景比如在新版本中需要对现有的API进行从新规划可能会将定义在程序集A中定义的类型转移到程序集B中。但是即使发生了这样的情况我们依然需要为新框架或者产品提供向后兼容的能力这就需要使用到所谓“类型转移Type Forwarding”的特性。为了让读者朋友们对类型转移这个重要的特性具有一个大体的认识我们来作一个简单的实例演示。我们利用Visual Studio创建一个针对.NET Framework 3.5的控制台应用App并在作为程序入口的Main方法中编写了如下两行代码将两个常用的类型String和Func所在的程序集名打印出来。程序编译之后会在 “\bin\Debug” 目录下生成可执行文件App.exe和对应的配置文件App.exe.config。从如下给出的配置文件内容可以看出.NET Framework 3.5采用的运行时CLR版本为 “v2.0.50727” 。span stylecolor:#000000span stylebackground-color:#ffffff 1: class Program/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: static void Main()/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: Console.WriteLine(typeof(string).Assembly.FullName);/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: Console.WriteLine(typeof(Func).Assembly.FullName);/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: }/span/spanApp.exe.configspan stylecolor:#000000span stylebackground-color:#ffffff 1: configuration/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: startup/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: supportedRuntimenbsp;versionspan stylecolor:#ff0000v2.0.50727/span//startup/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: /startup/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: /configuration/span/span现在我们直接以命令行的执行执行编译生成的App.exe后会在控制台上得到如下图所示的输出结果。可以看出对于我们给出的这两个基础类型String和Func只有String类型被定义在程序集mscorlib.dll之中而类型Func其实被定义在另一个叫做System.Core.dll的程序集之中。其实Framework 2.0、3.0和3.5不仅仅共享相同的运行时CLR 2.0对于提供基础类型的核心程序集mscorlib.dll也是共享的下图输出的版本信息已经说明了这一点。也就是说.NET Framework 2.0发布时提供的程序集mscorlib.dll在.NET Framework 3.x时代就没有升级过。Func类型是在.NET Framework 3.5发布时提供的一个基础类型所以不得不将它定义在一个另一个程序集中微软将这个程序集命令为System.Core.dll。现在我们看看.NET Framework 4.0CLR 4.0环境下运行同一个应用程序App.exe是否会有不同的输出结果。为此我们在不对项目做重新编译情况下直接修改配置文件App.exe.config并按照如下所示的方式将运行时版本设置为4.0。span stylecolor:#000000span stylebackground-color:#ffffff 1: configuration/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: startup/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: supportedRuntimenbsp;versionspan stylecolor:#ff0000v4.0/span//span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: /startup/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: /configuration/span/span下图是同一个App.exe在.NET Framework 4.0环境下的输出结果可以看出我们提供的两个基础类型所在的程序集都是mscorlib.dll。也就是当.NET Framework升级到4.0之后不仅仅运行时升级到了全新的CLR 4.0微软同时也对承载基础类型的mscorelib.dll程序集进行了重新规划所以定义在System.Core.dll程序集中的基础类型也基本上又重新回到了mscorlib.dll这个本应该属于它的程序集中。我们来继续分析上面演示的这个程序。由于App.exe这个程序集最初是针对目标框架.NET Framework 3.5编译生成的所以它的清单文件将包含针对mscorlib.dll2.0.0.0和System.Core.dll3.5.0.0的程序集引用。下面的代码片段展示了针对这两个程序集引用的元数据的定义。span stylecolor:#000000span stylebackground-color:#ffffff 1: .assembly extern mscorlib/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: .publickeytoken (B7 7A 5C 56 19 34 E0 89 ) /span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: .ver span stylecolor:#ff00002:0:0:0/span/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: .assembly extern System.Core/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: .publickeytoken (B7 7A 5C 56 19 34 E0 89 ) /span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 9: .ver span stylecolor:#ff00003:5:0:0/span/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 10: }/span/span当App.exe在.NET Framework 4.0环境中运行时由于它的元数据提供的是针对System.Core.dll程序集的引用所以CLR总是试图加载该程序集并从中定位目标类型比如我们演示实例中的类型Func。如果当前运行环境无法提供这个程序集那么毫无疑问一个FileNotFoundException类型的异常会被抛出来。也就是虽然类型Func在.NET Framework 4.0中已经转移到了新的程序集mscorlib.dll中当前环境依然会提供一个文件名为System.Core.dll的程序集。System.Core.dll存在的目的是告诉CLR它需要加载的类型已经发生转移并将该类型所在的新的程序集名称告诉它那么.NET Framework 4.0环境中的System.Core.dll是如何描述类型Func已经转移到程序集mscorelib.dll之中了呢如果分析程序集System.Core.dll中的元数据我们可以看到如下一段于此相关的代码。在程序集的清单文件中每一个被转移的类型都对应这个这么一个 “.class extern forwarder” 指令。span stylecolor:#000000span stylebackground-color:#ffffff 1: .class extern span stylecolor:#ff0000forwarder/span System.Func1/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: .assembly extern mscorlib/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: }/span/span不同于上面介绍的Retargetable程序集类型的转移并不是只针对.NET Framework提供的基础程序集如果我们自己开发的项目也需要提供类似的向后兼容性也可以使用这个特性。针对类型转移类型的编程只涉及到一个类型为TypeForwardedToAttribute的特性接下来我们通过一个简单的实例来演示一下如何利用这个特性将某个类型转移到一个新的程序集中。我们利用Visual Studio创建了如下图所示的解决方案它演示了这样一个场景控制台应用使用到了V1版本的类库Libv1\Lib其中涉及到一个核心类型Foobar。该类库升级到V2版本时我们选择将所有的核心类型统一定义在新的程序集Lib.Core中所以类型Foobar需要转移到Lib.Core中。作为类库的发布者我们希望使用到V1版本的应用能够直接升级到V2版本也就是升级的应用不需要在引用新的Lib.Core程序集情况下对源代码进行重新编译而是直接部署V2版本的两个程序集Lib.dll和Lib.Core就可以了。上图中的虚线箭头和实线箭头分别代表项目之间的引用关系我们从中可以看出v2目录下的Lib项目具有对Lib.Core项目的引用因为它需要引用转移到Lib.Core项目中的类型。为了完成针对类型Foobar的转移我们只需要在v2\Lib中定义如下一行简单的代码就可以了我们将这行代码定义在AssemblyInfo.cs文件中。span stylecolor:#000000span stylebackground-color:#ffffff [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Lib.Foobar))] /span/span为了检验针对Foobar类型的转移是否成功我们在控制台应用App中定义了如下一段程序它负责将Foobar类型当前所在程序集的名称输出到控制台上。接下来我们只需要编译以Debug模式整个解决方案那么V2版本的两个程序集Lib.dll和Lib.Core.dll将保存到\v2\lib\bin\debug\目录下。span stylecolor:#000000span stylebackground-color:#ffffff 1: class Program/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: static void Main()/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: Console.WriteLine(typeof(Foobar).Assembly.FullName); /span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: }/span/span接下来我们采用命令行的形式来运行控制台程序App.exe。如下图所示我们将当前目录切换到App.exe所在的目录\app\bin\debug下并执行App.exe输出的结果表明Foobar类型当前所在的程序集为Lib.dll。接下来我们将针对V2版本的两个程序集拷贝进来后再次执行App.exe我们发现此时的Foobar类型已经是从新的程序集Lib.Core.dll中加载的了。我们顺便来查看一下V2版本程序集Lib.dll的清单文件的内容。如下面的代码片段所示在源代码中通过使用TypeForwardedToAttribute特性定义的类型转移在编译之后被转换成了一个“.class extern forwarder”指令。span stylecolor:#000000span stylebackground-color:#ffffff 1: .assembly extern Lib.Core/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: .ver 1:0:0:0/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: .class extern span stylecolor:#ff0000forwarder/span Lib.Foobar/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: .assembly extern Lib.Core/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 9: …/span/span三、可移植类库PCL在.NET Framework的时代创建可移植类库PCLPortable Class Library是实现跨多个目标框架程序集共享的唯一途径。上面介绍的内容都是在为PCL做铺垫只有充分理解了Retargetable程序集和类型转移的前提下才可能了解PCL的实现原理有正确的理解。考虑到很多读者朋友并没有使用PCL的经历所以我们先来介绍一下如何创建一个PCL项目。 当我们采用Visualization Studio的Class LibraryPortal项目模板创建一个PCL项目的时候需要在如下图所示的对话框中选择支持的目标框架及其版本。Visual Studio会为新建的项目添加一个名为 “.NET” 的引用这个引用指向一个由选定目标框架决定的程序集列表。由于这些程序集提供的API能够兼容所有选择的平台我们在此基础编写的程序自然也具有平台兼容性。