一文透徹詳解.NET框架類型系統設計要點
自從我們啟動快速發展的 .NET 開源和跨平臺項目以來,.NET 發生了很大變化。我們重新思考并完善了該平臺,添加了專為性能和安全性而設計的新低級功能,以及以生產力為中心的高級功能。Span<T>、硬件內在函數和可為空的引用類型都是示例。我們正在啟動一個新的“.NET 設計要點”系列文章,以探索定義當今 .NET 平臺的基礎知識和設計選擇,以及它們如何使您現在編寫的代碼受益。
本系列的第一篇文章全面概述了平臺的支柱和設計要點。當您選擇 .NET 時,它在基礎級別上描述了“您得到了什么”,旨在成為一個充分且以事實為中心的框架,您可以使用它來向其他人描述該平臺。后續帖子將更詳細地介紹這些相同的主題,因為這篇帖子并沒有完全公正地介紹這些功能中的任何一個。這篇文章不描述工具,如 Visual Studio,也不涵蓋更高級的庫和應用程序模型,如 ASP.NET Core 提供的那些。
我們所說的“.NET”是現代的 .NET Core。我們在 GitHub 上作為開源項目于2014年啟動了這個項目。它在 Arm64、x64 和其他芯片架構上的 Linux、macOS 和 Windows 上運行。它在一堆 Linux 發行版中可用。它與 .NET Framework 保持了很大的兼容性,但又是一個全新的方向和產品。
.NET 設計要點.NET 平臺代表生產力、性能、安全性和可靠性。.NET 在這些價值之間取得的平衡使其具有吸引力。
.NET 的設計要點可以歸結為在安全域(一切都高效)和不安全域(存在大量功能)中都有效和高效。.NET 可能是具有最多內置功能的托管環境,同時還提供最低的與外部世界互操作的成本,并且兩者之間沒有權衡。事實上,許多功能都利用了這種無縫劃分,在底層操作系統和 CPU 的原始能力和功能上構建安全的托管 API。
我們可以進一步擴展設計點:
生產力是跨運行時、庫、語言和工具的首要設計考慮因素。安全代碼是主要的計算模型,而不安全代碼支持額外的手動優化。支持靜態和動態代碼,支持廣泛的不同場景。本機代碼互操作和硬件內在函數成本低且保真度高(原始 API 和指令訪問)。代碼可跨平臺(操作系統、芯片架構)移植,而平臺定位則支持專業化和優化。通過通用編程模型的專門實現,可以實現跨編程域(云、客戶端、游戲)的適應性。OpenTelemetry 和 gRPC 等行業標準優于定制解決方案。.NET 堆棧的支柱運行時、庫和語言是 .NET 堆棧的支柱。更高級別的組件,如 .NET 工具和應用程序堆棧,如 ASP.NET Core,構建在這些支柱之上。這些支柱具有共生關系,由一個團隊(Microsoft 員工和開源社區)共同設計和構建,致力于這些組件的多個方面并為其提供信息。
C# 是面向對象的,運行時支持面向對象。C# 需要垃圾收集,運行時提供跟蹤垃圾收集器。事實上,將 C#(以其完整形式)移植到沒有垃圾收集的系統是不可能的。這些庫(以及應用程序堆棧)將這些功能塑造成概念和對象模型,使開發人員能夠在直觀的工作流程中高效地編寫算法。
C# 是一種現代的、安全的、通用的編程語言,涵蓋了從面向數據的記錄等高級功能到函數指針等低級功能。它提供靜態類型以及類型和內存安全作為基準功能,同時提高開發人員的工作效率和代碼安全性。C# 編譯器也是可擴展的,支持插件模型,使開發人員能夠通過額外的診斷和編譯時代碼生成來增強系統。
許多 C# 功能已經影響或受最先進的編程語言的影響。例如,C# 是第一個引入 async?and?await.?同時,C# 借鑒了其他編程語言中首先引入的概念,例如采用模式匹配和主構造函數等函數式方法。
核心庫公開了數千種類型,其中許多類型與 C# 語言集成并為其提供動力。例如,C# 的 foreach 支持枚舉任意集合,基于模式的優化使 List<T>?等集合能夠被簡單高效地處理。資源管理可能留給垃圾收集,但可以通過 IDisposable?和 using 中的直接語言支持進行快速清理。
C# 中的字符串插值既富有表現力又高效,與 string 、StringBuilder 和 Span<T> 等跨核心庫類型的實現集成并受其支持。語言集成查詢 (LINQ)功能由庫中的數百個序列處理例程提供支持,例如 Where、Select 和 GroupBy,具有支持內存中和遠程數據源的可擴展設計和實現。從壓縮到密碼學再到正則表達式,列表還在繼續,直接集成到語言中的內容只是作為核心 .NET 庫的一部分公開的功能的表面。一個全面的從套接字到 HTTP/3 的網絡堆棧是一個獨立的領域。同樣,庫支持處理無數格式和語言,如 JSON、XML 和 tar。
.NET 運行時最初稱為“公共語言運行時 (CLR)”。它繼續支持多種語言,一些由 Microsoft 維護(例如 C#、F#、Visual Basic、C++/CLI 和 PowerShell),一些由其他組織維護(例如 Cobol、Java、PHP、Python、Scheme)。許多改進與語言無關,這會引發所有改善。
接下來,我們將看看它們一起提供的各種平臺特性。我們可以分別詳細說明這些組件中的每一個,但您很快就會看到它們在交付 .NET 設計點方面進行合作。讓我們從類型系統開始。
類型系統.NET 類型系統提供了顯著的廣度,大致同等地滿足了安全性、描述性、動態性和本機互操作性。
首先,類型系統支持面向對象的編程模型。它包括類型、(單個基類)繼承、接口(包括默認方法實現)和虛擬方法分派,為面向對象允許的所有類型分層提供合理的行為。
泛型是一種普遍的特性,它允許將類專門化為一種或多種類型。例如,List<T>?是一個開放的通用類,而像?List<string>?和?List<int>?這樣的實例化避免了對單獨的?ListOfString?和?ListOfInt?類的需要,或者像 ArrayList 那樣依賴 object 和強制轉換。泛型還可以跨不同類型創建有用的系統(并減少對大量代碼的需求),例如 Generic Math。
Delegates?和?lambdas?允許將方法作為數據傳遞,這使得將外部代碼集成到另一個系統擁有的操作流中變得容易。它們是一種“膠水代碼”,它們的簽名通常是通用的,可以廣泛使用。
app.MapGet('/Product/{id}', async (int id) => { if (await IsProductIdValid(id)) { return await GetProductDetails(id); } return Products.InvalidProduct; });這種對 lambdas 的使用是?ASP.NET Core Minimal APIs 的一部分。它可以直接向路由系統提供端點實現。在更新的版本中,ASP.NET Core 更廣泛地使用了類型系統。
與 .NET 的 GC 管理類型相比,值類型和堆棧分配的內存塊提供了對數據和本機平臺互操作的更直接、低級別的控制。.NET 中的大多數原始類型,如整數類型,都是值類型,用戶可以定義自己具有相似語義的類型。
.NET 的泛型系統完全支持值類型,這意味著像 List<T>?這樣的泛型類型可以提供值類型集合的平坦、無開銷的內存表示。此外,.NET 泛型在替換值類型時提供專門的編譯代碼,這意味著這些泛型代碼路徑可以避免昂貴的 GC 開銷。
byte magicSequence = 0b1000_0001; Span<byte> data = stackalloc byte[128]; DuplicateSequence(data[0..4], magicSequence);此代碼生成堆棧分配的值。Span<byte>?是 byte* 的安全和更豐富的版本,提供長度值(帶邊界檢查)和方便的跨度切片。
Ref?類型和變量是一種小型編程模型,它提供對類型系統數據的較低級別和更輕量級抽象。這包括 Span<T>。此編程模型不是通用的,包括維護安全的重要限制。
internal readonly ref T _reference;
這種使用 ref 導致將指針復制到底層存儲,而不是復制該指針引用的數據。默認情況下,值類型是“按值復制”。ref 提供“按引用復制”行為,可以提供顯著的性能優勢。
自動內存管理.NET 運行時通過垃圾收集器 (GC) 提供自動內存管理。對于任何語言,其內存管理模型可能是其最具決定性的特征。.NET 語言也是如此。
工程師花費數周甚至數月的時間來追蹤這些問題的情況并不少見。許多語言使用垃圾收集器作為消除這些錯誤的用戶友好方式,因為 GC 確保正確的對象生命周期。通常,GC 會分批釋放內存以高效運行。這會導致暫停,如果您對延遲要求非常嚴格,這可能不適合,并且內存使用率會更高。GC 往往具有更好的內存局部性,并且某些 GC 能夠壓縮堆,使其不易產生內存碎片。
.NET 具有自我調整、跟蹤 GC。它旨在一般情況下提供“放手”操作,同時為更極端的工作負載提供配置選項。GC 是多年投資、改進和從多種工作負載中學習的結果。
▌Bump 指針分配通過指針遞增所需的大小分配對象(而不是在分離的空閑塊中尋找空間),因此一起分配的對象往往會在一起。由于用戶經常一起訪問不同對象,這樣做可以實現更好的內存局部性 memory locality ,這有利于保證性能。
▌分代收集對象生命周期遵循分代假設 generational hypothesis 是非常常見的,對象生存周期要么很長,要么很短。因此,對于 GC 來說,如果大部分運行時只收集臨時對象占用的內存(稱為臨時 GC ),而不是每次運行時都必須收集整個堆(稱為完整 GC ),那么效率就要高得多。
▌壓縮相同數量的可用空間在面積大而數量少的塊中比在面積小和數量多的塊中更有用。在壓縮 GC 期間,仍然存在的對象會被移動到一起,由此可以形成更大的自由空間。這種行為需要比非移動 GC 更復雜的實現,因為它需要更新對這些移動對象的引用。.NET GC 被動態調整為僅在確定回收的內存高于 GC 成本時才執行壓縮。這意味著臨時集合通常會被壓縮。
▌并行GC 工作可以在單個線程或多個線程上運行。Workstation flavor 在單個線程上進行 GC,而 Server flavor 在多個 GC 線程上進行,這樣可以更快結束作業。服務器 GC 還可以適應更大的分配率,因為有多個堆供應用程序分配,因此它對吞吐量適應性也很好。
▌并發在用戶線程暫停時進行 GC 工作稱為 Stop-The-World,這樣使實現需求更簡單,但這些暫停可能對于 GC 來說是不可接受的。.NET 提供 concurrent flavor 來緩解該問題。
▌固定.NET GC 支持對象固定,它可以實現與本機代碼的零拷貝互操作。此功能可實現高性能和高保真度的本機互操作,同時限制 GC。
▌獨立 GC可以使用具有不同機制的獨立 GC(通過配置指定并滿足 interface requirements)。這樣一來,調查和嘗試新功能就更容易了。
▌診斷GC 提供有關內存和集合的大量信息,這允許您將數據與系統的其余部分相關聯。例如,您可以通過捕獲 GC 事件并將它們與其他事件(如 IO)相關聯來評估 GC impact of your tail latency 尾部延遲對 GC 的影響,以計算 GC 對其他因素的影響程度,這樣您就可以將精力集中在正確的組件上。
安全.NET 編程安全一直是過去十年的熱門話題之一。它是 .NET 等托管環境的固有組件。
安全形式:
Type safety 類型安全 — 不能使用任意類型代替另一個類型,避免未定義的行為。Memory safety 內存安全 — 不能使用任意類型代替另一個類型,避免未定義的行為。Concurrency or thread safety 并發或線程安全 — 不能使用任意類型代替另一個類型,避免未定義的行為。.NET 從最初的設計開始就被設計成一個保證安全的平臺。特別需要指出的是,它旨在啟用新一代 Web 服務器,這些服務器一直需要在世界上復雜的計算環境(Internet)中接受不受信任的輸入的考驗。現在普遍認為網絡程序應該用安全的語言編寫。
類型安全由語言和運行時模塊同時強制執行。編譯器驗證靜態不變量,例如分配不同的類型——例如,分配 string 給 Stream——這將導致編譯器中產生錯誤。運行時驗證動態不變量,例如不同類型之間的轉換,就將產生 InvalidCastException。
內存安全主要由代碼生成器(如 JIT)和垃圾收集器合作實現。變量引用值要么是活動對象,要么是 null,要么超出范圍。默認情況下內存是自動初始化的,這樣新對象就不會使用未初始化的內存。邊界檢查禁止訪問數組中無效索引的元素讀取未定義的內存——通常由一個單位的錯誤偏移引起——這會導致 IndexOutOfRangeException。
Cnull 處理是保證內存安全的一種特殊形式。可空引用類型 Nullable reference types 是一種 C# 語言和編譯器功能,可靜態標識未安全處理的代碼 null。特別是,如果您取消引用可能為 null 的變量,編譯器會發出警告。您還可以禁止 null 賦值,這樣編譯器會在您可能給變量賦空值時發出警告。運行時具有匹配的動態驗證功能,可通過拋出 NullReferenceException 來防止引用被訪問。
C# 功能依賴于庫中可為空的屬性 nullable attributes 。它還依賴于它們在庫和應用程序堆棧(我們已經完成)中的詳盡應用,以便為您的代碼提供來自靜態分析工具的準確結果。
.NET 中沒有內置的并發安全。相反,開發人員需要遵循模式和約定來避免未定義的行為。.NET 生態系統中還有分析器和其他工具,可以深入了解并發問題。核心庫包括多種可以安全并發使用的類型和方法,例如支持任意數量的并發讀取器和寫入器而不會冒數據結構損壞風險的 concurrent collections 并發集合。
運行時公開安全和 unsafe code 不安全的代碼模型。安全代碼的安全性得到保證,這是默認設置,而開發人員必須選擇使用不安全代碼。不安全代碼通常用于與底層平臺互操作、與硬件交互或對性能關鍵路徑實施手動優化。
沙箱 sandbox 是一種特殊的安全形式,它提供隔離并限制組件之間的訪問。我們依賴標準的隔離技術,如進程(和 CGroups)、虛擬機和 WebAssembly(具有不同的特性)。
錯誤處理異常是 .NET 中的主要錯誤處理模型。異常的好處是錯誤信息不需要在方法簽名中表示或由每個方法處理。
下面的代碼演示了一個典型的模式:
try{ var lines = await File.ReadAllLinesAsync(file); Console.WriteLine($'The {file} has {lines.Length} lines.');}catch (Exception e) when (e is FileNotFoundException or DirectoryNotFoundException){ Console.WriteLine($'{file} doesn't exist.');}正確的異常處理對于應用程序的可靠性至關重要。可以在用戶代碼中有意處理預期的異常,否則應用程序就會崩潰。崩潰的應用程序比具有未定義行為的應用程序更可靠。當您想找出問題的根本原因時,它也更容易診斷。
異常從錯誤點拋出,并自動收集有關程序狀態的附加診斷信息。這些信息可用于交互式調試、應用程序可觀察性和事后調試。這些診斷方法中的每一種都依賴于訪問大量的錯誤信息和應用程序狀態來診斷問題。
異常是為罕見的情況而設計的。這在一定程度上是因為它們的性能成本相對較高。它們不打算用于控制流,即使它們有時以這種方式使用。
異常(有一部分)依賴于取消。一旦觀察到取消請求,它們就可以有效地停止執行并展開正在進行的調用堆棧。
try { await source.CopyToAsync(destination, cancellationToken); } catch (OperationCanceledException) { Console.WriteLine('Operation was canceled'); }.NET 設計模式包括替代形式的錯誤處理,以應對異常的性能成本過高的情況。例如,int.TryParse 返回成功時其參數包含已解析的有效整數,Dictionary<TKey, TValue>.TryGetValue 提供了一個類似的模型,返回一個有效 TValue 類型作為案例中的參數等。
錯誤處理和更普遍的診斷是通過低級運行時 API、higher-level libraries 和 tools 實現的。這些功能旨在支持更新的部署選項,例如容器。例如,dotnet-monitor 可以通過內置的面向診斷的 Web 服務器將運行時數據從應用導出到偵聽器。
并發支持同時做多件事是幾乎所有工作負載的基礎,無論是在保持 UI 響應的同時進行后臺處理的客戶端應用程序、處理成千上萬同時請求的服務、響應大量同時刺激的設備,還是高驅動的機器并行處理計算密集型操作。操作系統通過線程為這種并發性提供支持,這使得多個指令流能夠獨立處理,操作系統管理這些線程在機器中任何可用處理器內核上的執行。操作系統還提供對執行 I/O 的支持,提供的機制使 I/O 能夠以可擴展的方式執行,并且在任何特定時間都有許多“運行中”的 I/O 操作。
.NET 通過庫和深度集成到 C# 中,在多個抽象級別提供此類并發和并行化支持。線程 Thread 類位于層次結構的底部,代表一個操作系統線程,使開發人員能夠創建新線程并隨后加入它們。線程池 ThreadPool 位于線程之上,允許開發人員考慮異步安排在線程池上運行的工作項,并這些線程的管理(包括從池中添加和刪除線程,以及為這些線程分配工作項)放在運行時。Task 然后為任何異步執行的操作提供統一的表示形式,并且可以通過多種方式創建和連接;例如,Task.Run 允許在 ThreadPool 上運行安排委托并返回 Task 以表示該工作的最終完成,同時 Socket.ReceiveAsync 返回一個Task<int>(或 ValueTask<int>)表示異步 I/O 的最終完成,提供了大量的同步原語,用于協調線程和異步操作之間的同步和異步活動,并提供了大量高級 API 以簡化常見并發模式的實現,例如,SocketParallel.ForEach 和 Parallel.ForEachAsync 使處理一個線程的所有元素變得更容易實現數據序列并行。
異步編程支持也是 C# 編程語言的一流功能,它提供了 async 和 await 關鍵字,使編寫和組合異步操作變得容易,同時仍然享受該語言必須提供的所有控制流結構的全部好處。
反射反射是一種“程序即數據”范例,它能讓程序的一部分根據程序集、類型和成員動態查詢和/或調用另一部分。它對于后期綁定編程模型和工具特別有用。
以下代碼使用反射來查找和調用 type。
foreach (Type type in typeof(Program).Assembly.DefinedTypes) { if (type.IsAssignableTo(typeof(IStory)) && !type.IsInterface) { IStory? story = (IStory?)Activator.CreateInstance(type); if (story is not null) { var text = story.TellMeAStory(); Console.WriteLine(text); } } } interface IStory { string TellMeAStory(); } class BedTimeStore : IStory { public string TellMeAStory() => 'Once upon a time, there was an orphan learning magic ...'; } class HorrorStory : IStory { public string TellMeAStory() => 'On a dark and stormy night, I heard a strange voice in the cellar ...'; }此代碼動態枚舉實現特定接口的所有程序集類型,實例化每個類型的實例,并通過該接口調用對象的方法。代碼本來可以靜態編寫的,因為它只查詢它所引用的程序集中的類型,但要這樣做,需要將所有實例的集合(也許是作為一個 List<IStory>)交給它來處理。如果此算法從加載項目錄加載任意程序集,則更有可能使用這種后期綁定方法。有這樣一種情況:您無法提前獲取程序集和類型,反射通常就被用在這樣的場景中。
反射可能是 .NET 中提供的最動態的系統。它旨在使開發人員能夠創建自己的二進制代碼加載器和方法分派器,其語義可以與靜態代碼策略(由運行時定義)相匹配或有所區別。反射公開了一個豐富的對象模型,它可以直接用于簡單的用例,但隨著場景變得更加復雜,您就需要更深入地了解 .NET 類型系統。
反射還啟用了一種單獨的模式,其中生成的 IL 字節代碼可以在運行時進行 JIT 編譯,有時用于以專用算法替換通用算法。有了對象模型和其他細節,它通常會被用于序列化器或對象關系映射器中。
編譯后的二進制格式應用程序和庫被編譯為 PE/COFF 格式的標準化跨平臺字節碼。二進制分發最重要的是性能特征。它使應用程序能夠擴展到越來越多的項目。每個庫都包含一個導入和導出類型的數據庫,稱為元數據,它對開發操作和運行應用程序都起著重要作用。
編譯的二進制文件包括兩個主要方面:
二進制字節碼——簡潔而規則的格式,無需在高級語言編譯器(如 C#)編譯后解析文本源。元數據——描述導入和導出的類型,包括給定方法的字節代碼的位置。例如,對于開發,工具可以有效地讀取元數據以確定給定庫公開的類型集以及哪些類型實現了某些接口。此過程可加快編譯速度,并使 IDE 和其他工具能夠準確呈現給定上下文的類型和成員列表。
對于運行時,元數據使庫能夠延遲加載,方法體更是如此。上文討論過的反射是元數據和 IL 的運行時 API。還有其他更適合工具的 API。
隨著時間的推移,IL 格式一直保持向后兼容。最新的 .NET 版本仍然可以加載和執行由 .NET Framework 1.0 編譯器生成的二進制文件。
共享庫通常通過 NuGet 包分發。默認情況下,帶有單個二進制文件的 NuGet 包可以在任何操作系統和體系結構上運行,但也可以專門用于在特定環境中提供特定行為。
代碼生成.NET 字節碼不是機器可執行的格式,它需要通過某種形式的代碼生成器使其可執行。這可以通過提前 (AOT) 編譯、即時 (JIT) 編譯、解釋或轉譯來實現。事實上,這些都是今天在各種場景中使用的。
.NET 以 JIT 編譯而聞名。JIT 在應用程序運行時將方法(和其他成員)編譯為本機代碼,并且僅在需要時才將其編譯,因此得名“及時(just in time,縮寫為 JIT)”。例如,一個程序在運行時可能只調用一種類型中幾種方法中的一種。JIT 還可以利用僅在運行時可用的信息,如初始化的只讀靜態變量的值或程序運行的確切 CPU 模型,并且可以多次編譯相同的方法,以便每次針對不同的目標進行優化,并從以前的編譯中吸取教訓。
JIT 為給定的操作系統和芯片架構生成代碼。.NET 具有支持 Arm64 和 x64 指令集以及 Linux、macOS 和 Windows 操作系統等的 JIT 實現。作為 .NET 開發人員,您不必擔心 CPU 指令集和操作系統調用約定之間的差異。JIT 負責生成 CPU 需要的代碼。它還知道如何為每個 CPU 生成快速代碼,操作系統和 CPU 供應商經常幫助我們做到這一點。
AOT 類似,只是代碼是在程序運行之前生成的。開發人員選擇 AOT 是因為它可以通過消除 JIT 完成的工作來顯著縮短啟動時間。AOT 構建的應用程序本質上是特定于操作系統和體系結構的,這意味著需要額外的步驟才能使應用程序在多個環境中運行。例如,如果您想支持 Linux 和 Windows 以及 Arm64 和 x64,那么您需要構建四個變體(以支持所有組合)。AOT 代碼也可以提供有價值的優化,但總體不如 JIT 多。
代碼生成器優化之一是內在函數。硬件內在函數就是 .NET API 直接轉換為 CPU 指令的例子。這已在整個 .NET 庫中普遍用于 SIMD 指令。
互操作.NET 被特意設計用于與本機庫的低成本互操作。.NET 程序和庫可以無縫調用低級操作系統 API 或利用 C/C++ 庫的龐大生態系統。現代 .NET 運行時專注于提供低級互操作構建塊,例如通過函數指針調用本機方法的能力,將托管方法公開為非托管回調或自定義接口轉換。.NET 也在這個領域不斷發展,在 .NET 7 中發布了源代碼生成的解決方案,進一步減少了開銷并且便于使用 AOT。
下面的代碼演示了 C# 函數指針的效率。
// Using a function pointer avoids a delegate allocation. // Equivalent to `void (*fptr)(int) = &Callback;` in C delegate* unmanaged<int, void> fptr = &Callback; RegisterCallback(fptr); [UnmanagedCallersOnly] static void Callback(int a) => Console.WriteLine($'Callback: {a}'); [LibraryImport('...', EntryPoint = 'RegisterCallback')] static partial void RegisterCallback(delegate* unmanaged<int, void> fptr);此示例使用 .NET 7 中引入的 LibraryImport 源代碼生成器。它位于現有 DllImport 或 P/Invoke 功能之上。
獨立包通過利用這些低級構建塊(例如 ClangSharp、Xamarin.iOS 和 Xamarin.Mac、CsWinRT、CsWin32 和 DNNE?)提供更高級別的特定于域的互操作解決方案。
這些新功能并不意味著內置運行時托管/非托管編組或 Windows COM 互操作等內置互操作解決方案沒有用——我們知道它們有用,而且人們已經開始依賴它們。那些之前內置到運行時中的功能將繼續按原樣提供支持,只是為了向后兼容,我們沒有進一步發展它們的計劃。所有未來的投資都將集中在互操作構建塊以及它們支持的特定領域和更高性能的解決方案上。
二進制分布Microsoft 的 .NET 團隊維護著多個二進制發行版,最近開始支持 Android、iOS 和 WebAssembly。該團隊使用多種技術為這些環境中的每一個環境定制代碼庫。大多數平臺是用 C# 編寫的,這使得移植可以集中在相對較小的組件集上。
社區維護著另一套發行版,主要集中于 Linux 。例如,.NET 已包含在?Alpine Linux、Fedora、Red Hat Enterprise Linux 和 Ubuntu中。
概括我們有幾個版本進入現代 .NET 時代,最近發布了 .NET 7。我們認為,如果我們總結自 .NET Core 1.0 以來我們一直在平臺的最低級別構建的內容,將會很有用。我們明確保留了原始 .NET 的精神,結果是一個新平臺開辟了一條新道路,并為開發人員提供了新的和更多的價值。
讓我們用最開始的話題結束本篇文章。.NET 代表四個值:生產力、性能、安全性和可靠性。我們堅信,當不同的語言平臺提供不同的方法時,開發人員會得到最好的服務。作為一個團隊,我們尋求為 .NET 開發人員提供高生產力,同時提供在性能、安全性和可靠性方面處于領先地位的平臺。
這篇文章由 Jan Kotas、Rich Lander、Maoni Stephens 和 Stephen Toub 撰寫,囊括了 .NET 團隊同事的深刻見解和審閱。
以上就是一文透徹詳解.NET框架類型系統設計要點的詳細內容,更多關于.NET 框架類型系統的資料請關注好吧啦網其它相關文章!
