1. 程式人生 > >NET Core跨平臺的奧秘[中篇]:復用之殤

NET Core跨平臺的奧秘[中篇]:復用之殤

遇到 pla 服務 ade 空間 targe linux pcl 如何

從本質上講,按照CLI規範設計的.NET從其出生的那一刻就具有跨平臺的基因,這與Java別無二致。由於采用了統一的中間語言,微軟只需要針對不同的平臺設計不同的虛擬機(運行時)就能彌合不同操作系統與處理器架構之間的差異,但是“理想很豐滿,現實很骨感”。在過去十多年中,微軟將.NET引入到了各個不同的應用領域,表面上看起來似乎欣欣向榮,但是由於采用完全獨立的多目標框架的設計思路,導致針對多目標框架的代碼平臺只能通過PCL(參考《.NET Core跨平臺的奧秘[中篇]:復用之殤》)這種“妥協”的方式來解決。如果依然按照這條道路走下去,.NET的觸角延伸得越廣,枷鎖將越來越多,所以.NET 已經到了不得不做出徹底改變的時刻了。

一、跨平臺的.NET Core
綜上所述,要真正實現.NET 的跨平臺偉業,主要需要解決兩個問題,一是針對不同的平臺設計相應的運行時為中間語言CIL提供一個一致性的執行環境,而是提供統一的BCL以徹底解決代碼復用的難題。對於真正跨平臺的.NET Core來說,微軟不僅為它設計了針對不同平臺被成為CoreCLR的運行時,同時還重新設計了一套被稱為CoreFX的BCL。

2-221

如上圖所示,NET Core目前支持的AppModel主要有兩種,其中ASP.NET Core用於開發服務器Web應用和服務,而UWP(Universal Windows Platform)則用於開發能夠在各種客戶端設備(Mobile、PC、Xbox、Devices + IOT、HoloLens和Surface Hub等)上以自適應方式運行的Windows 10應用。CoreFX是經過完全重寫的BCL,除了自身就具有跨平臺執行的能力之外,其提供的API也不再是統一定義在少數幾個單一的程序集中,而是經過有效分組之後被定義在各自獨立的模塊中。這些模塊對應著一個單一的程序集,並最終由對應的NuGet包來分發。至於底層的虛擬機,微軟則為主流的操作系統類型(Windows、Mac OS X和Linux)和處理器架構(x86、x64和ARM)設計了針對性的運行時,被稱為CoreCLR。

作為運行時的CoreCLR和提供BCL的CoreFX是.NET Core兩根重要的基石,但是就開發成本來看,微軟在後者投入的精力是前者無法比擬的。我們知道.NET Core自誕生到現在已經有好些年了,目前的版本還只是到了2.0,從發布進度上顯得稍顯緩慢,其中一個主要的原因是:重寫CoreFX提供的基礎API確實是一件繁瑣耗時的工程,而且這項工程遠未結束。為了對CoreFX提供的BCL有一個大致的了解,我們看看這些常用的基礎API究竟定義在哪些命名空間下。

System.Collections:定義了我們常用的集合類型。
System.Console:提供API完成基本的控制臺操作。
System.Data:提供用於訪問數據庫的API,相當於原來的ADO.NET。
System.Diagnostics:提供基本的診斷、調試和追蹤的API。
System.DirectoryServices:提供基於AD(Active Directory)管理的API。
System.Drawing:提供GDI相關的API。
System.Globalization:提供API實現多語言以及全球化支持。
System.IO:提供針對文件輸入輸出相關的API。
System.Net:提供與網絡通信相關的API。
System.Reflection:提供API以實現與反射相關的操作。
System.Runtime:提供與運行時相關的一些基礎類型。
System.Security:提供與數據簽名和加解密相關的API。
System.Text:提供針對字符串/文本編碼與解碼相關的API。
System.Threading:提供用於管理線程的API。
System.Xml:提供API用以操作XML結構的數據。
我們知道對於傳統的.NET Framework來說,承載BCL的API幾乎都定義在mscorlib.dll這個程序集中,這些API並不是全部都轉移到組成CoreFX的眾多程序集中,那些與運行時(CoreCLR)具有緊密關系的底層API被定義到一個叫做System.Private.CoreLib.dll的程序集中,所以下圖反映了真正的.NET Core層次結構。我們在編程過程中使用的基礎數據類型基本上都定義在這個程序集中,所以目前這個程序集的尺寸已經超過了10M。由於該程序集提供的API與運行時關聯較為緊密,較之CoreFX提供的API,這些基礎API具有較高的穩定性,所以它是隨著CoreCLR一起發布的。

