查看: 129|回复: 0

.NET Core跨平台的奥秘[中篇]:复用之殇

[复制链接]

该用户从未签到

发表于 2019-11-4 16:57:32 | 显示全部楼层 |阅读模式
在《.NET Core跨平台的奥秘[上篇]:历史的枷锁》中我们谈到:由于.NET是创建在CLI这一标准的规范之上,所以它天生就具有了“跨平台”的基因。在微软发布了第一个针对桌面服务器平台的.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定义层面上存在一些共同之处,但是由于它们定义在差别的步调集之中,所以在PCL(Portal Class Library)推出之前,针对步调集的共享是不可能实现的,我们只能在源代码层面实现共享。源代码的共享通过在差别项目之间共享源文件的方式来实现,至于具体采用的方式,我们有三种差别的方案供你选择。
源文件共享

对于一个能够多个针对差别目标框架的项目共享的源文件,定义其中的代码也有不少是针对具体某个目标框架的。对于这种代码,我们必要按照如下的方式进行编写,相应的项目以添加编译的方式选择与自身平台相匹配的代码编译道生成的步调会合。
  1.    1: #if WINDOWS
复制代码
  1.    2:     
复制代码
  1.    3: #elif SILVERLIGHT
复制代码
  1.    4:     
复制代码
  1.    5: #elif WINDOWS_PHONE
复制代码
  1.    6:     
复制代码
  1.    7: #else
复制代码
  1.    8:     
复制代码
  1.    9: #endif
复制代码
如果多个针对差别.NET Framework平台的项目文件存在于同一个物理目次下,存在于相同目次下的源文件可以同时包含到这些项目中以实现共享的目的。如下图所示,两个分别针对SilverlightWPF的项目共享相同的目次,与两个项目文件同在一个目次下的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”)来说,公钥令牌也一并包含其中。
  1.    1: .assembly extern Foo
复制代码
  1.    2: {
复制代码
  1.    3:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        
复制代码
  1.    4:   .ver 1:0:0:0
复制代码
  1.    5: }
复制代码
  1.    6: .assembly extern Bar
复制代码
  1.    7: {
复制代码
  1.    8:   .ver 1:0:0:0
复制代码
  1.    9: }
复制代码
包含在当前步调集清单文件中针对引用步调集的元数据是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)所在步调集的全名。
  1.    1: class Program
复制代码
  1.    2: {
复制代码
  1.    3:     static void Main()
复制代码
  1.    4:     {
复制代码
  1.    5:         Console.WriteLine(typeof(int).Assembly.FullName);
复制代码
  1.    6:         Console.WriteLine(typeof(XmlDocument).Assembly.FullName);
复制代码
  1.    7:         Console.WriteLine(typeof(DataSet).Assembly.FullName);
复制代码
  1.    8:     }
复制代码
  1.    9: }
复制代码
直接运行这段步调使之在默认版本的CLR(2.0)下运行会在控制台上输出如下的结果,我们会发现上述三个基本范例所在步调集的版本都是2.0.0.0。也就说在这种情况下,运行时加载的步调集和编译时引用的步调集是一致的。

如今我们在目次“\bin\debug”直接找到以Debug模式编译生成的步调集App.exe,并按照如下的情势修改对应的设置文件(App.exe.config),该设置的目的在于将启动应用时采用的运行时(CLR)版本从默认的2.0切换到4.0。
  1.    1:
复制代码
  1.    2:   
复制代码
  1.    3:     
复制代码
  1.    4:   
复制代码
  1.    5:
复制代码
或者:
  1.    1:
复制代码
  1.    2:   
复制代码
  1.    3:     
复制代码
  1.    4:   
复制代码
  1.    5:
复制代码
无需重新编译(确保运行的依然是同一个步调集)直接运行App.exe,我们会在控制台上得到如下图所示的输出结果,可以看到三个步调集的版本全部变成了4.0.0.0,也就说真正被CLR加载的这些基础步调集是与当前CLR的版本相匹配的。

这个简单的实例体现了这么一个特性:运行过程中加载的.NET Framework步调集(承载FCL的步调集)是由当前运行时(CLR)决定的,这些步调集的版本总是与CLR的版本相匹配。包含在元数据中的步调集信息提供目标步调集的名称,而版本则由当前运行的CLR来决定,我们将这个重要的机制称为“步调集一致性(Assembly Unification)”,下图很清晰地揭示了这个特性。
  
Retargetable步调集

