在微软发布了第一个针对桌面和服务器平台的.NET Framework之后它开始 “乐此不疲” 地对这个完整版的.NET Framework进行不同范围和层次的 “阉割” 进而造就了像Windows Phone、Windows Store、Silverlight和.NET Micro Framework的压缩版的.NET Framework。从这个意义上讲Mono和它们并没有本质的区别唯一不同的是Mono真正突破了Windows平台的藩篱。包括Mono在内的这些分支促成了.NET的繁荣但我们都知道这仅仅是一种虚假的繁荣而已。虽然都是.NET Framework的子集但是由于它们采用完全独立的运行时和基础类库这使我们很难开发一个支持多种设备的“可移植Portable”应用这些分支反而成为制约.NET发展的一道道枷锁。至于为什么“可移植Portable”.NET应用的开发如此繁琐呢所谓由于目标框架的独立性意味着不仅仅是作为虚拟机的Runtime是根据具体平台特性设计的作为编程基础的BCL也不能跨平台共享它为开发者带来的一个最大的问题就是很难编写能够在各个目标框架复用的代码。比较极端的场景就是当我们需要为一个现有的桌面应用提供针对移动设备的支持时我们不得不从头到尾开发一个全新的应用现有的代码难以被新的应用所复用用。 “代码复用”是软件设计一项最为根本的目标在不考虑跨平台的前提下我们可以应用相应的设计模式和编程技巧来实现代码的重用但是平台之间的差异导致了跨平台代码重用确实具有不小的困难。虽然作得不算非常的理想但是微软在这方面确实做出了很多尝试我们不妨先来聊聊目前我们都有哪些跨平台代码复用的解决方案。目录一、源代码复用源文件共享文件链接共享项目二、程序集复用程序集一致性Retargetable程序集类型的转移三、可移植类库PCL一、源代码复用对于包括Mono在内的各个.NET Framework平台的BCL来说虽然在API定义层面上存在一些共同之处但是由于它们定义在不同的程序集之中所以在PCLPortal Class Library推出之前针对程序集的共享是不可能实现的我们只能在源代码层面实现共享。源代码的共享通过在不同项目之间共享源文件的方式来实现至于具体采用的方式我们有三种不同的方案供你选择。源文件共享对于一个能够多个针对不同目标框架的项目共享的源文件定义其中的代码也有不少是针对具体某个目标框架的。对于这种代码我们需要按照如下的方式进行编写相应的项目以添加编译的方式选择与自身平台相匹配的代码编译道生成的程序集中。span stylecolor:#000000span stylebackground-color:#ffffff 1: #if WINDOWS/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 2: 针对Windows Desktop/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 3: #elif SILVERLIGHT/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 4: 针对 Silverlight/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 5: #elif WINDOWS_PHONE/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: 针对Windows Phone/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: #else/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: 针对其他平台/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 9: #endif/span/span如果多个针对不同.NET Framework平台的项目文件存在于同一个物理目录下存在于相同目录下的源文件可以同时包含到这些项目中以实现共享的目的。如下图所示两个分别针对Silverlight和WPF的项目共享相同的目录与两个项目文件同在一个目录下的C#文件Shared.cs可以同时被包含到这两个项目之中。文件链接当我们采用默认的方式将一个现有的文件添加到当前项目之中的时候Visual Studio会将目标文件拷贝到项目本地的目录下所以根本起不到共享的目的。但是针对现有文件的添加支持一种叫做“链接”的方式使添加到项目中的文件指向的依然是原来的地址我们可以为多个项目添加针对同一个文件的链接以实现源文件跨项目共享。同样还是上面演示分别针对Silverlight和WPF的两个项目不论项目文件和需要被共享的文件存在于哪个目录下面我们都可以采用如下图所示的添加文件链接的方式分享这个Shared.cs文件。共享项目Shared Project普通项目的目的都是组织源文件和其他相关资源并将它们最终编译成一个可被部署的程序集。但是Shared Project这种项目类型则比较特别它只有对源文件进行组织的功能却不能通过编译生成程序集它存在的目的就是为了实现源文件的共享。对于上面我们介绍的两种源代码的共享方式来说它们都是针对某个单一文件的共享而Shared Project则可以对多个源文件进行打包以实现批量共享。如上图所示我们可以创建一个Shared Project类型的项目Shared.shproj并将需要共享的三个C#文件Foo.cs、Bar.cs和Baz.cs添加进来。我们将针对这个项目的引用同时添加到一个Silverlight项目SilverlightApp.csproj和Windows Phone项目WinPhoneApp.csproj之中当我们对这两个项目实施编译的时候包含在项目Shared.shproj中的三个C#文件会自动作为当前项目的源文件参与编译。二、程序集复用我们采用C#、VB.NET这样的编程语言编写的源文件经过编译会生成有IL代码和元数据构成的托管模块一个或者多个托管模块合并生成一个程序集。程序集的文件名、版本、语言文化和签名的公钥令牌共同组成了它的唯一标识我们将该标识称为程序集有效名称Assembly Qualified Name。除了包含必要的托管模块之外我们还可以将其他文件作为资源内嵌到程序集中程序集的文件构成一个“清单Manifest”文件来描述这个清单文件包含在某个托管模块中。除了作为描述程序集文件构造清单之外描述程序集的元数据也包含在这个清单文件中。程序集使程序集成为一个自描述性Self-Describing的部署单元除了描述定义在本程序集中所有类型之外这些元数据还包括对引用自外部程序集的描述。包含在元数据中针对外部程序集的描述是由编译时引用的程序集决定的引用程序集的名称包含文件名、版本和签名的公钥令牌会直接体现在当前程序集的元数据中。针对程序集引用的元数据采用如下的形式“.assembly extern”被记录在清单文件中我们可以看出被记录下来的不仅包含被引用的程序集文件名“Foo”和“Bar”还包括程序集的版本对于签名的程序集“Foo”来说公钥令牌也一并包含其中。span stylecolor:#000000span stylebackground-color:#ffffff 1: .assembly extern Foo/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/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: .assembly extern Bar/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: {/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: .ver 1:0:0:0/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 9: }/span/span包含在当前程序集清单文件中针对引用程序集的元数据是CLR加载目标程序集的依据。在默认的情况下CLR要求加载与程序集引用元数据完全一致的程序集。具体来说如果引用的是一个未签名的程序集“Bar”那么只要求被加载的程序集具有一致的文件名和版本如果引用的是一个经过签名的程序集那么还要求被加载的程序集具有一致的公钥令牌。在回到《.NET Core跨平台的奥秘[上篇]历史的枷锁》关于.NET多目标框架独立性的问题。虽然不同的目标框架的BCL在API层面具有很多交集但是这些API实际上被定义在不同的程序集中这就导致了在不同的目标框架下共享同一个程序集几乎成了不可能的事情。如果要使跨目标平台程序集复用成为现实就必须要求CLR在加载程序集时放宽“完全匹配”的限制因为针对当前程序集清单文件中描述的某个引用程序集来说在不同的目标框架下可能指向不同的程序集。实际上确实存在这样的一些机制或者策略让CLR加载一个与引用元数据的描述不一致的程序集我们现在就来聊聊这些策略。程序集一致性我们都知道.NET Framework是向后兼容的也就是说原来针对低版本.NET Framework编译生成的程序集是可以直接在高版本CLR下运行的。我们试想一下这么一个问题就一个针对.NET Framework 2.0编译生成的程序集自身来说所有引用的基础程序集的版本在元数据描述中都应该是2.0如果这个程序集在NET Framework 4.0环境下执行CLR在决定加载它所依赖程序集的时候应该选择2.0还是4.0呢我们不妨通过实验来获得这个问题的答案。我们利用Visual Studio创建一个针对.NET Framework 2.0的控制台应用命名为App并在作为程序入口的Main方法上编写如下一段代码。如下面代码片断所示我们在控制台上输出了三个基本类型Int32、XmlDocument和DataSet所在程序集的全名。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(int).Assembly.FullName);/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 6: Console.WriteLine(typeof(XmlDocument).Assembly.FullName);/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 7: Console.WriteLine(typeof(DataSet).Assembly.FullName);/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 8: }/span/spanspan stylecolor:#000000span stylebackground-color:#ffffff 9: }/span/span直接运行这段程序使之在默认版本的CLR2.0下运行会在控制台上输出如下的结果我们会发现上述三个基本类型所在程序集的版本都是2.0.0.0。也就说在这种情况下运行时加载的程序集和编译时引用的程序集是一致的。现在我们在目录“\bin\debug”直接找到以Debug模式编译生成的程序集App.exe并按照如下的形式修改对应的配置文件App.exe.config该配置的目的在于将启动应用时采用的运行时CLR版本从默认的2.0切换到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或者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: requiredRuntimenbsp;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我们会在控制台上得到如下图所示的输出结果可以看到三个程序集的版本全部变成了4.0.0.0也就说真正被CLR加载的这些基础程序集是与当前CLR的版本相匹配的。这个简单的实例体现了这么一个特征运行过程中加载的.NET Framework程序集承载FCL的程序集是由当前运行时CLR决定的这些程序集的版本总是与CLR的版本相匹配。包含在元数据中的程序集信息提供目标程序集的名称而版本则由当前运行的CLR来决定我们将这个重要的机制称为“程序集一致性Assembly Unification”下图很清晰地揭示了这个特性。