2-23

雖然我們編程過程中使用到的絕大部分基礎類型都定義在System.Private.CoreLib.dll程序集中,但是這卻是一個“私有”的程序集,我們可以從其命名看出這一點。我們將System.Private.CoreLib.dll稱為一個私有程序集,並不是說定義其中的都是一些私有類型,而是因為我們在編程的過程不會真正引用這個程序集,這與.NET Framework下的mscorlib.dll是不一樣的。不僅如此,當我們編寫的.NET Core代碼被編譯的時候,編譯器也不會鏈接到這個程序集上,也就是說編譯後生成的程序集中同樣也沒有針對該程序集引用的元數據。但是當我們的應用被真正執行的時候,所有引用的基礎類型全部會自動 “轉移” 到這個程序集中。至於如何實現運行過程中的類型轉移,其實就是利用了我們上面介紹的Type Forwarding技術。

實例演示:針對System.Private.CoreLib.dll程序集的類型轉移
對上面介紹的針對System.Private.CoreLib.dll程序集的類型轉移,可能很多人還是難以理解,為了讓大家對這個問題具有徹底的認識,我們不妨來做一個簡單的實例演示。我們利用Visual Studio創建一個.NET Core控制臺應用,並在作為程序入口的Main方法中編寫如下幾行代碼,它們會將我們常用的幾個數據類型(System.String、System.Int32和System.Boolean)所在的程序集名稱打印在控制臺上。

1: class Program
2: {
3: static void Main()
4: {
5: Console.WriteLine(typeof(string).Assembly.FullName);
6: Console.WriteLine(typeof(int).Assembly.FullName);
7: Console.WriteLine(typeof(bool).Assembly.FullName);
8: }
9: }
根據我們上面的分析,程序運行過程中使用到的這些基礎類型全部來源於System.Private.CoreLib.dll這個程序集中,關於這一點在如下圖所示的輸出結果中得到了證實。我們通過圖2-24所示的輸出結果,我們不僅僅知道了這個核心程序集的名稱,還知道了該程序集目前的版本(4.0.0.0);

2-24

我們說應用編譯後生成的程序集並不會具有針對System.Private.CoreLib.dll程序集引用的元數據,為了證明這一點,我們只需要利用Windows SDK(在目錄“%ProgramFiles(x86)%Microsoft SDKs\Windows\{version}\Bin”下)提供的反編譯工具ildasm.exe就可以了。利用ildasm.exe打開這個控制臺應用編譯後生成的程序集之後,我們會發現它具有如下這兩個程序集的應用。

1: .assembly extern System.Runtime
2: {
3: .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
4: .ver 4:2:0:0
5: }
6: .assembly extern System.Console
7: {
8: .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
9: .ver 4:1:0:0
10: }
實際上我們的程序只涉及到四個類型,即一個Console類型和三個基礎數據類型(String、Int32和Boolean),而程序集層面則只有針對System.Runtime和System.Console程序集的引用,那麽毫無疑問,後面這三個數據類型肯定與System.Runtime程序集有關,那麽該程序集針對這三個數據類型具有怎樣的定義呢?為了得到答案,我們先得知道這個程序集究竟被保存在哪裏。我們知道“%ProgramFiles%dotnet\”是.NET Core的應用根目錄,而這個System.Runtime.dll作為“共享”程序集被保存在子目錄“\shared\Microsoft.NETCore.App\2.0.0”下面,這個目錄下面還保存著很多其他的共享程序集。

