# 是一種通用、類型安全、面向對象的編程語言。該語言的目標是程序員的生產力。為此,C# 平衡了簡單性、表現力和性能。自第一個版本以來,該語言的首席架構師是Anders Hejlsberg(Turbo Pascal的創建者和Delphi的架構師)。C# 語言與平臺無關,適用于一系列特定于平臺的運行時。
C# 是面向對象范例的豐富實現,其中包括、和。封裝意味著在周圍創建邊界,以將其外部(公共)行為與其內部(私有)實現細節分開。從面向對象的角度來看,以下是 C# 的獨特功能:
統一型系統
C# 中的基本構建基塊是稱為數據和函數的封裝單元。C# 具有,其中所有類型最終共享一個公共基類型。這意味著所有類型,無論它們表示業務對象還是基元類型(如數字),都共享相同的基本功能。例如,任何類型的實例都可以通過調用其 ToString 方法轉換為字符串。
類和接口
在傳統的面向對象范式中,唯一的類型是類。在 C# 中,還有其他幾種類型,其中一種是。接口就像一個無法保存數據的類。這意味著它只能定義(而不是),這允許多重繼承以及規范和實現之間的分離。
屬性、方法和事件
在純面向對象的范式中,所有函數都是。在 C# 中,方法只是一種,它還包括和事件(還有其他屬性和)。屬性是封裝對象狀態片段(如按鈕的顏色或標簽的文本)的函數成員。事件是簡化對對象狀態更改的操作的函數成員。
盡管 C# 主要是一種面向對象的語言,但它也借鑒了函數式編程范;具體說來:
函數可以被視為值
使用,C# 允許函數作為值傳遞到其他函數或從其他函數傳遞。
C# 支持純度模式
函數式編程的核心是避免使用值發生變化的變量,而支持聲明性模式。C# 具有幫助處理這些模式的關鍵功能,包括能夠動態編寫“捕獲”變量( 表達式)的未命名函數,以及通過執行列表或響應式編程的能力。C# 還提供,這使得編寫(只讀)類型變得容易。
C# 主要是一種語言,這意味著類型的實例只能通過它們定義的協議進行交互,從而確保每個類型的內部一致性。例如,C# 阻止您與類型交互,就好像它是類型一樣。
更具體地說,C# 支持靜態類型,這意味著該語言在強制實施安全。這是對在強制實施的類型安全的補充。
靜態類型甚至在程序運行之前就消除了一大類錯誤。它將負擔從運行時單元測試轉移到編譯器上,以驗證程序中的所有類型是否正確組合在一起。這使得大型程序更易于管理、更可預測且更健壯。此外,靜態類型允許Visual Studio中的IntelliSense等工具幫助您編寫程序,因為它知道給定變量的類型,因此可以對該變量調用哪些方法。此類工具還可以識別程序中使用變量、類型或方法的任何地方,從而實現可靠的重構。
C# 還允許通過 dynamic 關鍵字動態鍵入部分代碼。但是,C# 仍然是一種主要是靜態類型的語言。
C# 也稱為,因為它的類型規則是嚴格強制執行的(無論是靜態還是在運行時)。例如,不能調用旨在接受具有浮點數的整數的函數,浮點數顯式轉換為整數。這有助于防止錯誤。
C# 依賴于運行時來執行自動內存管理。公共語言運行庫有一個垃圾回收器,該回收器作為程序的一部分執行,為不再引用的對象回收內存。這使程序員不必顯式地為對象釋放內存,從而消除了在C++等語言中遇到的錯誤指針的問題。
C# 不會消除指針:它只是使大多數編程任務不需要指針。對于性能關鍵型熱點和互操作性,允許在標記為不安全的塊中使用指針和顯式內存分配。
C# 具有支持以下平臺的運行時:
還有一種稱為 的技術,可以將 C# 編譯為在瀏覽器中運行的 Web 程序集。
C# 程序的運行時支持包括和。運行時還可以包括更高級別的,其中包含用于開發胖客戶端、移動或 Web 應用程序的庫(參見)。存在不同的運行時以允許不同類型的應用程序以及不同的平臺。
(CLR) 提供必要的運行時服務,如自動內存管理和異常處理。(“common”一詞是指同一運行時可以由其他編程語言(如 F#、Visual Basic 和托管C++)共享的事實。
C# 之所以稱為托管語言,是因為它將源代碼編譯為代碼,托管代碼以 (IL) 表示。CLR 將 IL 轉換為計算機的本機代碼,如 X64 或 X86,通常在執行之前。這稱為實時 (JIT) 編譯。提前編譯也可用于縮短大型程序集或資源受限設備的啟動時間(并在開發移動應用程序時滿足 iOS 應用商店規則)。
托管代碼的容器稱為。程序集不僅包含 IL,還包含類型信息()。元數據的存在允許程序集引用其他程序集中的類型,而無需其他文件。
您可以使用Microsoft的 工具檢查和反匯編程序集的內容。使用ILSpy或JetBrain的dotPeek等工具,您可以更進一步將IL反編譯為C#。由于 IL 比本機機器代碼級別更高,因此反編譯器可以很好地重建原始 C#。
程序可以查詢自己的元數據(反射),甚至可以在運行時生成新的 IL()。
CLR 始終附帶一組稱為 (BCL) 的程序集。BCL 為程序員提供核心功能,例如集合、輸入/輸出、文本處理、XML/JSON 處理、網絡、加密、互操作、并發和并行編程。
BCL 還實現 C# 語言本身所需的類型(用于枚舉、查詢和異步等功能),并允許您顯式訪問 CLR 的功能,如反射和內存管理。
(也稱為)是下載和安裝的可部署單元。運行時由 CLR(及其 BCL)以及特定于您正在編寫的應用程序類型(Web、移動、富客戶端等)的可選應用程序層組成(如果要編寫命令行控制臺或非 UI 庫,則不需要應用程序層。
編寫應用程序時,以特定運行時,這意味著應用程序使用并依賴于運行時提供的功能。運行時的選擇還決定了應用程序將支持哪些平臺。
下表列出了主要的運行時選項:
應用層 | CLR/BCL | 程序類型 | 運行在... |
ASP.NET | .NET 6 | 蹼 | Windows, Linux, macOS |
視窗桌面 | .NET 6 | 窗戶 | 視窗 7-10+ |
毛伊島(2022年初) | .NET 6 | 移動設備、桌面版 | iOS, 安卓, 蘋果操作系統, 視窗 10+ |
WinUI 3(2022 年初) | .NET 6 | 贏10 | 視窗 10+ 桌面 |
UWP | .NET Core 2.2 | Win10 + Win10 設備 | Windows 10+ 桌面和設備 |
(Legacy) .NET Framework | .NET 框架 | 網頁, 視窗 | 視窗 7-10+ |
以圖形方式顯示了此信息,也可作為本書所涵蓋內容的指南。
.NET 6 是 Microsoft 的旗艦開源運行時。您可以編寫在 Windows、Linux 和 macOS 上運行的 Web 和控制臺應用程序,在 Windows 7 到 11 和 macOS 上運行的胖客戶端應用程序,以及在 iOS 和 Android 上運行的移動應用程序。本書重點介紹.NET 6 CLR和BCL。
與.NET Framework不同,.NET 6沒有預安裝在Windows機器上。如果在不存在正確運行時的情況下嘗試運行 .NET 6 應用程序,則會出現一條消息,將您定向到可在其中下載運行時的網頁。可以通過創建部署來避免這種情況,該部署包括應用程序所需的運行時部分。
.NET 6的前身是.NET 5,其前身是.NET Core 3。(Microsoft從名稱中刪除了“核心”,并跳過了版本 4)。跳過版本的原因是為了避免與 4.x混淆。
這意味著在大多數情況下,在 .NET Core 版本 1、2 和 3(以及 .NET 5)下編譯的程序集將在 .NET 6 下無需修改即可運行。相反,在(任何版本的).NET Framework 下編譯的程序集通常與 .NET 6 不兼容。
.NET 6 BCL和CLR與.NET 5(和.NET Core 3)非常相似,其區別主要集中在性能和部署上。
(多平臺應用程序 UI,2022 年初)旨在創建適用于 iOS 和 Android 的移動應用程序,以及適用于 macOS 和 Windows 的跨平臺桌面應用程序。MAUI 是 Xamarin 的演變,允許單個項目面向多個平臺。
(UWP) 旨在編寫在 Windows 10+ 桌面和設備(Xbox、Surface Hub 和 HoloLens)上運行的沉浸式觸摸優先應用程序。UWP 應用經過沙盒處理,并通過 Windows 應用商店提供。UWP 預裝在 Windows 10 中。它使用 .NET Core 2.2 CLR/BCL 的版本,并且不太可能更新此依賴項。相反,Microsoft發布了一個名為的繼任者,作為的一部分。
Windows 應用 SDK 適用于最新的 .NET,與 .NET 桌面 API 更好地集成,并且可以在沙盒外部運行。但是,它尚不支持Xbox或HoloLens等設備。
是 Microsoft 最初的僅限 Windows 的運行時,用于編寫(僅)在 Windows 桌面/服務器上運行的 Web 和富客戶端應用程序。沒有計劃發布重大的新版本,但由于現有應用程序的豐富性,Microsoft將繼續支持和維護當前的 4.8 版本。
在.NET Framework中,CLR/BCL與應用程序層集成在一起。用 .NET Framework 編寫的應用程序可以在 .NET 6 下重新編譯,盡管它們通常需要一些修改。.NET Framework 的某些功能在 .NET 6 中不存在(反之亦然)。
.NET Framework 預裝在 Windows 中,并通過 Windows Update 自動修補。當您面向 .NET Framework 4.8 時,可以使用 C# 7.3 及更早版本的功能。
長期以來,“.NET”一詞一直被用作包含“.NET”一詞的任何技術(.NET Framework,.NET Core,.NET Standard等)的總稱。
這意味著Microsoft將.NET Core重命名為.NET造成了不幸的歧義。在本書中,我們將新的.NET稱為。為了提到.NET Core及其后續版本,我們將使用短語“.NET Core和.NET 5+”。
更令人困惑的是,.NET(5+)是一個框架,但它與非常不同。因此,在可能的情況下,我們將優先使用術語而不是。
還有以下利基運行時:
也可以在 SQL Server 中運行托管代碼。通過 SQL Server CLR 集成,可以使用 C# 編寫自定義函數、存儲過程和聚合,然后從 SQL 調用它們。它與 .NET Framework 和特殊的“托管”CLR 結合使用,后者強制實施沙箱以保護 SQL Server 進程的完整性。
以下是每個 C# 版本中新功能的反向時間順序,以便已經熟悉該語言舊版本的讀者受益。
C# 10 隨 Visual Studio 2022 一起提供,并在面向 .NET 6 時使用。
在文件中所有類型都在單個命名空間中定義的常見情況下,C# 10 中的聲明可減少混亂并消除不必要的縮進級別:
namespace MyNamespace; // Applies to everything that follows in the file.
class Class1 {} // inside MyNamespace
class Class2 {} // inside MyNamespace
在 using 指令前面加上 global 關鍵字時,它會將該指令應用于項目中的所有文件:
global using System;
global using System.Collection.Generic;
這樣可以避免在每個文件中重復相同的指令。全局 using 指令與 using static .
此外,.NET 6 項目現在支持:如果在項目文件中將 ImplicitUsings 元素設置為 true,則會自動導入最常用的命名空間(基于 SDK 項目類型)。有關更多詳細信息,請參閱中的
C# 9 引入了 with 關鍵字,用于對記錄執行非破壞性更改。在 C# 10 中,with 關鍵字也適用于匿名類型:
var a1 = new { A = 1, B = 2, C = 3, D = 4, E = 5 };
var a2 = a1 with { E = 10 };
Console.WriteLine (a2); // { A = 1, B = 2, C = 3, D = 4, E = 10 }
C# 7 引入了元組(或使用解構方法的任何類型的元組)的解構語法。C# 10 更進一步擴展了此語法,允許您在同一解構中混合賦值和聲明:
var point = (3, 4);
double x = 0;
(x, double y) = point;
從 C# 10 開始,可以在結構中包含字段初始值設定項和無參數構造函數(請參閱中的)。這些僅在顯式調用構造函數時執行,因此可以輕松繞過 - 例如,通過默認關鍵字。引入此功能主要是為了結構記錄。
記錄最初是在 C# 9 中引入的,它們充當編譯增強類。在 C# 10 中,記錄也可以是結構:
record struct Point (int X, int Y);
規則在其他方面是相似的:記錄結構與具有大致相同的功能(請參閱中的)。 例外情況是,編譯器在記錄結構上生成的屬性是可寫的,除非您在記錄聲明前面加上 readonly 關鍵字。
圍繞 lambda 表達式的語法已通過多種方式得到增強。首先,允許隱式類型 ( var ):
var greeter = () => "Hello, world";
lambda 表達式的隱式類型是 Action 或 Func 委托,因此在這種情況下,greeter 的類型為 Func<string> 。必須顯式聲明任何參數類型:
var square = (int x) => x * x;
其次,lambda 表達式可以指定返回類型:
var sqr = int (int x) => x;
這主要是為了提高復雜嵌套 lambda 的編譯器性能。
第三,您可以將 lambda 表達式傳遞到對象、委托或表達式類型的方法參數中:
M1 (() => "test"); // Implicitly typed to Func<string>
M2 (() => "test"); // Implicitly typed to Func<string>
M3 (() => "test"); // Implicitly typed to Expression<Func<string>>
void M1 (object x) {}
void M2 (Delegate x) {}
void M3 (Expression x) {}
最后,您可以將屬性應用于 lambda 表達式的編譯生成的目標方法(及其參數和返回值):
Action a = [Description("test")] () => { };
有關更多詳細信息,請參閱中的
以下簡化語法在 C# 10 中對于嵌套屬性模式匹配是合法的(請參閱中的):
var obj = new Uri ("https://www.linqpad.net");
if (obj is Uri { Scheme.Length: 5 }) ...
這相當于:
if (obj is Uri { Scheme: { Length: 5 }}) ...
應用 [CallerArgumentExpression] 屬性的方法參數從調用站點捕獲參數表達式:
Print (Math.PI * 2);
void Print (double number,
[CallerArgumentExpression("number")] string expr = null)
=> Console.WriteLine (expr);
// Output: Math.PI * 2
此功能主要用于驗證和斷言庫(請參閱中的)。
#line 指令已在 C# 10 中得到增強,允許指定列和范圍。
C# 10 中的內插字符串可以是常量,只要內插的值是常量即可。
記錄可以在 C# 10 中密封 ToString() 方法。
C# 的定賦值分析已得到改進,因此表達式如下:
if (foo?.TryParse ("123", out var number) ?? false)
Console.WriteLine (number);
(在 C# 10 之前,編譯器將生成錯誤:“使用未賦值的局部變量'number'。
C# 9.0 隨 一起提供,并在面向 .NET 5 時使用。
使用(參見中的),您可以編寫一個沒有 Main 方法和 Program 類包袱的程序:
using System;
Console.WriteLine ("Hello, world");
頂級語句可以包含方法(充當本地方法)。您還可以通過 “magic” args 變量訪問命令行參數,并向調用方返回一個值。頂級語句后可以跟類型和命名空間聲明。
屬性聲明中的僅(參見中的使用 init 關鍵字而不是 set 關鍵字:
class Foo { public int ID { get; init; } }
這類似于只讀屬性,只是它也可以通過對象初始值設定項進行設置:
var foo = new Foo { ID = 123 };
這使得創建可通過對象初始值設定項而不是構造函數填充的不可變(只讀)類型成為可能,并有助于避免接受大量可選參數的構造函數的反模式。僅初始化設置器在中使用時還允許。
(參見中的是一種特殊的類,旨在很好地處理不可變數據。它最特別的特點是關鍵字(帶有):
Point p1 = new Point (2, 3);
Point p2 = p1 with { Y = 4 }; // p2 is a copy of p1, but with Y set to 4
Console.WriteLine (p2); // Point { X = 2, Y = 4 }
record Point
{
public Point (double x, double y) => (X, Y) = (x, y);
public double X { get; init; }
public double Y { get; init; }
}
在簡單情況下,記錄還可以消除定義屬性以及編寫構造函數和解構函數的樣板代碼。我們可以將點記錄定義替換為以下內容,而不會丟失功能:
record Point (double X, double Y);
與元組一樣,默認情況下,記錄表現出結構相等性。記錄可以對其他記錄進行子類化,并且可以包含類可以包含的相同構造。編譯器在運行時將記錄實現為類。
(參見中的允許 <、>、<= 和 >= 運算符出現在模式中:
string GetWeightCategory (decimal bmi) => bmi switch {
< 18.5m => "underweight",
< 25m => "normal",
< 30m => "overweight",
_ => "obese" };
使用,您可以通過三個新關鍵字( 和 , or 和 not )組合模式:
bool IsVowel (char c) => c is 'a' or 'e' or 'i' or 'o' or 'u';
bool IsLetter (char c) => c is >= 'a' and <= 'z'
or >= 'A' and <= 'Z';
與 && 和 || 一樣運算符,并且具有高于 或 的優先級。您可以使用括號覆蓋此設置。
not 組合器可以與類型模式一起使用,以測試對象是否是(不是):
if (obj is not string) ...
構造對象時,C# 9 允許在編譯器可以明確推斷類型名稱時省略類型名稱:
System.Text.StringBuilder sb1 = new();
System.Text.StringBuilder sb2 = new ("Test");
當變量聲明和初始化位于代碼的不同部分時,這特別有用:
class Foo
{
System.Text.StringBuilder sb;
public Foo (string initialValue) => sb = new (initialValue);
}
在以下情況下:
MyMethod (new ("test"));
void MyMethod (System.Text.StringBuilder sb) { ... }
有關詳細信息,請參閱中的
C# 9 引入了函數指針(請參閱 章中的“函數指針”和第 中的)。 它們的主要用途是允許非托管代碼在 C# 中調用靜態方法,而無需委托實例的開銷,并且能夠在參數和返回類型可 (在每一端以相同方式表示)時繞過 P/Invoke 層。
C# 9 還引入了 nint 和 nuint 本機大小的整數類型(請參閱中的),這些類型在運行時映射到 System.IntPtr 和 System.UIntPtr 。在編譯時,它們的行為類似于支持算術運算的數字類型。
此外,C# 9 現在允許你:
C# 8.0 首次隨 一起提供,今天在面向 .NET Core 3 或 .NET Standard 2.1 時仍在使用。
簡化了數組的元素或部分(或低級類型 Span<T> 和 ReadOnlySpan<T> )的使用。
索引允許您使用 ^ 運算符引用相對于數組的元素。^1 表示最后一個元素,^2 表示倒數第二個元素,依此類推:
char[] vowels = new char[] {'a','e','i','o','u'};
char lastElement = vowels [^1]; // 'u'
char secondToLast = vowels [^2]; // 'o'
范圍允許您使用 ..算子:
char[] firstTwo = vowels [..2]; // 'a', 'e'
char[] lastThree = vowels [2..]; // 'i', 'o', 'u'
char[] middleOne = vowels [2..3] // 'i'
char[] lastTwo = vowels [^2..]; // 'o', 'u'
C# 借助索引和范圍類型實現索引和范圍:
Index last = ^1;
Range firstTwoRange = 0..2;
char[] firstTwo = vowels [firstTwoRange]; // 'a', 'e'
可以通過定義參數類型為 Index 或 Range 的索引器來支持自己的類中的索引和范圍:
class Sentence
{
string[] words = "The quick brown fox".Split();
public string this [Index index] => words [index];
public string[] this [Range range] => words [range];
}
有關更多信息,請參閱中的
這??= 運算符僅在變量為 null 時才分配變量。而不是
if (s == null) s = "Hello, world";
您現在可以這樣寫:
s ??= "Hello, world";
如果省略 using 語句后面的方括號和語句塊,則它將成為 。然后,當執行落在語句塊之外時,將釋放資源:
if (File.Exists ("file.txt"))
{
using var reader = File.OpenText ("file.txt");
Console.WriteLine (reader.ReadLine());
...
}
在這種情況下,當執行落在 if 語句塊之外時,將釋放讀取器。
C# 8 允許您將只讀修飾符應用于結構的函數,確保如果嘗試修改任何字段,則會生成編譯時錯誤:
struct Point
{
public int X, Y;
public readonly void ResetX() => X = 0; // Error!
}
如果只讀函數調用非只讀函數,編譯器將生成警告(并防御性地復制結構以避免發生突變的可能性)。
將 static 修飾符添加到局部方法可防止它看到封閉方法的局部變量和參數。這有助于減少耦合,并使本地方法能夠隨心所欲地聲明變量,而不會有與包含方法中的變量發生沖突的風險。
C# 8 允許您向接口成員添加默認實現,使其成為可選的實現:
interface ILogger
{
void Log (string text) => Console.WriteLine (text);
}
這意味著您可以在不中斷實現的情況下向接口添加成員。默認實現必須通過接口顯式調用:
((ILogger)new Logger()).Log ("message");
接口還可以定義靜態成員(包括字段),這些成員可以從默認實現中的代碼訪問:
interface ILogger
{
void Log (string text) => Console.WriteLine (Prefix + text);
static string Prefix = "";
}
或從接口外部,除非通過靜態接口成員(例如私有、受保護或內部)上的可訪問性修飾符進行限制:
ILogger.Prefix = "File log: ";
禁止使用實例字段。有關更多詳細信息,請參閱中的
從 C# 8 開始,可以在的上下文中使用 switch:
string cardName = cardNumber switch // assuming cardNumber is an int
{
13 => "King",
12 => "Queen",
11 => "Jack",
_ => "Pip card" // equivalent to 'default'
};
有關更多示例,請參閱中的
C# 8 支持三種新模式,主要是為了 switch 語句/表達式(請參閱中的)。允許您打開多個值:
int cardNumber = 12; string suite = "spades";
string cardName = (cardNumber, suite) switch
{
(13, "spades") => "King of spades",
(13, "clubs") => "King of clubs",
...
};
模式允許對公開解構函數的對象使用類似的語法,允許您匹配對象的屬性。您可以在開關中和 is 運算符中使用所有模式。下面的示例使用來測試 obj 是否為長度為 4 的字符串:
if (obj is string { Length:4 }) ...
可為 null 類型為值類型帶來可為空性,而 的引用類型則相反,為引用類型帶來(一定程度的),目的是幫助避免 NullReferenceExceptions。可為 null 的引用類型引入了一種安全級別,當編譯器檢測到存在生成 NullReferenceException 風險的代碼時,該級別純粹由編譯器以警告或錯誤的形式強制實施。
可以在項目級別(通過 項目文件中的 Nullable 元素)或在代碼(通過 #nullable 指令)啟用可為 null 的引用類型。啟用后,編譯器會將非可為空性設為默認值:如果希望引用類型接受 null,則必須應用 ?指示后綴:
#nullable enable // Enable nullable reference types from this point on
string s1 = null; // Generates a compiler warning! (s1 is non-nullable)
string? s2 = null; // OK: s2 is nullable reference type
未初始化的字段也會生成警告(如果類型未標記為可為空),如果編譯器認為可能發生 NullReferenceException,則取消引用可為空的引用類型也是如此:
void Foo (string? s) => Console.Write (s.Length); // Warning (.Length)
若要刪除警告,可以使用 ( !
void Foo (string? s) => Console.Write (s!.Length);
有關完整討論,請參閱中的
在 C# 8 之前,可以使用 yield return 編寫,或等待編寫。但是你不能同時做這兩件事,并編寫一個等待的迭代器,異步生成元素。C# 8 通過引入來解決此問題:
async IAsyncEnumerable<int> RangeAsync (
int start, int count, int delay)
{
for (int i = start; i < start + count; i++)
{
await Task.Delay (delay);
yield return i;
}
}
await foreach 語句使用異步流:
await foreach (var number in RangeAsync (0, 10, 100))
Console.WriteLine (number);
有關詳細信息,請參閱中的
C# 7.x 最初隨 Visual Studio 2017 一起提供。今天,當您面向.NET Core 7,.NET Framework 3.2019至2.4或.NET Standard 6.4時,Visual Studio 8仍在使用C# 2.0。
C# 7.3 對現有功能進行了細微改進,例如允許將相等運算符與元組一起使用、改進重載分辨率以及將特性應用于自動屬性的支持字段的功能:
[field:NonSerialized]
public int MyProperty { get; set; }
C# 7.3 還基于 C# 7.2 的高級低分配編程功能構建,能夠重新分配 ,在索引固定字段時無需固定,并且使用 stackalloc 支持字段初始值設定項:
int* pointer = stackalloc int[] {1, 2, 3};
Span<int> arr = stackalloc [] {1, 2, 3};
請注意,堆棧分配的內存可以直接分配給 Span<T> 。我們將在第 中描述跨度,以及為什么要使用它們。
C# 7.2 添加了一個新的私有受保護修飾符(內部修飾符和受保護修飾符的),在調用方法時跟隨帶有位置參數的命名參數的功能,以及只讀結構。只讀結構強制所有字段都是只讀的,以幫助聲明意圖并允許編譯器更大的優化自由度:
readonly struct Point
{
public readonly int X, Y; // X and Y must be readonly
}
C# 7.2 還添加了專門的功能來幫助進行微優化和低分配編程:請參閱 章中的“引用”和“,以及中的
從 C# 7.1 開始,如果可以推斷出類型,則可以在使用默認關鍵字時省略該類型:
decimal number = default; // number is decimal
C# 7.1 還放寬了 switch 語句的規則(以便可以在泛型類型參數上進行模式匹配),允許程序的 Main 方法是異步的,并允許推斷元組元素名稱:
var now = DateTime.Now;
var tuple = (now.Hour, now.Minute, now.Second);
C# 7 中的數字文本可以包含下劃線以提高可讀性。這些稱為數字分隔符,編譯器會忽略這些:
int million = 1_000_000;
可以使用 0b 前綴指定:
var b = 0b1010_1011_1100_1101_1110_1111;
C# 7 使調用包含 out 參數的方法變得更加容易。首先,您現在可以動態聲明(請參閱中的):
bool successful = int.TryParse ("123", out int result);
Console.WriteLine (result);
當調用具有多個 out 參數的方法時劃線字符丟棄您不感興趣的方法:
SomeBigMethod (out _, out _, out _, out int x, out _, out _, out _);
Console.WriteLine (x);
您還可以使用 is 運算符動態引入變量。這些被稱為模式變量(參見中的):
void Foo (object x)
{
if (x is string s)
Console.WriteLine (s.Length);
}
switch 語句還支持類型模式,因此您可以打開常量(請參閱中的)。您可以使用 when 子句指定條件,也可以打開空值:
switch (x)
{
case int i:
Console.WriteLine ("It's an int!");
break;
case string s:
Console.WriteLine (s.Length); // We can use the s variable
break;
case bool b when b == true: // Matches only when b is true
Console.WriteLine ("True");
break;
case null:
Console.WriteLine ("Nothing");
break;
}
在另一個函數中聲明的方法(請參閱中的):
void WriteCubes()
{
Console.WriteLine (Cube (3));
Console.WriteLine (Cube (4));
Console.WriteLine (Cube (5));
int Cube (int value) => value * value * value;
}
局部方法僅對包含函數可見,并且可以像 lambda 表達式一樣捕獲局部變量。
C# 6 為方法、只讀屬性、運算符和索引器引入了表達式體“fat-arrow”語法。C# 7 將其擴展到構造函數、讀/寫屬性和終結器:
public class Person
{
string name;
public Person (string name) => Name = name;
public string Name
{
get => name;
set => name = value ?? "";
}
~Person () => Console.WriteLine ("finalize");
}
C# 7 引入了解構模式(請參閱中的)。構造函數通常采用一組值(作為參數)并將它們分配給字段,而則執行相反的操作,并將字段分配回一組變量。我們可以在前面的示例中為 Person 類編寫一個解構函數,如下所示(除了異常處理):
public void Deconstruct (out string firstName, out string lastName)
{
int spacePos = name.IndexOf (' ');
firstName = name.Substring (0, spacePos);
lastName = name.Substring (spacePos + 1);
}
解構函數使用以下特殊語法調用:
var joe = new Person ("Joe Bloggs");
var (first, last) = joe; // Deconstruction
Console.WriteLine (first); // Joe
Console.WriteLine (last); // Bloggs
也許對 C# 7 最顯著的改進是顯式支持(請參閱中的)。元組提供了一種存儲一組相關值的簡單方法:
var bob = ("Bob", 23);
Console.WriteLine (bob.Item1); // Bob
Console.WriteLine (bob.Item2); // 23
C# 的新元組是使用 System.ValueTuple<...>泛型結構。但是多虧了編譯器的魔力,元組元素可以命名為:
var tuple = (name:"Bob", age:23);
Console.WriteLine (tuple.name); // Bob
Console.WriteLine (tuple.age); // 23
使用元組,函數可以返回多個值,而無需求助于 out 參數或額外的類型包袱:
static (int row, int column) GetFilePosition() => (3, 10);
static void Main()
{
var pos = GetFilePosition();
Console.WriteLine (pos.row); // 3
Console.WriteLine (pos.column); // 10
}
元組隱式支持解構模式,因此您可以輕松地將它們為單個變量:
static void Main()
{
(int row, int column) = GetFilePosition(); // Creates 2 local variables
Console.WriteLine (row); // 3
Console.WriteLine (column); // 10
}
在 C# 7 之前,throw 始終是一個語句。現在,它也可以在表達式體函數中顯示為表達式:
public string Foo() => throw new NotImplementedException();
拋出表達式也可以出現在三元條件表達式中:
string Capitalize (string value) =>
value == null ? throw new ArgumentException ("value") :
value == "" ? "" :
char.ToUpper (value[0]) + value.Substring (1);
附帶的C# 0.2015具有新一代編譯器,完全用C#編寫。新的編譯器稱為項目“Roslyn”,通過庫公開整個編譯管道,允許您對任意源代碼執行代碼分析。編譯器本身是開源的,源代碼可在 獲得。
此外,C# 6.0 還具有幾個次要但重要的增強功能,主要旨在減少代碼混亂。
(“Elvis”)運算符(參見中的避免了在調用方法或訪問類型成員之前顯式檢查null。在下面的示例中,結果計算結果為 null,而不是拋出 NullReferenceException:
System.Text.StringBuilder sb = null;
string result = sb?.ToString(); // result is null
(參見中的允許以 lambda 表達式的樣式更簡潔地編寫組成單個表達式的方法、屬性、運算符和索引器:
public int TimesTwo (int x) => x * 2;
public string SomeProperty => "Property value";
()允許您為自動屬性分配初始值:
public DateTime TimeCreated { get; set; } = DateTime.Now;
初始化的屬性也可以是只讀的:
public DateTime TimeCreated { get; } = DateTime.Now;
還可以在構造函數中設置只讀屬性,從而更輕松地創建不可變(只讀)類型。
()允許公開索引器的任何類型的單步初始化:
var dict = new Dictionary<int,string>()
{
[3] = "three",
[10] = "ten"
};
字符串(參見中的的簡潔替代方法。格式:
string s = $"It is {DateTime.Now.DayOfWeek} today";
(參見中的允許您將條件應用于 catch 塊:
string html;
try
{
html = await new HttpClient().GetStringAsync ("http://asef");
}
catch (WebException ex) when (ex.Status == WebExceptionStatus.Timeout)
{
...
}
using static(參見中的指令允許您導入類型的所有靜態成員,以便您可以使用這些非限定成員:
using static System.Console;
...
WriteLine ("Hello, world"); // WriteLine instead of Console.WriteLine
nameof()運算符以字符串形式返回變量、類型或其他符號的名稱。這樣可以避免在 Visual Studio 中重命名符號時中斷代碼:
int capacity = 123;
string x = nameof (capacity); // x is "capacity"
string y = nameof (Uri.Host); // y is "Host"
最后,您現在可以等待內部捕獲并最終阻止。
C# 5.0 的一大新功能是通過兩個新關鍵字 async 和 await 支持。異步函數支持,這使得編寫響應式和線程安全的胖客戶端應用程序變得更加容易。它們還可以輕松編寫高并發且高效的 I/O 綁定應用程序,這些應用程序不會占用每個操作的線程資源。我們將在第 中詳細介紹異步函數。
C# 4.0 引入了四個主要增強功能:
( 章和第 19 章)將(解析類型和成員的過程)從編譯時推遲到運行時,并且在需要復雜反射代碼的方案中非常有用。動態綁定在與動態語言和 COM 組件進行互操作時也很有用。
()允許函數指定默認參數值,以便調用方可以省略參數,參數允許函數調用方按名稱而不是位置標識參數。
在 C# 4.0(和第 )中放寬了類型規則,以便泛型接口和泛型委托中的類型參數可以標記為變或,從而允許更自然的類型轉換。
()在 C# 4.0 中以三種方式得到增強。首先,參數可以在沒有 ref 關鍵字的情況下通過引用傳遞(與可選參數結合使用特別有用)。其次,可以而不是包含 COM 互操作類型的程序集。鏈接互操作類型支持類型等效性,避免了的需求,并結束了版本控制和部署難題。第三,從鏈接互操作類型返回 COM-Variant 類型的函數被映射到動態而不是對象,從而消除了強制轉換的需要。
添加到 C# 3.0 的功能主要集中在 (LINQ) 功能上。LINQ 允許直接在 C# 程序中編寫查詢并檢查其正確性,并查詢本地集合(如列表或 XML 文檔)或遠程數據源(如數據庫)。為支持 LINQ 而添加的 C# 3.0 功能包括隱式類型化局部變量、匿名類型、對象初始值設定項、lambda 表達式、擴展方法、查詢表達式和表達式樹。
變量(var關鍵字,)允許您在聲明語句中省略變量類型,從而允許編譯器推斷它。這減少了混亂,并允許(),匿名類型是動態創建的簡單類,通常用于 LINQ 查詢的最終輸出。您還可以隱式類型化數組()。
()允許您在構造函數調用后以內聯方式設置屬性,從而簡化了對象構造。對象初始值設定項同時適用于命名類型和匿名類型。
()是由編譯器動態創建的微型函數;它們在“流暢”的 LINQ 查詢中特別有用()。
方法()使用新方法擴展現有類型(不更改類型的定義),使靜態方法感覺像實例方法。LINQ 的查詢運算符作為擴展方法實現。
()為編寫 LINQ 查詢提供了更高級別的語法,在處理多個序列或范圍變量時,該語法可以簡單得多。
()是微型代碼文檔對象模型 (DOM),用于描述分配給特殊類型 Expression<TDelegate> 的 lambda 表達式。表達式樹使 LINQ 查詢可以遠程執行(例如,在數據庫服務器上),因為它們可以在運行時進行內省和轉換(例如,轉換為 SQL 語句)。
C# 3.0 還添加了自動屬性和分部方法。
()通過讓編譯器自動執行該工作來減少編寫屬性的工作,這些屬性只是獲取/設置私有支持字段。()讓自動生成的分部類為手動創作提供可自定義的鉤子,如果不使用,這些鉤子就會“消失”。
C# 2 中的重大新功能是泛型(第 章)、可為空的值類型( 章)、迭代器()和匿名方法(lambda 表達式的前身)。這些功能為在 C# 3 中引入 LINQ 鋪平了道路。
C# 2 還添加了對分部類、靜態類以及大量次要和雜項功能(如命名空間別名限定符、友元程序集和固定大小緩沖區)的支持。
泛型的引入需要新的 CLR (CLR 2.0),因為泛型在運行時保持完整類型保真度。
IT之家 6 月 14 日消息,微軟近日邀請 Release Preview 通道的 Windows Insider 項目成員,通過安裝 KB5011048 更新,測試最新發布的 .NET Framework 4.8.1。
微軟本次發布的 KB5011048 更新特別適用于以下版本:
Win11 Version 21H2(初代版本)
Win10 Version 21H2(LTSC)
Win10 Version 22H2
IT之家注:微軟官方今天宣布停止對 Win10 Version 21H2 的支持,因此這是該版本 Win10 可以升級使用的 .NET Framework 最后一個版本。
微軟于 2022 年 8 月宣布了.NET Framework 4.8.1 更新的主要功能,其中最大的亮點在于為 Win11 設備提供 Arm64 原生支持。