在默认情况下,如果某个步调集引用了另一个具有强署名的步调集,CLR在实行的时候总是会根据步调集文件名、版本和公钥令牌去定位目标步调集。如果无法找到一个与之完全匹配的步调集,一般情况下会抛出一个FileNotFoundException范例的异常。如果当前引用的是一个Retargetable步调集,则意味着CLR在定位目标步调集的时候可以 “放宽” 匹配的要求,即指要求目标步调集具有相同的文件名即可。
如下图所示,我们的应用步调(App)引用了具有强署名的步调集“Foobar, Version=1.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a”,所以对于编译后生成的步调集App.exe来说,对应的步调集引用将包含目标步调集的文件名、版本和公钥令牌。如果在运行的时候只提供了一个有效名称为“Foobar, Version=2.0.0.0, Culture=neutral, PublicKeyToken=d7fg7asdf7asd7aer”的步调集,除了文件名,后者的版本号和公钥令牌都与步调集引用元数据描述的都不一样。在默认情况下,系统此时总是会抛出一个FileNotFoundException范例的异常,倘若Foobar是一个Retargetable步调集,我们提供的将作为目标步调集被加载并使用。

除了定义步调集的元数据多了如下一个retargetable标记之外,Retargetable步调集与普通步调集并没有本质区别。
普通步调集:   
  1. .assembly Foobar