我們依然利用反編譯工具ildasm.exe查看System.Runtime.dll程序集清單文件的元數據定義。我們會發現整個程序集除了定義少數幾個核心類型(比如兩個重要的委托類型Action和Func就定義在這個程序集中),它的作用就是將所有基礎的類型采用Type Forwarding的方式轉移到System.Private.CoreLib.dll程序集中,下面的代碼片段為你展示了針對我們程序使用的三個基礎數據類型轉移的相關定義。

1: .assembly extern System.Private.CoreLib
2: {
3: .publickeytoken = (7C EC 85 D7 BE A7 79 8E )
4: .ver 4:0:0:0
5: }
6: .class extern forwarder System.String
7: {
8: .assembly extern System.Private.CoreLib
9: }
10: .class extern forwarder System.Int32
11: {
12: .assembly extern System.Private.CoreLib
13: }
14: .class extern forwarder System.Boolean
15: {
16: .assembly extern System.Private.CoreLib
17: }
我們演示實例體現的程序集直接的引用關系,以及如上代碼片段體現的相關基礎類型(System.String、System.Int32和System.Boolean)的轉移方向基本體現在如下圖所示的關系圖中。

2-25

復用.NET Framework程序集
我們將上述這種利用Type Forwarding方式實現跨程序集類型轉移的技術成為“墊片(Shim)”,這是實現程序集跨平臺復用的重要手段。除了System.Runtime.dll,.NET Core還提供了其他一些其他墊片程序集,正是源於這這些墊片程序集的存在,我們可以將在.NET Framework環境下編譯的程序集在.NET Core應用中使用。為了讓讀者朋友們對此有深刻的認識,我們照例來做一個簡單的實例演示。

我們利用Visual Studio創建一個空的解決方案,並添加如下三個項目(NetApp、NetCoreApp、NetLib),其中NetApp和NetCoreApp分別是針對.NET Framework(4.7)和.NET Core(2.0)的控制臺程序,而NetLib則是針對.NET Framework的類庫項目,該項目定義的API將在NetApp和NetCoreApp被調用。

2-26

我們在NetLib項目中定義了一個Utils工具類,並在其中定義了一個PrintAssemblyNames方法。如下面的代碼片段所示,我們在這個方法中打印出三個常用的類型(Task、Uri和XmlWriter)所在的程序集的名稱。通過在不同類型(.NET Framework和.NET Core)的應用中調用這個方法,我們就可以確定它們在運行時究竟是從那個程序集中加載的。我們分別在NetApp和NetCoreApp這兩個不同類型的控制臺程序中調用了這個方法。

NetLib:

1: public class Utils
2: {
3: public static void PrintAssemblyNames()
4: {
5: Console.WriteLine(typeof(Task).Assembly.FullName);
6: Console.WriteLine(typeof(Uri).Assembly.FullName);
7: Console.WriteLine(typeof(XmlWriter).Assembly.FullName);
8: }
9: }
NetApp:

1: class Program
2: {
3: static void Main()
4: {
5: Console.WriteLine(".NET Framework 4.7");
6: Utils.PrintAssemblyNames();
7: }
8: }
NetCoreApp:

1: class Program
2: {
3: static void Main()
4: {
5: Console.WriteLine(".NET Core 2.0");
6: Utils.PrintAssemblyNames();
7: }
8: }
直接運行NetApp和NetCoreApp這兩個控制臺程序後,我們會發現不同的輸出結果。如下圖所示,對於我們指定的三個類型(System.Threading.Tasks.Task、System.Uri和System.Xml.XmlWriter),分別在.NET Framework和.NET Core環境下承載它們的程序集是不同的。具體來說,.NET Framework環境下的這三個類型分別定義在mscorlib.dll、System.dll和System.Xml.dll中;當切換到.NET Core環境下後,運行時則會從三個私有的程序集System.Private.CoreLib.dll、System.Private.Uri.dll和System.Private.Xml.dll中加載這三個類型。

