自從我們啟動快速發展的 .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 團隊同事的深刻見解和審閱。