复制代码
Retargetable步调集:  
  1. .assembly [color=#ff0000]retargetable[/color] Foobar
复制代码
这样一个retargetable标记可以通过按照如下所示的方式在步调集上应用AssemblyFlagsAttribute特性来添加。不过这样的重定向仅仅是针对.NET Framework自身提供的基础步调集有效,虽然我们也可以通过使用AssemblyFlagsAttribute特性为自定义的步调集添加这样一个retargetable标记,但是CLR并不会赋予它重定向的能力。
  1. [assembly:AssemblyFlags(AssemblyNameFlags.Retargetable)]
复制代码
如果某个步调集引用了一个Retargetable步调集,自身清单文件针对该步调集的引用元数据同样具有如下所示的retargetable标记。CLR正式使用这个标记确定它引用的是否是一个Retargetable步调集,进而确定针对该步调集的加载策略,即采用针对文件名、版本和公钥令牌的完全匹配策略,照旧采用只针对文件名的降级匹配策略。
针对普通步调集的引用:
  1.    1: 针对普通步调集的引用
复制代码
  1.    2: .assembly extern Foobar
复制代码
  1.    3: {
复制代码
  1.    4:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        
复制代码
  1.    5:   .ver 1:0:0:0
复制代码
  1.    6: }
复制代码
针对Retargetable步调集的引用:
  1.    1: .assembly extern [color=#ff0000]retargetable[/color] Foobar
复制代码
  1.    2: {
复制代码
  1.    3:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89)                        
复制代码
  1.    4:   .ver 1:0:0:0
复制代码
  1.    5: }
复制代码
范例的转移

在进行框架或者产品升级过程,我们常常会遇到针对步调集的合并和拆分的场景,比如在新版本中必要对现有的API进行从新规划,可能会将定义在步调集A中定义的范例转移到步调集B中。但是纵然发生了这样的情况,我们依然必要为新框架或者产品提供向后兼容的能力,这就必要使用到所谓“范例转移(Type Forwarding)”的特性。
为了让读者朋友们对范例转移这个重要的特性具有一个大要的熟悉,我们来作一个简单的实例演示。我们使用Visual Studio创建一个针对.NET Framework 3.5的控制台应用App,并在作为步调入口的Main方法中编写了如下两行代码将两个常用的范例(StringFunc)所在的步调集名打印出来。步调编译之后会在 “\bin\Debug” 目次下生成可实行文件App.exe和对应的设置文件App.exe.config。从如下给出的设置文件内容可以看出.NET Framework 3.5采用的运行时(CLR)版本为 “v2.0.50727” 。
  1.    1: class Program
复制代码
  1.    2: {
复制代码
  1.    3:     static void Main()
复制代码
  1.    4:     {
复制代码
  1.    5:         Console.WriteLine(typeof(string).Assembly.FullName);
复制代码
  1.    6:         Console.WriteLine(typeof(Func).Assembly.FullName);
复制代码
  1.    7:     }
复制代码
  1.    8: }
复制代码
App.exe.config
  1.    1:
复制代码
  1.    2:   
复制代码
  1.    3:     
复制代码
  1.    4:   
复制代码
  1.    5:
复制代码
如今我们直接以下令行的实行实行编译生成的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.0(CLR 4.0)情况下运行同一个应用步调(App.exe)是否会有差别的输出结果。为此我们在不对项目做重新编译情况下直接修改设置文件App.exe.config,并按照如下所示的方式将运行时版本设置为4.0。
  1.    1:
复制代码
  1.    2:   
复制代码
  1.    3:     
复制代码
  1.    4:   
复制代码
  1.    5:
复制代码
下图是同一个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.dll(2.0.0.0)和System.Core.dll(3.5.0.0)的步调集引用。下面的代码片断展示了针对这两个步调集引用的元数据的定义。
  1.    1: .assembly extern mscorlib
复制代码
  1.    2: {
复制代码
  1.    3:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        
复制代码
  1.    4:   .ver [color=#ff0000]2:0:0:0[/color]
复制代码
  1.    5: }
复制代码
  1.    6: .assembly extern System.Core
复制代码
  1.    7: {
复制代码
  1.    8:   .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )                        
复制代码
  1.    9:   .ver [color=#ff0000]3:5:0:0[/color]
复制代码
  1.   10: }
复制代码
当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” 指令。
  1.    1: .class extern [color=#ff0000]forwarder[/color] System.Func`1
复制代码
  1.    2: {
复制代码
  1.    3:   .assembly extern mscorlib
复制代码
  1.    4: }
复制代码
差别于上面先容的Retargetable步调集,范例的转移并不是只针对.NET Framework提供的基础步调集,如果我们本身开发的项目也必要提供雷同的向后兼容性,也可以使用这个特性。针对范例转移范例的编程只涉及到一个范例为TypeForwardedToAttribute的特性,接下来我们通过一个简单的实例来演示一下如何使用这个特性将某个范例转移到一个新的步调会合。
我们使用Visual Studio创建了如下图所示的解决方案,它演示了这样一个场景:控制台应用使用到了V1版本的类库Lib(v1\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文件中。
  1.    [assembly: System.Runtime.CompilerServices.TypeForwardedTo(typeof(Lib.Foobar))]
复制代码
为了检验针对Foobar范例的转移是否成功,我们在控制台应用App中定义了如下一段步调,它负责将Foobar范例当前所在步调集的名称输出到控制台上。接下来我们只必要编译(以Debug模式)整个解决方案,那么V2版本的两个步调集(Lib.dll和Lib.Core.dll)将保存到\v2\lib\bin\debug\目次下。
  1.    1: class Program
复制代码
  1.    2: {
复制代码
  1.    3:     static void Main()
复制代码
  1.    4:     {
复制代码
  1.    5:         Console.WriteLine(typeof(Foobar).Assembly.FullName);
复制代码
  1.    6:     }
复制代码
  1.    7: }
复制代码
接下来我们采用下令行的情势来运行控制台步调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”指令。
  1.    1: .assembly extern Lib.Core
复制代码
  1.    2: {
复制代码
  1.    3:   .ver 1:0:0:0
复制代码
  1.    4: }
复制代码
  1.    5: .class extern [color=#ff0000]forwarder[/color] Lib.Foobar
复制代码
  1.    6: {
复制代码
  1.    7:   .assembly extern Lib.Core
复制代码
  1.    8: }
复制代码
  1.    9: …
复制代码
三、可移植类库(PCL)

在.NET Framework的期间,创建可移植类库(PCL:Portable Class Library)是实现跨多个目标框架步调集共享的唯一途径。上面先容的内容都是在为PCL做铺垫,只有充实明白了Retargetable步调集范例转移的条件下才可能了解PCL的实现原理有精确的明白。考虑到很多读者朋友并没有使用PCL的履历,所以我们先来先容一下如何创建一个PCL项目。 当我们采用Visualization Studio的Class Library(Portal)项目模板创建一个PCL项目的时候,必要在如下图所示的对话框中选择支持的目标框架及其版本。Visual Studio会为新建的项目添加一个名为 “.NET” 的引用,这个引用指向一个由选定目标框架决定的步调集列表。由于这些步调集提供的API能够兼容所有选择的平台,我们在此基础编写的步调天然也具有平台兼容性。
  
如果查察这个特别的.NET引用所在的地址,我们会发现它指向目次“%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETPortable\{version}\Profile\ProfileX”。如果查察 “%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETPortable” 目次,我们会发现它具有如下图所示的结构。
  
如上图所示,本机所在目次“%ProgramFiles%\Reference Assemblies\Microsoft\Framework\.NETPortable”下具有三个代表.NET Framework版本的子目次(v4.0、v4.5和v4.6)。具体到针对某个.NET Framework版本的目次(比如v4.6),其子目次Profile下具有一系列以 “Profile” + “数字” (比如Profile31、Profile32和Profile44等)命名的子目次,实际上PCL项目引用的就是存储在这些目次下的步调集
对于两个差别平台的.NET Framework来说,它们的BCL在API的定义上存在交集,从理论上来说,创建在这个交集基础上的步调是可以被这两个平台中共享的。如下图所示,如果我们编写的代码必要分别对Windows Desktop/Phone、Windows Phone/Store和Windows Store/Desktop平台提供支持,那么这样的代码依靠的部门仅限于两两的交集A+B、A+C和A+D。如果要求这部门代码能够运行在Windows Desktop/Phone/Store三个平台上,那么它们只能创建在三者之间的交集A上。   

针对所有可能的目标框架(包罗版本)的组合,微软会将作为两者交集的API提取出来并定义在相应的步调会合。比如说所有的目标框架都包含一个核心的步调集mscorlib.dll,虽然定义其中的范例及其成员在各个目标框架不尽相同,但是它们之间肯定存在交集,微软针对差别的目标框架组合将这些交集提取出来并定义在一系列同名步调会合,并同样命名为mscorlib.dll。 微软按照这样的方式创建了其他针对差别.NET Framework平台组合的基础步调集,这些针对某个组合的所有步调集构成一系列的Profile,并定义在上面我们提到过的目次下。值得一提的是,所有这些针对某个Profile的步调集均为Retargetable步调集。
当我们创建一个PCL项目的时候,第一个必需的步调是选择兼容的目标框架(和版本),Visual Studio会根据我们的选择确定一个具体的Profile,并为创建的项目添加针对该Profile的步调集引用。由于所有引用的步调集是根据我们选择的目标框架组合 “度身定制” 的,所以定义在PCL项目的代码才具有可移植的能力。
上面我们仅仅从开发的角度表明了定义在PCL项目的代码本身为什么能够确保是与目标.NET Framework平台兼容的,但是在运行的角度来看这个标题,却存在额外两个标题:

  • 元数据描述的引用步调集与真实加载的步调集不一致,比如我们创建一个兼容.NET Framework 4.5和Silverlight 5.0的PCL项目,被引用的步调集mscorlib.dll的版本为2.0.5.0,但是Silverlight 5.0运行时情况中的步调集mscorlib.dll的版本则为5.0.5.0
  • 元数据描述的引用步调集的范例定义与运行时加载步调集范例定义不一致,比如引用步调会合的某个范例被转移到了另一个步调会合。
由于PCL项目在编译时引用的均为Retargetable步调集,所以步调集的重定向机制帮助我们解决了第一个标题。由于在CLR在加载某个Retargetable步调集的时候,如果找不到一个与引用步调集在文件名、版本、语言文化和公钥令牌完全匹配的步调集,则会只考虑文件名的一致性。至于第二个标题,天然可以通过上面我们先容的范例转移机制来解决。
  
综上所述,虽然微软在针对多个目标框架的代码复用上面为我们提供了一些解决方案。在源代码共享方面,我们可以采用共享项目,虽然共享项目能够做到将一组源文件进行打包复用,但是我个人基本上不怎么用它,由于如果我们在其中定义一些公有范例,那么引用该共享项目的项目之间会造成命名冲突。从另一方面讲,我们真正必要的是步调集层面的复用,但是在这方面微软只为我们提供了PCL。PCL这种采用提取目标框架API交集的方式注定了只能是一种暂时的解决方案,试着想一下:如果目标框架由10种,每种有3个版本,我们必要为多少种组合创建相应的Profile。对于开发者来说,如果目标框架(包罗版本),我们在创建PCL项目进行兼容框架的选择都会成标题。所以我们针对盼望的是能够提供给全平台支持的BCL,你可以已经知道了,这就是Net Standard,那么Net Standard是如何能够在多个目标框架中复用的呢?请求关注本系列终结篇《.NET Core跨平台的奥秘[下篇]:全新的结构》。
  
.NET Core跨平台的奥秘[上篇]:历史的枷锁
.NET Core跨平台的奥秘[中篇]:复用之殇
.NET Core跨平台的奥秘[下篇]:全新的结构

相关技术服务需求,请联系管理员和客服QQ:2753533861或QQ:619920289
您需要登录后才可以回帖 登录 | 用户注册

本版积分规则

帖子推荐:
客服咨询

QQ:2753533861

服务时间 9:00-22:00

快速回复 返回顶部 返回列表