2-27

由於NetApp和NetCoreApp這兩個控制臺應用使用的都是同一個針對.NET Framework編譯的程序集NetLib.dll,所以我們先利用反編譯工具ildasm.exe查看一下它具有怎樣的程序集引用。如下面的代碼片段所示,程序集NetLib.dll引用的程序集與控制臺應用NetApp的輸出結果是一致的。

1: .assembly extern mscorlib
2: {
3: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
4: .ver 4:0:0:0
5: }
6: .assembly extern System
7: {
8: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
9: .ver 4:0:0:0
10: }
11: .assembly extern System.Xml
12: {
13: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
14: .ver 4:0:0:0
15: }
那麽我們的核心問題變成了:Task、Uri和XmlWriter這三個類型在.NET Core的運行環境下是如何轉移到其他程序集中的。要回答這個問題,我們只需要利用ildasm.exe查看mscorlib.dll、System.dll和System.Xml.dll反編譯這三個程序集就可以了。這三個程序集同樣存在於“%ProgramFiles%dotnet\\shared\Microsoft.NETCore.App\2.0.0”目錄下,通過反編譯與它們相關的程序集,我們得到如下所示的相關元數據。

mscorlib.dll

1: .assembly extern System.Private.CoreLib
2: {
3: .publickeytoken = (7C www.huarencai5200.com EC 85 D7 BE A7 79 8E )
4: .ver 4:0:0:0
5: }
6: .class extern forwarder System.Threading.Tasks.Task
7: {
8: .assembly extern System.Private.CoreLib
9: }
System.dll

1: .assembly extern System.Private.Uri
2: {
3: .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
4: .ver 4:0:4:0
5: }
6: .class extern forwarder System.Uri
7: {
8: .assembly extern System.Private.Uri
9: }
System.Xml.dll

1: .assembly extern System.Xml.ReaderWriter
2: {
3: .publickeytoken = (www.8555388.cn/ B0 3F 5F 7F 11 D5 0A 3A )
4: .ver 0:0:0:0
5: }
6: .class extern forwarder System.Xml.XmlWriter
7: {
8: .assembly extern System.Xml.ReaderWriter
9: }
System.Xml.ReaderWriter.dll

1: .assembly extern System.Private.Xml
2: {
3: .publickeytoken = (CC 7B 13 FF CD 2D DD 51 )
4: .ver 4:0:0:0
5: }
6: .class extern forwarder System.Xml.XmlWriter
7: {
8: .assembly extern System.Private.Xml
9: }
如上面的代碼片段所示,針對Task、Uri和XmlWriter這三個類型的轉移一共涉及到七個程序集,其中mscorlib.dll、System.dll和System.Xml.dll是NetLib.dll直接引用的三個程序集,而System.Private.CoreLib.dll、System.Private.Uri.dll和System.Private.Xml.dll則是最終承載這三個類型的程序集。對於Task和Uri類型來說,它們只經歷一次轉移,而XmlWriter則經歷了兩次類型轉移,它轉移到程序集System.Xml.ReaderWriter.dll中,再借助後者轉移到目標程序集System.Private.Xml.dll,程序集引用和類型轉移關系體現在下圖中。

2-28

二、多平臺復用的BCL
雖然.NET Core借助於CoreCLR和CoreFX實現了真正的跨平臺,但是目前的.NET Core僅僅提供ASP.NET Core和UWP這兩種編程模型,雖然後者旨在實現多種設備的統一編程,但依然還是關註於Windows平臺。對於傳統.NET Framework下面向桌面應用的WPF和Windows Forms,它們並沒有跨平臺的意義,所以依然是今後.NET的一大分支。除此之外,雖然我們有了跨平臺的ASP.NET Core,傳統的ASP.NET依然被保留了下來,並且在今後一段時間內還將繼續升級。除了.NET Framework和.NET Core,.NET還具有另一個重要的分支,那就是Xamarin,它可以幫助我們為iOS、OS X和Android編寫統一的應用。在.NET誕生十多年後,微軟開始對.NET進行了全新的布局,建立了 “大一統” 的.NET平臺。總的來說,這個所謂的大一統.NET平臺由如下圖所示的.NET Framework、.NET Core和Xamarin這三個分支組成。

2-29

雖然被微軟重新布局的.NET平臺只包含了三個分支,但是之前遇到的一個重要的問題依然存在,那就是代碼的復用,說的更加具體的是應該是程序集的復用而不是源代碼的復用。我們知道之前解決程序集服務的方案就是PCL,但這並不是一種理想的解決方案,由於各個目標框架具有各種獨立的BCL,所以我們創建的PCL項目只能建立在指定的幾種兼容目標框架的BCL交集之上。對於全新的.NET平臺來說,這個問題通過提供統一的BCL得到根本的解決,這個統一的BCL被稱為.NET Standard。

我們可以將.NET Standard稱為新一代的PCL,PCL提供的可移植能力僅僅限於創建時就確定下來的幾種目標平臺,但是.NET Standard做得更加徹底,因為它在設計的時候就已經考慮針對三大分支的復用。如下圖所示,.NET Standard為.NET Framework、.NET Core和Xamarin提供了統一的API,那麽我們在這組標準API基礎上編寫的代碼自然就能被所有類型的.NET應用復用。

2-30

.NET Standard提供的API主要是根據現有.NET Framework來定義的,它的版本升級反映了其提供的API不斷豐富的過程,目前最新版本(.NET Standard 2.0)提供的API數量在前一版本基礎上幾乎翻了一番。Visual Studio提供相應的項目模板幫助我們創建基於.NET Standard的類庫項目,這樣的項目會采用專門的目標框架別名netstandard{version}。一個針對.NET Standard 2.0的類庫項目具有如下的定義,我們可以看到它采用的目標框架別名為 “.NET Standard 2.0” 。

1: <Project Sdk="Microsoft.NET.Sdk">
2: <PropertyGroup>
3: <TargetFramework>netstandard2.0<www.jpg157.com /TargetFramework>
4: </PropertyGroup>
5: </Project>
顧名思義,.NET Standard僅僅是一個標準,而不提供具體的實現。我們可以簡單理解為.NET Standard為我們定義了一整套標準的接口,各個分支需要針對自身的執行環境對這套接口提供實現。對於.NET Core來說,它的基礎API主要由CoreFX和System.Private.CoreLib.dll這個核心程序集來承載,這些API基本上就是根據.NET Standard來設計的。但是對.NET Framework來說,它的BCL提供的API與.NET Standard存在著很大的交集,實際上.NET Standard基本上就是根據.NET Framework現有的API來設計的,所以微軟不可能在.NET Framework上重寫一套類型於CoreFX的實現,只需要采用某個技術 “鏈接” 到現有的程序集上就可以了。

一個針對.NET Standard編譯生成的程序集在不同的執行環境中針對真正提供實現的程序集的所謂“鏈接”依然是通過上面我們介紹的“墊片”技術來實現的,為了徹底搞清楚這個問題,我們還是先來作一個簡單的實例演示。如下圖所示,我們創建了與上面演示實例具有類似結構的解決方案,與之不同的是,分別針對.NET Framework和.NET Core的控制臺應用NetApp和NetCoreApp共同引用的類庫NetStandardLib是一個.NET Standard 2.0類庫項目。

2-31

與上面演示的實例一樣,我們在NetStandardLib中定義了如下一個Utils類,並利用定義其中的靜態方法PrintAssemblyNames數據兩個數據類型(Dictionary<,>和SortedDictionary<,>)所在的程序集名稱,該方法分別在NetApp和NetCoreApp的入口Main方法中被調用。

NetStandardLib:

1: public class Utils
2: {
3: public static void PrintAssemblyNames()
4: {
5: Console.WriteLine(typeof(Dictionary<,www.mayiyule158.cn >).Assembly.FullName);
6: Console.WriteLine(typeof(SortedDictionary<,>).Assembly.FullName);
7: }
8: }
NetApp:

1: class Program
2: {
3: static void Main(www.feihuanyule.com)
4: {
5: Console.WriteLine(www.feityl.com ".NET Framework 4.7");
6: Utils.PrintAssemblyNames();
7: }
8: }
NetCoreApp:

1: class Program
2: {
3: static void Main()
4: {
5: Console.WriteLine(www.longboshyl.cn ".NET Core 2.0");
6: Utils.PrintAssemblyNames(www.wangcai157.com );
7: }
8: }
直接運行這兩個分別針對.NET Framework和.NET Core的控制臺應用NetApp和NetCoreApp,我們會發現它們會生成不同的輸出結果。如下圖所示,在.NET Framework和.NET Core 執行環境下,Dictionary<,>和SortedDictionary<,>這另個泛型字典類型其實來源於不同的程序集。具體來說,我們常用的Dictionary<,>類型在.NET Framework 4.7和.NET Core 2.0環境下分別定義在程序集mscorlib.dll和System.Private.CoreLib.dll中,而SortedDictionary<,>所在的程序集則分別是System.dll和System.Collection.dll。

2-32

對於演示的這個實例來說,這個NetStandardLib類庫項目針對的目標框架為.NET Standard 2.0,後者最終體現為一個名為NetStandard.Library.nupkg的NuGet包,這一點其實可以從Visual Studio針對該項目的依賴節點可以看出來。如下圖所示,這個名為NetStandard.Library的NuGet包具有一個核心的程序集netstandard.dll,上面我們所說的.NET Standard API就定義在該程序集中。

2-33

也就是說,所有.NET Standard 2.0項目都具有針對程序集netstandard.dll的依賴,這個依賴自然也會體現在編譯後生成的程序集上。對於我們演示實例中的這個類庫項目NetStandardLib編譯生成的同名程序集來說,它針對程序集netstandard.dll的依賴體現在如下所示的元數據中。

1: .assembly extern netstandard
2: {
3: .publickeytoken = (CC www.wangcai157.com 7B 13 FF CD 2D DD 51 )
4: .ver 2:0:0:0
5: }
6: .assembly NetStandardLib
7: {
8: ...
9: }
10: ...
按照我們即有的知識,原本定義在netstandard.dll的兩個類型(Dictionary<,>和SortedDictionary<,>)在不同過的執行環境中需要被轉移到另一個程序集中,我們完全可以在相應的環境中提供一個同名的墊片程序集並借助類型的跨程序集轉移機制來實現,實際上微軟也就是這麽做的。我們先來看看針對.NET Framework的墊片程序集netstandard.dll的相關定義,我們可以直接在NetApp編譯的目標目錄中找到這個程序集。借助於反編譯工具ildasm.exe,我們可以很容易地得到與Dictionary<,>和SortedDictionary<,>這兩個泛型字典類型轉移的相關元數據,具體的內容下面的代碼片段所示。

1: .assembly extern mscorlib
2: {
3: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
4: .ver 0:0:0:0
5: }
6: .assembly extern System
7: {
8: .publickeytoken = (B7 7A 5C 56 19 34 E0 89 )
9: .ver 0:0:0:0
10: }
11: .class extern forwarder System.Collections.Concurrent.ConcurrentDictionary`2
12: {
13: .assembly extern mscorlib
14: }
15: .class extern forwarder System.Collections.Generic.SortedDictionary`2
16: {
17: .assembly extern System
18: }
針對.NET Core的墊片程序集netstandard.dll被保存在我們前面提到的共享目錄“%ProgramFiles%dotnet\shared\Microsoft.NETCore.App\2.0.0”下,我們采用同樣的方式提取出與Dictionary<,>和SortedDictionary<,>這兩個泛型字典類型轉移的元數據。從如下的代碼片段我們可以清晰地看出,Dictionary<,>和SortedDictionary<,>這兩個類型都被轉移到程序集System.Collections.dll之中。

1: .assembly extern System.Collections
2: {
3: .publickeytoken = (B0 3F 5F 7F 11 D5 0A 3A )
4: .ver 0:0:0:0
5: }
6: .class extern forwarder System.Collections.Generic.Dictionary`2
7: {
8: .assembly extern System.Collections
9: }
10: .class extern forwarder System.Collections.Generic.SortedDictionary`2
11: {
12: .assembly extern System.Collections
13: }
從演示實例的執行結果我們知道,SortedDictionary<,>確實是定義在程序集System.Collections.dll中,但是我們常用的Dictionary<,>類型則出自核心程序集System.Private.CoreLib.dll,那麽我們可以斷定Dictionary<,>類型在System.Collections.dll中必然出現了二次轉移。為了確認我們的斷言,我們只需要采用相同的方式反編譯程序集System.Collections.dll,該程序集也被存儲在共享目錄 “%ProgramFiles%dotnet\shared\Microsoft.NETCore.App\2.0.0” 中,該程序集中針對Dictionary<,>類型的轉移體現在如下所示的元數據中。

1: .assembly extern System.Private.CoreLib
2: {
3: .publickeytoken = (7C EC 85 D7 BE A7 79 8E )
4: .ver 4:0:0:0
5: }
6: .class extern forwarder System.Collections.Generic.Dictionary`2
7: {
8: .assembly extern System.Private.CoreLib
9: }
上面針對Dictionary<,>和SortedDictionary<,>這兩個類型分別在.NET Framework 4.7和.NET Core環境下的跨程序集轉移路徑基本上體現在下圖之中。簡單來說,.NET Framework環境下的墊片程序集netstandard.dll將這兩個類型分別轉移到了程序集mscorlib.dll和System.dll之中。如果執行環境切換到了.NET Core,這兩個類型先被轉移到System.Collection.dll中,但是Dictionary<,>這個常用類型最終是由System.Private.CoreLib.dll這個基礎程序集承載的,所有System.Collection.dll中針對該類型作了二次轉移。

2-34

上面這個簡單的類型基本上揭示了.NET Standard為什麽能夠提供全平臺的可移植性,我們現在來對此做一個簡單的總結。.NET Standard API由NetStandard.Library這個NuGet包來承載,後者提供了一個名為netstandard.dll的程序集,保留在這個程序集中的僅僅是. NET Standard API的存根(Stub),而不提供具體的實現。所有對於一個目標框架為.NET Standard的類庫項目編譯生成的程序集來說,它們保留了針對程序集netstandard.dll的引用。

.NET平臺的三大分支(.NET Framework、.NET Core和Xamarin)按照自己的方式各自實現了.NET Standard規定的這套標準的API。由於在運行時真正承載.NET Standard API的類型被分布到多個程序集中,所以. NET Standard程序集能夠被復用的前提是運行時能夠將這些基礎類型鏈接到對應的程序集上。由於. NET Standard程序集是針對netstandard.dll進行編譯的,所以我們只需要在各自環境中提供這個同名的程序集來完成類型的轉移即可。

.NET Core跨平臺的奧秘[上篇]:歷史的枷鎖
.NET Core跨平臺的奧秘[中篇]:復用之殤
.NET Core跨平臺的奧秘[下篇]:全新的布局

NET Core跨平臺的奧秘[中篇]:復用之殤