--- tags: C# , Language --- # 版本整理摘要 ##### 下载 .NET [下载 .NET 版本](https://dotnet.microsoft.com/zh-cn/download/dotnet) ### 版本整理圖表 | C#版本 | 釋出時間 | .NET 版本 | VS版本 | |:---------------- | -------------- | ----------------------------------------- | ------------- | | C#1.0 | 2002-02-13 | .NET Framework 1.0 | VS.NET 2002 | | C#1.1 C#1.2 | 2003-04-24 | .NET Framework 1.1 | VS.NET 2003 | | C#2.0 C#3.0 | 2005-11-07 | .NET Framework 2.0 | VS2005 | | C#3.0 | 2006-11 | .NET Framework 3.0 | VS2008 | | C#3.0 (導入LinQ) | 2007-11-19 | .NET Framework 3.5 | VS2008 | | C#4.0 | 2010-4-12 | .NET Framework 4.0 | VS2010 | | C#5.0 | 2012-02-20 | .NET Framework 4.5 | VS2012 | | C#5.0 | 2013-10-17 | .NET Framework 4.5.1 | VS2013 | | C#5.0 | 2014-05-05 | .NET Framework 4.5.2 | VS2013 | | C#6.0 | 2015-07-26 | .NET Framework 4.6 | VS2015(v14) | | C#6.0 | 2015-11-30 | .NET Framework 4.6.1 | VS2015(v14) | | C#7.0 | 2016-08-02 | .NET Framework 4.6.2 | VS2017(v15) | | C#7.1 | 2017-04-05 | .NET Framework 4.7 | VS2017(v15.3) | | C#7.2 | 2017-10-17 | .NET Framework 4.7.1 | VS2017(v15.5) | | C#7.3 | 2018-04-30 | .NET Framework 4.7.2 (對應 .Net Core 2.X) | VS2017(v15.7) | | C#8 | 2019-04-18 | .NET Framework 4.8 (對應 .Net Core 3.X) | VS2019(v16) | | C#9 | 2020-11 | .NET 5.0 | VS2019(16.8) | | C#10 | 2021年11-08 | .NET 6.0 ~ 6.0.36 | VS2022(17.0) | | C#11 | 2022-11-8 | .Net 7.0.0 ~7.0.20 | VS2022(17.4) | | C#12 | 2023年11月14日 | .Net 8.0.0 | | | C#13 | 2024年12月3日 | .Net 9.0.0 | | ### [C# 1.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-10) 僅提供最基本的功能 , 此版本連泛型都沒有了 , 就更不用說 LinQ 了... - [Class](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/classes) - [Struct](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/struct) - [Interface](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/interfaces/) - [Event](https://docs.microsoft.com/zh-tw/dotnet/csharp/events-overview) - [Properties](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/properties) - 語法糖 , 簡化存取/設定欄位(變數) 的方法的實作動作 - [Delegate](https://docs.microsoft.com/zh-tw/dotnet/csharp/delegates-overview) - 此版的委派只能接收具名方法 - [Expressions](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/expressions) - [Statements](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/statements) - [Attributes](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/attributes/) - Is 運算子 (只能檢查實質型別 e.g. bool isInt = 5 is int; ) ### [C# 2.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-20) 開始支援泛型(Generic) - [Generics(泛型)](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/generics/) - [Partial Classes](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/partial-classes-and-methods#partial-classes) - > 將 class、struct、interface,**分割到兩個以上的來源檔案**。 每一個來源檔案都包含型別或方法定義的一個區段,而當編譯應用程式時,就會將所有區段結合起來。 - [Delegate Operator](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/delegate-operator) - 使用 delegate 關鍵字宣告**匿名方法**. - 匿名方法的用法已經被 C# 3.0 的 Lambda 給取代了. - [Nullable Value Types](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/nullable-value-types) - **實質型別** 可以使用 Nullable Type (可以被指派一個 null) - e.g. `int? i = null;` - [Null Coalescing Operator ??](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/null-coalescing-operator) - e.g. ```C# int? i = null; // 當 i 是 null 時 , // 則繼續查找 ?? 右邊的字符是否為 null , 若否 , 則回傳該值. 因此 j=3 int j = i ?? 3; ``` - [Iterators](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/iterators) - [Covariance & Contravariance](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/covariance-contravariance/) - [Accessor Accessibility](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/restricting-accessor-accessibility) - 此版本開始可以設定 Accessor 的存取權限 - ```C# public class MyClass { private int _myField = 5; public int MyProperty { // get set 皆是 accessor // 設定 set 的存取權限為 protected get { return _myField; } protected set { _myField = value; } } } ``` - [Static Class](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/static-classes-and-static-class-members) - [is Operation](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/type-testing-and-cast#is-operator) - is 運算子檢查運算式的執行階段型別是否與給定型別相容 (可檢查 enum、字符和類別) - ```CSHARP var o = new object(); bool isA = o is A(ClassName); // 回傳 True or False` ``` - C# 7.0 模式比對, 會再進一步擴充 - [as Operation](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/type-testing-and-cast#as-operator) - >as 運算子將運算式的結果明確地轉換成給定參考或可為 Null 的實值型別。 如果無法轉換,則 as 運算子會傳回 null - object as ClassName // 回傳 ClassName 型別的物件 or null - [ThreadPool](https://dotblogs.com.tw/yc421206/2011/01/15/20821) Queues a method for execution. The method executes when a thread pool thread becomes available. - QueueUserWorkItem(WaitCallback) 將方法排入佇列,以等候執行。 可以使用執行緒集區執行緒時,即可執行這個方法。 - QueueUserWorkItem(WaitCallback, Object) 將方法排入佇列,以等候執行,並指定包含這個方法所要使用之資料的物件。 可以使用執行緒集區執行緒時,即可執行這個方法。 - QueueUserWorkItem<TState>(Action<TState>, TState, Boolean) 將 Action<T> 委派指定的方法排入佇列以便執行,並提供將由方法使用的資料。 可以使用執行緒集區執行緒時,即可執行這個方法。 ### [C# 3.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-30) 引用[函數式語言設計的概念](https://zh.wikipedia.org/wiki/%E5%87%BD%E6%95%B0%E5%BC%8F%E7%BC%96%E7%A8%8B) , LINQ 在此版被導入 , [不嫌棄 , 請參考我的筆記](https://github.com/s0920832252/LinQ-Note) - [Property Auto Implemented](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/auto-implemented-properties) - 編譯器自動生成私有變數給屬性使用(精確說法是給 Getter or Setter 使用) - ```C# // 舊的寫法 private string _property = string.Empty; public string Property { get { return _property; } set { _property = value; } } // 新的寫法 public string Property { get; set; } ``` - [Anonymous Types](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types) - 使用 new 關鍵字 , 可以簡單建立**匿名型別**. 此型別的物件不需要事先明確定義類別名稱 , 類別名稱是由編譯器產生 , 並且每個屬性的型別會由編譯器推斷. - ```C# // 建立匿名型別物件 , 其具有屬性為 Name , Age var value = new { Name = "王曉明", Age = 5 }; // 建立匿名型別陣列 , 其每一個成員(物件)都具有屬性為 Name , Age var values = new[] { new { Name = "周論發", Age = 15 }, new { Name = "小龍女", Age = 65 } }; ``` - [Query Expression](https://docs.microsoft.com/zh-tw/dotnet/csharp/linq/query-expression-basics) - LinQ 的 Query 語法 - [Expression Trees](https://docs.microsoft.com/zh-tw/dotnet/csharp/expression-trees) - 解析 LinQ 方法的東西. 我不會XD - [Object And Collection Initializers](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/object-and-collection-initializers) - ```C# // 物件初始舊的寫法 Person p = new Person(); p.Name = "City"; // 新的寫法 Person p = new Person{ Name = "City" } ``` - ```C# // 集合初始舊的寫法 List<int> list = new List<int>(); list.Add(1); list.Add(2); // 新的寫法 var list = new List<int>() { 1 , 2 }; ``` - [Var 變數](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/var) - 變數型別推斷 - `var s = "小名" // 自動推斷變數 s 為 string` - [Lambda](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/statements-expressions-operators/lambda-expressions) - [Extension Method](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/extension-methods) - 一個靜態類別內的方法 , 若此方法的第一個參數使用 this 關鍵字作為前綴詞時 , 該方法將會被視為是 this 關鍵字後所指的類別之中的方法. - [Partial Method](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/partial-method) - 允許在 Partial Class 內僅定義方法簽章 , 然後在另外的相同名稱的 Partial Class 內實作. - 類似於 C++ 的 .h 檔案以及 .cpp 檔案的感覺. ### [C# 4.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-40) 主要 Feature 為 Dynamic 關鍵字的導入. - [Dynamic Type](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/reference-types#the-dynamic-type) - [Tuple](https://dotblogs.com.tw/hatelove/2013/12/12/tuple-introduction) - 此版本的元祖名稱只能是 Item1 , Item2 ... 非常之不好用. 需要等到 C#7.0 才解禁. - [Method Support Named Arguments](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#named-arguments) - **具名引數**僅需指名方法參數名稱 , 則可無視方法簽章的參數順序 , 傳入參數 - ```C# public static void CityMethod(string name, int age) { Console.WriteLine($"{name}的年紀是 {age} 歲"); } public static void Main(string[] args) { // 即使傳入參數位置順序相反 , 但只要有指名參數名稱 , 仍可正確傳入參數 CityMethod(age: 29, name: "楊過"); Console.ReadKey(); } ``` - [Method Support Optional Arguments](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/named-and-optional-arguments#optional-arguments) - 選擇性引數 : 允許參數設定預設值 - ```C# public static void CityMethod(string name, int age = 15) { Console.WriteLine($"{name}的年紀是 {age} 歲"); } public static void Main(string[] args) { // 若未傳入參數 age , 則使用預設的 15 CityMethod("楊過"); Console.ReadKey(); } ``` - [Support COM Element ](https://docs.microsoft.com/zh-tw/dotnet/framework/interop/type-equivalence-and-embedded-interop-types) - 支援與 COM 組件互動 , e.g. 打開 Word 檔案 - [Covariance and Contravariance of Generics](https://docs.microsoft.com/zh-tw/dotnet/standard/generics/covariance-and-contravariance) - 在泛型介面或類別可以加上 in & out 修饰字 - [Task](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task?view=net-6.0) - [Task.Start](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.start?view=net-6.0) ```C# var task = new Task(Action); task.Start(); // 開始執行 Action ``` - [Task.Wait](https://docs.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.task.wait?view=net-6.0) ```C# var task = new Task(Action); task.Start(); task.Wait(); // Block UI 等待 task 完成 ``` - [Task.TaskFactory](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory?view=net-6.0) ```C# // 回傳 "不用 call Start , 就會執行 Action" 的 task var task = Task.Factory.StartNew(Action); ``` - [Task.ContinueWith](https://docs.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.task.continuewith?view=net-6.0) - 在能用 await 的地方盡量用await , 別再使用 Task.ContinueWith() <--- 過時了 ```C# private void Button1_Click(object sender, EventArgs e) { var backgroundScheduler = TaskScheduler.Default; var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(delegate { DoBackgroundComputation(); }, backgroundScheduler). ContinueWith(delegate { UpdateUI(); }, uiScheduler). ContinueWith(delegate { DoAnotherBackgroundComputation(); }, backgroundScheduler). ContinueWith(delegate { UpdateUIAgain(); }, uiScheduler); } ``` - [Task<Result>](https://docs.microsoft.com/en-us/dotnet/api/system.threading.tasks.task-1?view=net-6.0) 使用上 , 原則上跟 Task 一樣 , 只是多了回傳值 - Task<TResult>.Result ```C# var task = Task.Factory.StartNew(()=>"result data"); var result = task.Result; ``` ### [C# 5.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-50) 參考[基於 Task 的非同步設計的概念](https://docs.microsoft.com/zh-tw/dotnet/standard/asynchronous-programming-patterns/) , async 和 await 這兩個關鍵字被導入. 印象中 C# 是最早使用這兩個關鍵字的語言. - [Task.Run()](https://docs.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.task.run?view=net-6.0) - [async & await](https://docs.microsoft.com/zh-tw/dotnet/csharp/async) - async 關鍵字 - 保證此方法的回傳值必定是 Task 型態 - 保證此方法**必定會使用 await 關鍵字** - 使用 async 不代表程式必定為非同步執行或是新 thread 的產生 , 需要看方法內部的實作. 例如 : 該方法可能使用 Task.FromResult(TResult result) 將結果轉成 Task. 則此實作為同步執行 , 所以也不會產生新 Thread. - await 關鍵字 - await 代表程式會在此處紀錄 , 並承諾當非同步的結果完成時 , 會從記錄點開始繼續執行. - 再執行到 await 處時 , 當前 Thread 資源可能會被釋放 , 但通常是會跳回母涵式處繼續往下執行. 這由 OS 決定 - 當非同步結果完成時 , 不一定由原本的 Thread 繼續往下執行 , 由 OS 依據當時 Thread Pool 內的資源決定. - await 並不一定會新增一個 thread . 此依據被呼叫方法內部的實作是否有新增一個 thread 去執行. - [Caller Information](https://docs.microsoft.com/zh-tw/dotnet/csharp/programming-guide/concepts/caller-information) - | 屬性 | 描述 | 類型 | | --------------------------------- | ------------------------------------------------------------- |:------- | | CallerFilePathAttribute | 包含呼叫端的原始程式檔完整路徑。 完整路徑是在編譯時期的路徑。 | String | | CallerLineNumberAttribute | 原始程式檔中呼叫方法的行號。 | Integer | | CallerMemberNameAttribute | 呼叫端的方法名稱或屬性名稱。 | String | | CallerArgumentExpressionAttribute | 引數運算式的字串表示。 | String | - >使用 Caller Info 屬性,您就可以取得有關方法之呼叫端的資訊。 您可以取得原始程式碼的**檔案路徑**、**原始程式碼中的行號**,以及**呼叫端的成員名稱** - ```C# public abstract class NotifyPropertyBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; protected void OnPropertyChanged(string propertyName)=> PropertyChanged?.Invoke( this, new PropertyChangedEventArgs(propertyName) ); // 透過 CallerMemberName 去取得呼叫端的名稱 (呼叫端可以是方法或者是屬性) protected void SetProperty<T>(ref T storage, T value, [CallerMemberName] string propertyName = null) { if (EqualityComparer<T>.Default.Equals(storage, value)) return; storage = value; OnPropertyChanged(propertyName); } } ``` ### [C# 6.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-60) 主要是語法使用上的改進. - [Read-only Automatically Implemented Properties](https://www.huanlintalk.com/2018/02/c-readonly-auto-property-from-beginning.html) - 唯讀屬性 - ```C# // C# 2.0 private string _property = string.Empty; public string Property { get { return _property; } private set { _property = value; } } // C# 3.0 public string Property { get;private set; } // C# 6.0 public string Property { get; } ``` - [Auto Property Initializers](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#auto-property-initializers) - ```C# public string Property { get; set; } = "自動實作的屬性可以設定初始值了" ``` - [Expression Bodied Function/Members](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#expression-bodied-function-members) - 方法以及唯獨屬性回傳值為單一運算式 , 可將回傳結果放在 **=>** 符號右側表示 - ```C# public string GetFullName(string name) => $"我的名字是 {name}"; // 等同唯讀屬性 , 因為沒有設定 set. public string FullName => GetFullName("王大力"); ``` - [Using Static](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#using-static) - 匯入類別所擁有的 static method - ```C# // 匯入 Math 類別所有的靜態方法 , 實體方法會忽略之 using static System.Math; ``` - [Null Conditional Operators](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#null-conditional-operators) - 語法糖 , 檢查成員是否為 null , 若為 null 則回傳 null - ```C# class Person { public string Name { get; set; } = "小王"; } public static void Main(string[] args) { List<Person> people = null; people = new List<Person>() { null, new Person() }; // 由左到右 , 第一個 ? 檢查 people 是否為 null , 若為 null 則回傳 null // 第二個 ? 檢查 people[1] 是否為 null , 若為 null 則回傳 null var firstPersonName = people?[1]?.Name; Console.WriteLine(firstPersonName); Console.ReadKey(); } ``` - [String Interpolation](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#string-interpolation) - 使用 $ 語法糖 取代 String.Format() 方法 - ```C# public string LastName { get; set; } = "小名"; public string FirstName { get; set; } = "王"; public string FullName => $"{FirstName} {LastName}"; ``` - [Exception Filters](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#exception-filters) - when () 內放入回傳值為 bool 的內容物 , 幫助塞選是否進入此例外類型. (不用再例外區間內加入邏輯判斷) - try 區塊內的東西 , 無法放入 when () 內. - ```C# abstract class Base { } class Circle : Base { } class Rectangle : Base { } public static void Main(string[] args) { var graph = new Rectangle(); try { throw new Exception(); } catch (Exception e) when (e.Message != null && graph is Circle ) { Console.WriteLine($"Circle Exception"); } catch (Exception e) when (e.Message != null && graph is Rectangle) { Console.WriteLine($"Rectangle Exception"); } Console.ReadKey(); } ``` - [nameof Expression](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#the-nameof-expression) - nameof 運算式將變數、屬性或成員欄位的名稱甚至是方法轉成字串回傳 - 使用快捷鍵 Ctrl+R+R 更改名稱時 , 會連 nameof()內的項目名稱一起更改 - ```C# public string Property { get; set; } public int _field; public void Method() { } public static void Main(string[] args) { Console.WriteLine(nameof(Property)); Console.WriteLine(nameof(_field)); Console.WriteLine(nameof(Method)); } ``` - 解除在 Catch 和 Finally 區塊中使用 Await 的限制 - 在 catch / finally 區塊可以開始使用 await 語法糖 - ```C# try{ // doSomeThing } catch (Exception) { await Task.Delay(1000); } finally { await Task.Delay(1000); } ``` - [Initialize Associative Collections Using Indexers](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#initialize-associative-collections-using-indexers) - 我不知道這可以幹嘛 = =" , 感覺也沒有比較好寫呀XDDDD - ```C# // 舊的寫法 Dictionary<int, string> webErrors = new Dictionary<int, string> { { 404, "Page not Found"}, { 302, "Page moved, but left a forwarding address."}, // 404 索引子重複 , 會跳 Exception { 404, "The web server can't come out to play today."} }; // 新的寫法 Dictionary<int, string> webErrors = new Dictionary<int, string> { [404] = "Page not Found", [302] = "Page moved, but left a forwarding address.", // 發現索引子已存在 , 會蓋過原本索引子的值 , 不會跳 Exception [404] = "The web server can't come out to play today." }; ``` - [Extension Add Methods In Collection Initializers](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#extension-add-methods-in-collection-initializers) - 自定義類別也可以透過實作 Add 方法 , 而使用集合初始設定式 - ```C# public class Person { public string Name { get; set; } public int Age { get; set; } } public static void Add(this ICollection<Person> source, string name, int age) { source.Add(new Person { Name = name, Age = age }); } public static void Main(string[] args) { // new Person 的動作 , 交給 Add 方法做掉了. // 自定義類別的集合初始設定式 var people = new List<Person>() { {"小王",15 }, {"大王",64 }, }; Console.ReadKey(); } ``` - [Improved Overload Resolution](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-6#improved-overload-resolution) - 舊版的編譯器無法正確分辨 Task.Run(Action) 與 Task.Run(Func<Task>()) - 需要寫成Task.Run(() => DoThings()); ### [C# 7.0 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-version-history#c-version-70) 主要是語法使用上的改進. 改進幅度比 6.0 更大. 新的語法與語法之間可以交互使用. - [out Variables](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#out-variables) - out 的用途是使用傳址呼叫將某變數丟入方法後 , 等待其修改. 是一種得到回傳值的方式. - 7.0 後 out 變數出現後 , 我們能夠直接宣告一個變數在它要傳入的地方. 也就是說 , 變數不必在丟入方法前將變數型態先宣告好. - ```C# public static void SetValue(out int v) => v = 3; static void Main() { // 舊的寫法 int v; SetValue(out v); // 新的寫法 , 支援變數型別推斷 , 不想使用 , var 可以改 int SetValue(out var v2); } ``` - [Tuples](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#tuples) - >C# 7.0 加入了 Tuple 的語言支援,讓 Tuple 欄位的語意名稱能使用全新且更具效率的 Tuple 型別。 - ```C# public static (string name, int age, bool successs) GetData( (string name, int age) data ) { var (name, age) = data; // deconstruct data into two variable name & age return (name, age, true); } static void Main() { var valueTuple = GetData(("王大明", 15)); } ``` - [Discards](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#discards) - >通常在您**解構 Tuple 或以 out 參數呼叫方法**時,會強制您要定義變數,而您沒有使用該值的打算。 C# 新增了對捨棄的支援,來應付這種狀況。 捨棄是僅限寫入的變數,其名稱為 _ (底線字元);您可將所有想要捨棄的值指派到單一變數。 捨棄類似於未經指派的變數;和指派陳述式一樣,都不能用於程式碼中。 - ```C# public static (string name, int age, bool successs) GetData( (string name, int age) data ) { var (name, age) = data; return (name, age, true); } static void Main() { // just record success by variable isSuccess var (_, _, isSuccess) = GetData(("王大明", 15)); Console.WriteLine($"get Data success is {isSuccess}"); } ``` - [Pattern Matching](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#pattern-matching) - >模式比對支援 is 運算式和 switch 運算式。 每個都讓您能檢查物件和其屬性,以判斷該物件是否符合搜尋的模式 , 並在一個指令後得到結果。 同時亦**可以使用 when 關鍵字**在 switch 運算式上 - ```C# if (input is int count) sum += count; ``` - ```C# public class MyClass { public string Name { get; set; } } static string Show(MyClass instance) { switch (instance) { case null: throw new NullReferenceException(); case MyClass c1 when c1.Name != null: return $"MyClass 's Name is {c1.Name}"; case MyClass _: return "MyClass 's Name is Empty"; default: throw new InvalidOperationException("Unrecognized type"); } } ``` - [ref locals and returns](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#ref-locals-and-returns) - 想成是 C 的 Point . - ```C# static void Before() { // unsafe 需要去 專案 => 專案屬性 => 建置 => 不安全程式碼打勾 int number = 100; unsafe { int* p = &number; *p = 999; } } static void After() { int number = 100; ref int p = ref number; p = 999; } ``` - [Local Functions](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#local-functions) - 支援在方法、屬性或是建構式中 , 宣告以及實作別的方法. - [local functions](https://dotblogs.com.tw/billchung/2020/01/02/024959) 1. 依照方法內部是否有使用到物件成員 , 來決定 local 方法會被編譯成實體方法還是靜態方法 (此物件成員若是透過local方法的參數傳入 , 則還是會編譯成靜態方法) 2. 若local方法內部使用到暫存變數(未透過local方法傳入) , 則 local 方法會自動長一個 struct 參數(將使用到的暫存變數裝入). - ```C# static string OutputDayString(string freq = "weekend") { var dayStr = IsEveryDay(freq) ?? IsWeekday(freq) ?? IsWeekend(freq) ?? "has error"; return dayStr; // local functions string IsEveryDay(string f) => f == "everyDay" ? "is everyDay ~" : null; string IsWeekday(string f) => f == "weekday" ? "is weekday ~" : null; string IsWeekend(string f) => f == "weekend" ? "is weekend ~" : null; } static void Main() { var dayString = OutputDayString(); Console.WriteLine(dayString); } ``` - [New Expression Bodied Members](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#more-expression-bodied-members) - 在 C# 7.0 , 可以在 「**建構子**」、「**解構子**」,以及「**屬性」上的 get 和 set 存取子**使用 => 運算子 - ```C# public class MyClass { private string _name = string.Empty; public MyClass(string name) => this._name = name; ~MyClass() => Console.WriteLine("do some thing"); public string Name { get => _name; set => _name = value; } } ``` - [Throw Expression](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/throw#the-throw-expression) - >throw 原本只能做為陳述式使用 , 但現在也可以當作運算式使用. - ```C# void ThrowException() => throw new Exception(); //能使用 => 運算子 // 能使用三元運算子 string result = value != string.Empty ? "correct" : throw new Exception("value is empty"); // 能使用 null 聯合運算子 return value ?? throw new Exception("value is null"); ``` - [ValueTask](https://docs.microsoft.com/zh-tw/dotnet/api/system.threading.tasks.valuetask-1?view=netcore-3.1&viewFallbackFrom=netframework-4.8) - >從非同步方法傳回 Task 物件可能會造成效能瓶頸。 Task 是參考型別,因此使用它表示要配置物件。 當使用 async 修飾詞宣告的方法傳回結果時,或是以同步方式完成,額外配置記憶體給物件可能會效能關鍵的程式中變成一項重要的時間成本。 例如 : 如果這些配置發生在緊密迴圈中,它可能會變得成本很高。 - >async 方法傳回型別除了 Task、Task<T> 和 void 外 , 新增一個 ValueTask<T>。 - >需要再 NuGet 安裝 System.Threading.Tasks.Extensions 才能使用該 ValueTask<TResult> - [[C#]7.0為async新增的ValueTask的作用](https://dotblogs.com.tw/kinanson/2018/04/25/091013) - [Numeric Literal Syntax Improvements](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7#numeric-literal-syntax-improvements) - 數字分隔符號 **_** 是幫助我們閱讀數字使用 , 其在程式執行時 , 會被忽略. - ```C# public const int Eight = 0b00001_000; public const int Sixteen = 0x1_0; public const double AvogadroConstant = 6.022_140_857_747_474e23; public const long BillionsAndBillions = 100_000_000_000; ``` ### [C# 7.1 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-1) - [Async Main](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-1#async-main) - Main 方法可以使用 async 修飾. - [default literal expressions](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-1#default-literal-expressions) - 除了原本的 [default 運算子](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/default)功能外 , 強化 default 的功能 - 預設常值(型別推斷後回傳該型別之預設值) - ```C# public static void MyMethod<TSource>() { // old var result = default(TSource); // new TSource result2 = default; } ``` - [Inferred Tuple Element Names](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-1#inferred-tuple-element-names) - 使用變數名稱自動推斷 tuple 項目名稱 - ```C# // old 寫法 string s = ""; int i = 5; var t = (s: s, i: i); // new 寫法 // 之後可使用 t.s or t.i 去使用 tuple 項目 , // C#7.1之前 , 若未替 tuple 項目命名 , 則只能使用 .item1 , .item2 ... string s = ""; int i = 5; var t = (s, i); ``` - [Pattern Matching On Generic Type Parameters](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-1#pattern-matching-on-generic-type-parameters) - **泛型變數**可以使用模式比對 - ```C# public class MyClass { public string Name { get; set; } } public class MyClass2 { public string Name { get; set; } } static string Show<T>(T instance) { switch (instance) { case MyClass c1 when c1.Name != null: return $"MyClass 's Name is {c1.Name}"; case MyClass _: return "MyClass 's Name is Empty"; case MyClass2 c2 when c2.Name != null: return $"MyClass2 's Name is {c2.Name}"; case MyClass2 _: return "MyClass2 's Name is Empty"; default: return "甚麼玩意"; } } ``` ### [C# 7.2 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2) - [Safe Efficient Code Enhancements](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2#safe-efficient-code-enhancements) - ref readonly 區域變數 - readonly structure - ref structure - [Non Trailing Named Arguments](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2#non-trailing-named-arguments) - 具名引數本來只能放在位置引數後方 , 現在只要位置正確 , 也可以放在前方 - ```C# // 通常會跟 Optional 參數混用. static void Main(string[] args) { Test("小名", phone: "0989782266"); // 不用把所有的 Optional 參數都打完. Console.ReadKey(); } public static void Test(string name, int age = 30, string phone = null, [CallerMemberName] string caller = null) { Console.WriteLine($"{name} {age} {phone} {caller}"); } ``` - [Leading Underscores In Numeric Literals](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2#leading-underscores-in-numeric-literals) - 數值常數的任何位置都可以使用 _ , 即使是 0b 後也可以加上 _ . - ```C# int binaryValue = 0b_0101_0101; ``` - [Private Protected Access Modifier](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2#private-protected-access-modifier) - 新增 private protected 存取修飾詞 - [Conditional Ref Expressions](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-2#conditional-ref-expressions) - ref 條件運算式 - `ref var r = ref (arr != null ? ref arr[0] : ref otherArr[0]); ` ### [C# 7.3 版](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-7-3) - ref 變數可重新指派 - ```C# var obj = new object(); var obj2 = new object(); ref var i = ref obj; i = ref obj2; // i 重新指派給 obj2 ``` - [Equality And Tuples](https://docs.microsoft.com/zh-tw/dotnet/csharp/tuples#equality-and-tuples) - Tuple 可使用 == 或是 != 運算子進行相等或不等的運算判斷 - [泛型約束](https://docs.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/where-generic-type-constraint) - >允許 Enum、Delegate 和 MulticastDelegate 作為基底類別條件約束 - 將 attributes 附加至自動實作屬性的支援欄位 ### [C# 8.0](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-8) #### [Index (readonly struct )](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#index-from-end-operator-) - ^ 運算子,指定索引相對於序列結尾,也就是倒著算 - 重要屬性 - 執行個體屬性 - IsFormEnd - Value - 靜態屬性 - Start -- 0 - End -- ^0 - 重要方法 - 執行個體方法 - GetOffset - 靜態方法 - FromEnd - FromStart ```CSHARP List<string> lines = ["one", "two", "three", "four"]; string prelast = lines[^2]; Console.WriteLine(prelast); // output: three ``` #### [Range (readonly struct )](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) - .. 運算子,它會指定範圍的開頭與結尾作為其運算子 - Range form x to y (x..y) 表示 x ≦ 序列<y ```csharp int[] numbers = [0, 10, 20, 30, 40, 50]; int margin = 1; int[] inner = numbers[margin..^margin]; Display(inner); // output: 10 20 30 40 ``` #### [靜態區域函式](https://learn.microsoft.com/zh-tw/dotnet/csharp/programming-guide/classes-and-structs/local-functions#local-function-syntax) - 靜態區域函式不能直接使用容器的區域變數與執行個體欄位 - 宣告為 static 的區域函式明確表明「這個函式不依賴外部狀態」, 意即絕對不會有外部變數, 區域變數若使用外部變數, 編譯器會偷偷建立 capture class 儲存變數, 會造成 GC 的壓力 ```csharp void Foo() { int counter = 0; static void Increment() => Console.WriteLine("No capture allowed"); Increment(); // 若 Increment 使用 counter 會編譯錯誤 } ``` #### [模式比對增強功能](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/operators/patterns#property-pattern) - switch expression - 屬性模式 - 新增巢狀比對模式 - {} 符號用來比對屬姓名 - Tuple 模式 - () 符號用來比對位置 ( tuple 是匿名 struct 並內建解構子 ) - 位置模式 - () 符號用來比對位置(透過 Deconstruct) ##### 變數捕捉(Property Pattern + 宣告變數) ```csharp record Person(string Name,int Age); // 比對時, 順便宣告變數 var name = p.Name; var age = p.Age; if (p is { Name: var name, Age: var age }){Console.WriteLine($"{name} 是 {age} 歲");} ``` ##### 使用 {} 去做屬性比對 - 支援巢狀比對 ```csharp record Address(string City); record Person(string Name,int Age, Address Address); var p = new Person("Mary",30 , new Address("Taipei")); // 檢查 p 不是 null 並且 p.Name == Mary 並且 p.Age == 30。 if (p is { Name : "Mary" , Age : 30 }){Console.WriteLine("30歲的馬利");} // 檢查 p 不是 null 並且 p.Address 不是 null 並且 p.Address.City == "Taipei"。 if (p is { Address: { City: "Taipei" } }){Console.WriteLine("這人住在台北");} ``` ##### 對 tuple 去位置比對 ```csharp var t = (X: 3, Y: 4); if (t is (3, 4)) Console.WriteLine("匹配成功"); ``` ##### 使用位置去比對(需要有解構子) ```csharp record Point(int X, int Y); // record 是匿名類別, C#9 才有 var pt = new Point(3, 4); if (pt is (3, 4)) Console.WriteLine("座標是 (3,4)"); ``` ##### switch expression - 可搭配模式比對 位置模式/tuple模式/屬性模式 ```csharp enum Morra{ 剪刀,石頭,布 } string GetResult(Morra first, Morra second) => (first, second) switch { (Morra.剪刀, Morra.布) => "勝", (Morra.布, Morra.石頭) => "勝", (Morra.石頭, Morra.剪刀) => "勝", (var x, var y) when x == y => "平", _ => "敗" }; //=== object obj = 123; string typeName = obj switch { int n => $"整數:{n}", string s => $"字串:{s}", null => "空值", _ => "其他型別" }; //=== record Customer(string Name, string? Email); record Order(string Product, int Quantity, Customer? Customer); string GetStatus(Order? order) => order switch { null => "這物件是 null", // 巢狀屬性比對 { Product: "Phone", Customer: { Name: "Mary" } } => "Mary 的手機訂單", { Customer: null } => "⚠️ 未指定客戶", // 巢狀屬性 + null 檢查 { Customer: { Email: null } } => "⚠️ 客戶未填 Email", // 巢狀屬性 + 內容比對 { Product: "Phone" } => "一般手機訂單", // 屬性模式比對 {} => "這物件不是 null", // 不檢查任何內容, 只檢查非 null _ => "未知商品" // 所有都不符合時, 相當於原本的 default : }; ``` #### 可為 null 的參考型別 - 使用 ? 符號來定義 "可為 null " 的參考型別變數 - 啟用 nullable reference types - 前置處理器指示詞 --– 針對單一檔案 - Project file —-- 針對整個 project (預設值) - 編譯器的處理是警告而非錯誤:CS86xx -> IDE 會報警告, 但dll 仍會產生 ```csharp string notNull = "Hello"; // 啟用警告後, reference 型別變數是不能指派 null 的 string? nullable = default; // 使用 ? 後綴代表此變數可以指派 null notNull = nullable!; // ! 是「null-forgiving operator, 告訴編譯器:這不是 null, 別警告 ``` ```csharp #nullable enable // 針對單一檔案 namespace NullableSample001 { class Program{ static void Main(string[] args){ string s1 = "ABC"; string s2? = null; } } } ``` #### [Null-coalescing assignment ??= ](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/assignment-operator#null-coalescing-assignment) ```csharp string? name = null; name ??= "Guest"; // 如果 name 是 null,就指定 "Guest" ``` #### [Default interface members](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/interface#default-interface-members) - 修改介面時不會影響現有已實作此介面的衍生型別 e.g. 已經 Release 很久的類別庫 - 介面可以實作了 = = , 可使用以下功能 - reabstraction (就是子型別使用 abstract 重新變成抽象罷了) - override - 存取修飾 - 靜態方法 - sealed - static Constructor ```csharp interface IBase { // 定義介面實作成員 voidRequest() => Console.WriteLine($"Call{nameof(Request)}in{nameof(IBase)}"); } ``` #### using scope - 語法糖 , 編譯後 Dispose 將會附加到方法末端. ```csharp void Save(IEnumerable<string> lines){ using var file = new System.IO.StreamWriter("file1.txt"){ // do some thing } } void Save(IEnumerable<string> lines){ using var file = new System.IO.StreamWriter("file1.txt"); // do some thing , 要離開 Save 前會執行 Dispose() } ``` #### [非同步迭代](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/statements/iteration-statements#await-foreach) 可以 await 的 foreach - IAsyncEnumerable<T> - IAsyncEnumerator<T> - IAsyncDisposiable ```csharp void 非同步迭代(){ await foreach (var item in GenerateSequenceAsync()) { Console.WriteLine(item); } } async IAsyncEnumerable<int> GenerateSequenceAsync() { for (int i = 1; i <= 5; i++) { await Task.Delay(500); // 模擬非同步操作 yield return i; // 逐一產出 } }> ``` ### [C# 9.0](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-9) #### [init](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/init) init 是一種新的為了不變性而設計的 setter,只能在「物件初始化階段」或「with 表達式」中設定,但不能在物件建立之後修改。 ```csharp class Person_InitExampleAutoProperty { public int YearOfBirth { get; init; } } var p = new Person { YearOfBirth = 1 }; // 第一次設定屬性 p = 2; // 編譯器報錯 var p2 = p1 with { YearOfBirth = 2000 }; // ✅ 可用 init 修改副本 ``` #### [Record](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/record) 聽說是從 kotlin 那邊抄來的 - 快速建立參考型別的語法糖, 主要用來支援不可變型別的設計 - record 的基底型別只能是 object class 或是其他的 record type - Record 是匿名類別, 它幫你做了 - 建立需要的屬性與欄位 - 產生建構子 - 實作 IEquatable<T> 介面,Equals(T o)方法實作內容會比較所有欄位值 - override void Equals(Object other) 方法,內容則為呼叫 this.Equals(T o) - overload == 和 != 運算子,讓這兩個運算子的行為和 Equals 方法一致 - override int GetHashCode() 方法 - 加入 Deconstruct 方法 --> 方便用來巢狀模式比對 - 產生唯讀屬性 protected virtual Type EqualityContract,用來 return typeof(T), 通常用在 Equals 與 GetHashCode 的比較邏輯 - 產生 protected virtual bool PrintMembers(StringBuilder builder) 方法,這 個方法的內容是串聯欄位與屬性值字串, 基本上是為了給 ToString() 方法呼叫 - override ToString() 方法 - 加入 <Clone>$() 方法,此方法無法直接呼叫,透過 with 敘述使用. 這個方法的內部會呼叫 new T(this) 以回傳一個新的執行個體 ```csharp // 有兩種方式建立 record public record MyRecord //自己宣告型別, 自動建立無參數建構子, 這種方式不會幫你產生 Deconstruct { public required string Name { get; init; } // 通常只有你要改變存取設定時才會用這種做法 public required string Age { get; init; } }; //用語法糖宣告 record 型別, 自動產生Name 和 Age 屬性, 以及無參數和兩個參數的建構子 public record MyRecord(string Name, int Age); var r1 = new MyRecord("Bill", 32); // 產生物件 var r2 = r1 with { Name = "Tom"};//建立新物件,內容跟r1一樣,修改其Name 成 Tom,再指派給變數 r2 Console.WriteLine(object.ReferenceEquals(r1, r2)); // 輸出 false ``` ```csharp record Person(string Name, int Age); // 此處等下於下面 public class Person { public string Name { get; init; } public int Age { get; init; } public Person(string Name, int Age) { this.Name = Name; this.Age = Age; } public virtual bool Equals(Person? other) => other is not null && EqualityContract == other.EqualityContract && Name == other.Name && Age == other.Age; protected virtual Type EqualityContract => typeof(Person); public override int GetHashCode() => HashCode.Combine(EqualityContract, Name, Age); public override string ToString() => $"Person {{ Name = {Name}, Age = {Age} }}"; // C# 10 會多一個 sealed //public sealed override string ToString()=>$"Person {{Name={Name},Age = {Age}}}" } ``` #### [Top-level statements - programs without Main methods](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/program-structure/top-level-statements) - 簡化 namespace & class 宣告, 一個應用程式裡只能有一個,而且必須在可執行檔專案 ```csharp //this is Program.cs Console.WriteLine("Hello World!"); ``` #### 模式比對增強功能 : 關聯式模式和邏輯模式 - 關聯式模式 - 模式比對中可以使用關係運算子,比較基準必須為常值或常數. e.g. > , >= , < , <= , - 邏輯模式 - 模式比對中可以使用 and, or, not 邏輯運算,並且可以用括弧組合順序 ```csharp static string GetCalendarSeason(DateTime date) => date.Month switch { >= 3 and < 6 => "spring", >= 6 and < 9 => "summer", >= 9 and < 12 => "autumn", 12 or (>= 1 and < 3) => "winter", _ => throw new Exception(), }; if (input is not null) // 邏輯模式 的 not { // ... } ``` #### 目標型別 new 運算式 --- new() 當類型為已知時,不需要建構函式的類型規格 ```csharp // new() { 1, 2, 3} <-- new List<int>() { 1, 2, 3} // new() {...} <-- new Dictionary<string, List<int>>(){ ... } Dictionary<string, List<int>> field = new() { { "item1", new() { 1, 2, 3 } }}; // 可以從使用推斷出類型時省略類型 XmlReader.Create(reader, new() { IgnoreWhitespace = true }); (int a, int b) t = new(); // same as (0, 0) ``` #### 靜態匿名函式 在 lambda 表達式和匿名方法中允許使用「靜態」修飾符,這樣可以避免從包含的範圍中捕捉局部變數或實例狀 ```csharp var f = static (int x, int y) => x + y; //不用產生 closure 類別存變數,所以不用配置記憶體 Console.WriteLine(f(3, 4)); // 輸出:7 // 比較 int x = 10, y =20; var f2 = () => x + y; // 編譯器會偷偷產生一個類別去存 x 和 y (捕捉外部變數) Console.WriteLine(f2()); // 輸出:30 ``` #### Target-Typed Conditional Expression 條件運算子 (?:)」更聰明,可根據目標型別(上下文期望的型別)自動推斷分支結果型別, 以前是 : 兩邊的型別要能讓編譯器看到是同一個型別 ```csharp // 現在 OK(target type 是 object), 以前 ok 共同型別為 object object obj = condition ? 123 : "ABC"; // 現在 OK(target type 是 string), 以前不 ok :沒有共同型別 string text = condition ? "Hi" : null; ``` #### Covariant Return 當你覆寫一個方法時,可以使用「比原本更具體的型別」作為回傳值 ```csharp class Base { public virtual Animal CreateAnimal() => new Animal(); } class Derived : Base { // C# 9 以前不能這樣寫, 即使 Dog 繼承 Animal public override Dog CreateAnimal() => new Dog(); // 以前只能這樣寫, 外面再自己轉型 // public override Animal CreateAnimal() => new Dog(); } ``` #### Extension GetEnumerator support for foreach loops. foreach 可以使用「擴充方法 (extension method)」提供的 GetEnumerator(), 而不再限制只能用「成員方法 (instance method)」 ```csharp struct MyRange { public int Start; public int End; } // 以前必須繼承 IEnumerable // struct MyRange : IEnumerable<int> { ... } // 現在補一個擴充方法就好 static class RangeExtensions { public static IEnumerator<int> GetEnumerator(this MyRange range) { for (int i = range.Start; i <= range.End; i++) yield return i; } } foreach (var i in new MyRange { Start = 1, End = 3 }) Console.WriteLine(i); // 印出 1,2,3 > ``` #### Lambda discard parameters 在 Lambda(匿名函式)中明確標示「不使用的參數」,而且不需要給這些參數命名 ```csharp var result = items.Select((_, i) => i); // 只用 index,不用 value button.Click += (_, _) => Console.WriteLine("Button clicked!"); //兩個參數都不用 ``` #### Attributes on local functions 可以在區域函式(local function)上標註屬性(attribute),就像在一般方法或類別上使用 [Attribute] 一樣 ```csharp void MyMethod() { [Obsolete("Use another method instead")] // C#9以前不行在區域方法上掛屬性 void SayHello() { Console.WriteLine("Hello!"); } SayHello(); // ⚠️ 編譯器警告:此方法已標記為過時 } ``` ### [C# 10.0](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-10) #### Record Struct - 快速建立實值型別的語法糖, 主要用來支援不可變型別的設計. 結合了 record(具備不可變、結構相等的語意) 與 struct(值型別) 兩者的特性。簡單說:👉 它是一個值型別(在堆疊上配置),但有 record 的行為語意 - 可搭配 readonly 使用 | 類型 | class | struct | record | record struct | | ---------- | -------- | -------- | -------------------- | -------------------- | | 型別分類 | 參考型別 | 值型別 | 參考型別 | 值型別 | | 預設相等性 | 參考相等 | 結構相等 | 結構相等(根據內容) | 結構相等(根據內容) | | 可繼承性 | 可 | 否 | 可 | 否 | | 複製行為 | 參考 | 值 | 具值語意的複製 | 具值語意的複製 | | 預設不可變 | 可變 | 可變 | 不可變(init-only) | 不可變(init-only) | ##### 為什麼 C# 要有 record struct ? ![image](https://hackmd.io/_uploads/BypTRlZJ-l.png) ```csharp public record struct Point(int X, int Y); // 以上等價於下面 public struct Point : IEquatable<Point> { public int X { get; init; } public int Y { get; init; } public Point(int x, int y) => (X, Y) = (x, y); public bool Equals(Point other) => X == other.X && Y == other.Y; public override int GetHashCode() => HashCode.Combine(X, Y); public override string ToString() => $"Point {{ X = {X}, Y = {Y} }}"; public static bool operator ==(Point left, Point right) => left.Equals(right); public static bool operator !=(Point left, Point right) => !left.Equals(right); // 支援 with 複製 public Point With(int? X = null, int? Y = null) => new Point(X ?? this.X, Y ?? this.Y); } ``` #### [Struct initialization and default values](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/builtin-types/struct#struct-initialization-and-default-values) C# 10 以前 * struct 的欄位/屬性在建構子中一定要全部指定 * 無法在屬性宣告時給預設值 C# 10 以後, 放寬了限制 ```csharp public struct Point { public int X { get; set; } = 1; public int Y { get; set; } = 2; } public struct Rectangle { public int Width { get; set; } = 100; // ❌ C# 10 以前不能這樣指定 public int Height { get; set; } = 50; // ❌ public Rectangle() // ✅ 可空建構子 { // 不用手動給 Width & Height 值 } } ``` #### [Compilation of interpolated strings](DefaultInterpolatedStringHandler) 可以在字串常數宣告使用字串插值,但所使用的插值僅限於字串常數 ```csharp const string Score = "99"; const string Name = "Bill"; const string Description = $"{Name}'s Score is {Score}"; ``` #### global using - 全域命名空間使用宣告,使用 global using 宣告使用的命名空間在專案內任何一個程式碼檔案就不需要再度宣告 - global using [namespace] ```csharp // 以前你可以有十個以上的 .cs 檔案都會需要宣告以下的 namespace using System; using System.Collections.Generic; using System.Linq; //有了global using後, 你可以建立一個專門放全域 using 的檔案 e.g. GlobalUsings.cs //內容如下 global using System; // 全域套用命名空間 global using System.Collections.Generic; global using System.Linq; global using MyProject.Common; global using static System.Math; // 全域套用靜態成員 global using IO = System.IO; // 全域套用別名 ``` #### [implicit usings](https://learn.microsoft.com/zh-tw/dotnet/core/project-sdk/overview#implicit-using-directives) C# 10 - 編譯器會自動引入基本的命名空間 (依據不同專案類型有不同的預設) - 檔案藏在 obj 目錄下 [project name].GlobalUsings.g.cs ```XML <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net6.0</TargetFramework> <ImplicitUsings>enable</ImplicitUsings> <!-- 是否啟用 implicit usings --> <Nullable>disable</Nullable> </PropertyGroup> <!-- NuGet 套件參考 --> <ItemGroup> <PackageReference Include="Newtonsoft.Json" Version="13.0.3" /> <PackageReference Include="Serilog" Version="3.0.1" /> </ItemGroup> <!-- 以下會自動編譯到 GlobalUsings.g.cs --> <ItemGroup> <!--客製化 Global using directives, 若預設不滿意可以自己決定要加啥 --> <Using Include="System.Reflection"/> <Using Include="System.Data.Common"/> <!-- 也可以為 NuGet 套件加入 global using--> <Using Include="Newtonsoft.Json"/> <Using Include="Serilog"/> </ItemGroup> </Project> ``` #### [file-scoped namespace](https://learn.microsoft.com/zh-tw/dotnet/csharp/language-reference/keywords/namespace) - file-scoped namespace 是一個新的語法特性,用於簡化命名空間的宣告方式,減少縮排層級,讓程式碼更簡潔 - 宣告此程式碼檔案屬於哪個命名空間,一個程式碼檔案只能有一個 ```XML <!-- namespace 可以直接 ; 結束, 不用再用大括號包住 class 了 --> namespace NamespaceSample003; internal class Program { /// <summary> /// C# 10.0 file-scoped namespace /// </summary> /// <param name="args"></param> static void Main(string[] args) { Console.WriteLine("Hello, World!"); } } ``` #### Extended property patterns - 模式比對 (pattern matching) 的語法加強, 讓你能中能更簡潔地存取巢狀屬性. - 可使用變數.屬性取代過多的 {} 讓屬性模式比對更方便 ```csharp // 確認 order 物件是否有一個不為 null 的 Customer 屬性, // 該 Customer 又有一個不為null 的 Address 屬性,而這個 Address 的 City 值剛好是 "Tokyo" if (order is { Customer: { Address: { City: "Tokyo" } } }) { Console.WriteLine("Japan order"); } // 以下寫法等價於上面, 但 C# 10 以前不能這麼寫. if (order is { Customer.Address.City: "Tokyo" }) { Console.WriteLine("Japan order"); } ``` #### Sealing overridden ToString() in sealed classe 即使類別是 sealed,仍然可以在覆寫的虛擬方法上加上 sealed override - 任何方法, 不侷限至於 ToString ```csharp public class Animal { public virtual void Speak() => Console.WriteLine("Virtual"); } public sealed class Dog : Animal { public sealed override void Speak() => Console.WriteLine("Bark"); } ``` #### infer delegate type 推斷委派變數型別 ```csharp var f = x => x + 1; // ❌ 無法編譯:編譯器不知道 x 是什麼型別 Func<int, int> f = x => x + 1; // 透過變數型別推斷型別 var f = (int x) => x + 1; // C# 10 才支援透過參數推斷型別 ``` #### Lambda expression attributes 允許將屬性 attribute 套用到 lambda 表達式 ```csharp [MyCustomHandler] var handle = (int x) => Console.WriteLine($"Handle {x}"); var action = [Loggable] (int id) => DoSomething(id); ``` #### CallerArgumentExpressionAttribute CallerArgumentExpression 可以用在方法的參數上,讓編譯器自動把呼叫時那個參數所傳遞的「表達式文字」作為字串,傳入這個參數對應的變數 ```csharp public static void NotNull<T>(T value, string paramName) { if (value is null) throw new ArgumentNullException(paramName); } string? name = null; NotNull(name, "name"); // 要手動打 "name" // 以下是新寫法 public static void NotNull<T>( T value, [CallerArgumentExpression("value")] string? paramName = null) { if (value is null) throw new ArgumentNullException(paramName); } string? name = null; NotNull(name); // 編譯器自動帶入 paramName = "name" ``` ```csharp public static void Require(bool condition, string message) { if (!condition) throw new ArgumentException($"Requirement failed: {message}"); } int x = -1; Require(x > 0, "x > 0"); // 你必須手動寫出 "x > 0" public static void Require( bool condition, [CallerArgumentExpression("condition")] string? expr = null) { if (!condition) throw new ArgumentException($"Requirement failed: {expr}"); } int x = -1; Require(x > 0); // 編譯器自動幫你帶入 expr = "x > 0" ``` ### [C# 11.0](https://docs.microsoft.com/zh-tw/dotnet/csharp/whats-new/csharp-11) #### Raw string literals 擴充字串插值的功能. 是一種新的字串表示法(使用 """ 開頭和結尾) 讓你不用再用 \ 來跳脫字元、也不用加 @, 而是原封不動地保留字串內容(e.g. 換行、縮排都會被完整保留) ```csharp // 這有雙雙引號 ""在雙雙引號內"" // v1 string value1 = "這有雙雙引號 \"\"在雙雙引號內\"\""; Console.WriteLine(value1); // v2 string value2 = @"這有雙雙引號 """"在雙雙引號內""""。"; Console.WriteLine(value2); // v3 string value3 = """這有雙雙引號 ""在雙雙引號內""。"""; Console.WriteLine(value3); // v3 也支援變數 var str = "雙雙引號"; string value4 = $"""這有{str} ""在雙雙引號內""。"""; Console.WriteLine(value4); ``` ```csharp // 輸出 { "name": "City", "age": 30 } // 以前要這麼寫 var json = "{\n \"name\": \"City\",\n \"age\": 30\n}"; // or var json = @"{ ""name"": ""City"", ""age"": 30 }"; // C# 11 後 var json = """ { "name": "City", "age": 30 } """; ``` #### [Generic math support]((https://devblogs.microsoft.com/dotnet/preview-features-in-net-6-generic-math/)) 以前想實現「泛型數學」時,你要靠: * dynamic(慢又沒型別安全) * Reflection(更慢) * Expression tree(麻煩) * Code generator(難維護) 現在你可以直接像模板數學(C++)一樣,寫乾淨、型別安全的泛型數學程式 ```csharp static T Add<T>(T a, T b) where T : INumber<T> { return a + b; //where T : INumber<T> 告訴編譯器:「T 是一種數值型別,支援基本數學運算 } Console.WriteLine(Add(3, 4)); // int → 7 Console.WriteLine(Add(1.5, 2.5)); // double → 4.0 Console.WriteLine(Add(3.2m, 1.1m)); // decimal → 4.3 ``` ##### 常見的運算介面家族(在 System.Numerics) 幾乎所有內建數值型別(int, double, decimal, float, long...) 都已在 .NET 7 中實作這些介面 ![image](https://hackmd.io/_uploads/HkS8f6gg-x.png) - INumber<T> 是這些介面的整合版本 ```csharp public interface INumber<TSelf> : IAdditionOperators<TSelf, TSelf, TSelf>, ISubtractionOperators<TSelf, TSelf, TSelf>, IMultiplyOperators<TSelf, TSelf, TSelf>, IDivisionOperators<TSelf, TSelf, TSelf>, IComparisonOperators<TSelf, TSelf, bool>, IAdditiveIdentity<TSelf, TSelf>, IMultiplicativeIdentity<TSelf, TSelf> where TSelf : INumber<TSelf>; ``` #### [Generic attributes](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#generic-attributes) 可以宣告「泛型屬性類別」並在使用時直接以型別參數指定它的泛型參數 ```csharp // Before C# 11: 在 C# 11 之前,你必須用 typeof() 傳入型別給屬性 public class TypeAttribute : Attribute { public TypeAttribute(Type t) => ParamType = t; public Type ParamType { get; } } [TypeAttribute(typeof(string))] public string Method() => default; //at Program.cs var method = typeof(Program).GetMethod(nameof(Method)); var attr = method.GetCustomAttribute<TypeAttribute>(); Console.WriteLine(attr.ParamType.Name); // string ``` ```csharp // After C# 11: 在 C# 11 之後,你可以直接用泛型語法 [MyAttr<T>] 標註,編譯期就能檢查型別正確性 //同時語法更自然、少字又安全。 public class GenericAttribute<TDescription> : Attribute { public TDescription Description { get; } public GenericAttribute(TDescription description) => Description = description; } [GenericAttribute<string>("string")] public string Method() => default; //at Program.cs var method = typeof(Program).GetMethod(nameof(Method)); var attr = method!.GetCustomAttribute<GenericAttribute<string>>(); // ✅ 指定 <string> Console.WriteLine(attr!.Description); // string ``` #### UTF-8 string literals C#11 新增了一個 後綴字元 u8,讓你可以在編譯期直接寫出 UTF-8 編碼常值 - 預設型別是 ReadOnlySpan<byte> ```csharp // 以前 byte[] utf8 = System.Text.Encoding.UTF8.GetBytes("Hello"); // 🐢 執行期轉換 // After ReadOnlySpan<byte> bytes = "Hello"u8; ``` #### Newlines in string interpolations 可以在字串插值 (interpolated expression) 的大括號 { … } 內加入換行 放寬「大括號內不能換行,只能單行表達式」的限制 ```csharp // before int score = 85; string message = $"Your score is {score switch { >= 90 => "Excellent", >= 70 => "Good", _ => "Needs Improvement" }}."; // after int score = 85; string message = $"Your score is { score switch { >= 90 => "Excellent", >= 70 => "Good", _ => "Needs Improvement", } }."; // message => "Your score is Good." ``` #### List patterns 「List Pattern」是 C# 11 在「模式比對(pattern matching)」領域中新增的一種模式語法,主要用於 比對陣列或清單(list/array)中的資料結構。可以用像 [1,2,3]、[_,2,..] 這種語法,來比對「一個陣列/清單是否符合某種元素數量、元素內容、甚至可變長度」的條件 ```csharp // 精確匹配 int[] nums = {1, 2, 3}; if (nums is [1, 2, 3]) { Console.WriteLine("Matched exactly [1,2,3]"); } // 忽略中間元素、只比對起頭或尾 int[] nums = {1, 9, 8, 3}; if (nums is [1, .., 3]) // .. 像正則表達式裡的 .* 代表 1~3之間可以有至少0個數字 { Console.WriteLine("Starts with 1 and ends with 3"); } // 只比對第二個元素 int[] nums = {0, 2, 5, 7}; if (nums is [_, 2, ..]) // _ 代表不在乎, 不在乎第一位是甚麼數字, 2 代表 第二位要是 2 , .. { Console.WriteLine("Second element is 2"); } // 與 switch 搭配 int[] vector = { 3, 4 }; string result = vector switch { [] => "empty", [var x] => $"single {x}", // 比對只有一個變數 , 指派值到 x [var x, var y] => $"two-dimensional {x},{y}", // 比對只有兩個變數, 指派值到 x,y _ => "other" }; Console.WriteLine(result); // two-dimensional 3,4 ``` #### File local types 一個新的在語言層級上新增的可見性範圍修飾詞. file 修飾詞可以用在任何頂層型別 (top-level type) * class * struct * record * record struct * interface * enum * delegate ![image](https://hackmd.io/_uploads/HJcpzGMlbe.png) ```csharp // File: MyService.cs file class Helper // Helper 只能在MyService.cs使用 { public static void Print() => Console.WriteLine("Hi!"); } public class MyService { public void Run() => Helper.Print(); } ``` #### Required members > required 是一個新的修飾詞,可以標記 class 或 struct 的屬性(property)或欄位(field),表示「在物件初始化時一定要被設定值」。如果沒設定,編譯器會報錯,不會執行期才發現 ```csharp public class Person { public required string Name { get; set; } // 必填 public int Age { get; set; } // 可選 } // 正確初始化 var p = new Person { Name = "City Chen", Age = 30 }; // 錯誤 var p = new Person(); // ❌ 編譯錯誤:必填屬性未設定 ``` #### Auto-default struct 改善 struct 預設建構行為的限制, 可以在 struct 中定義自己的 無參數建構函式, 而且編譯器會自動在其中初始化未明確設定的欄位為預設值(default) * 定義無參數建構函式(以前不行) * 任何沒初始化的欄位,會自動設成 default(T) ```csharp public struct Rectangle { public int Width; public int Height; public Rectangle(int width) { Width = width; // Height 沒有指定,C# 11 會自動幫你 default = 0, 但以前沒指派會報錯. } } ``` #### Pattern match Span<char> or ReadOnlySpan<char> on a constant string 針對 Span<char> 或 ReadOnlySpan<char> 支援用常數字串進行比對 ```csharp // Span<char> 或 ReadOnlySpan<char>,過去無法使用 pattern match. , 以下以前會報錯 ReadOnlySpan<char> input = "yes world".AsSpan(0, 3); // "yes" string result = input switch { "yes" => "Affirmative", "no" => "Negative", _ => "Unknown" }; Console.WriteLine(result); // Aff­irmative ``` #### Extended nameof scope C# 11 中,新增了 Extended nameof Scope(擴充 nameof 作用域) 的功能,讓 nameof 運算子的可用範圍更寬, 可在方法宣告或其參數的屬性中使用 nameof(parameter) 或 nameof(TParameter) ```csharp public class ExampleAttribute : Attribute { public ExampleAttribute(string name) { } } public class Demo { [Example(nameof(param))] // 以前只能常數寫死 public void M(int param) { } [Example(nameof(T))] public void G<T>(T value) { } public void H(int param, [Example(nameof(param))] int other) { } } ``` #### Numeric IntPtr and UIntPtr ![image](https://hackmd.io/_uploads/HkUPhzfxbl.png) ```csharp // C# 10 或更早的版本裡,IntPtr/UIntPtr 雖然代表「整數型別」,但並不能直接進行算術運算 IntPtr p = new IntPtr(5); p += 10; // ❌ 編譯錯誤:沒有對應的運算子 if (p > 0) // ❌ 也不行 p = new IntPtr(p.ToInt64() + 10); // 必須這樣寫, 但很麻煩 ``` C# 11 / .NET 7 現在讓 IntPtr/UIntPtr 實作了泛型數值介面 (Generic Math Interfaces) , 以下操作變的可行 ```csharp IntPtr a = 5; IntPtr b = 10; IntPtr c = a + b; // ✅ 現在可以加 Console.WriteLine(c); // 輸出 15 ``` #### ref fields and ref scoped variables ##### ref fields * 可以在 ref struct(只能存在於堆疊上的 struct)裡宣告 ref T field; 這樣的「儲存參考(alias)到變數/記憶體位置」的欄位, 可讓 struct 裡的一個欄位不是儲存值,而是儲存「參考(reference)/別名 (alias)」到另一個變數或記憶體位置 * 用途:例如你要做非常高效能的 buffer 操作、堆疊記憶體 (stackalloc) 操作,或類似 Span<T> 這樣的型別,就比較需要這樣的能力 * ref fields 只能宣告在 ref struct 型別中 ```csharp ref struct MyRefStruct { public ref int FieldValue; // ref field . 以前不允許 } ``` ##### scoped variables 關鍵字 scoped,用來限制參考 (ref) 或某些變數的生命週期 (lifetime) * 當你宣告 scoped ref T x、或方法參數 scoped ref T x、或局部變數 scoped Span<T> s 時,你是在告訴編譯器「此參考/變數不能逃離這個範圍/不能保存到較長的生命週期」 * 為什麼?因為當你有 ref 型別、堆疊記憶體 (stackalloc) 或 Span<T> 等,若這些參考逃到方法之外,就有可能造成「引用已經釋放/無效記憶體」的問題。compiler 為了安全會限制這些行為 ```csharp public ref struct BufferHolder { public ref int _value; public BufferHolder(ref int value) { _value = ref value; } } BufferHolder _escaped; // ❌ 危險,全域變數! // 不使用的情況 void Use() { int local = 42; // ❌ 試圖把局部 stack 變數的 ref 結構儲存到外部欄位 _escaped = new BufferHolder(ref local); //local 位於 stack。_escaped 位於 heap(或全域區域)。當 Use() 執行完畢時,local 被釋放, // 但 _escaped._value 仍指向那段 stack 記憶體。→ 如果程式繼續用 _escaped, //會發生懸空參考(dangling reference) } //使用的情況 void Use() { int local = 42; scoped ref BufferHolder bh = ref new BufferHolder(ref local); _escaped = bh; // ❌ 編譯錯誤:scoped 變數不能逃出 Use 範圍 // 若編譯器會偵測出 bh 被傳到比目前範圍更長壽的物件, 直接抱錯 //e.g. return bh or 把 bh 傳入 lambda } ``` ### [C# 12](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-12) #### Primary constructors 參考 record 型別(從 C# 9 開始)已經支援在型別名稱後帶參數的簡化語法 - 例如 record Person(string FirstName, string LastName); 現在將這種簡化語法擴展到一般 class 與 struct ```csharp // before public class Point { private double _x; private double _y; public Point(double x, double y) { _x = x; _y = y; } public double X => _x; public double Y => _y; } // after // class Point(double x, double y) 就等同宣告:建構函式:public Point(double x, double y) // x, y 在類別內可直接使用,不需要建立欄位 _x、_y , 也不需要把值手動存進欄位 // 編譯器會自動產生隱藏 field 來存 x, y public class Point(double x, double y) { public double X => x; public double Y => y; } ``` #### Collection Expressions 一種「新語法」來建立集合(array、List、Span、ImmutableArray…),不需要寫 new、型別、不必呼叫任何建構函式,只用 [ ] 就能建集合 >var numbers = [1, 2, 3]; // <- 預設推斷為 array , 此為 int[] - [1, 2, 3] 可以自動變成 int[]、List<int>、Span<int>、ImmutableArray<int>… 取決於它被賦予的目標型別(Target-Typed) ```csharp int[] arr = [1, 2, 3]; // 等價於 int[] arr = new int[] { 1, 2, 3 }; List<int> list = [1, 2]; // 等價於 List<int> list = new List<int> { 1, 2 }; Span<int> span = [1, 2, 3]; // 等價於 Span<int> span = stackalloc int[] { 1, 2, 3 }; ``` ##### spread operator (把集合合併) ```csharp var a = [1, 2, 3]; var b = [0, ..a, 4]; //..a 代表把集合 a 展開插進去 // 結果 b = [0, 1, 2, 3, 4] // .. 可跟方法一起用 IEnumerable<int> GetData() => new[] { 7, 8, 9 }; var numbers = [1, 2, ..GetData(), 99]; //結果:[1, 2, 7, 8, 9, 99] ``` #### Inline arrays Inline array = 在 struct 裡直接定義一塊連續的固定大小記憶體,類似 C語言 struct 裡的 int data[16]。而不是像 C# 的 int[] data = new int[16](陣列被分配在 heap). Inline arrays 配置在 array (因為是 struct) Inline arrays 讓你可以在 struct 內嵌(inline)一塊固定大小的元素 buffer,且是以 Span<T> 訪問 Inline Arrays 本質上是「底層效能優化工具」,不是一般的集合替代品。 ![image](https://hackmd.io/_uploads/r19kmfclZg.png) ```csharp [InlineArray(3)] public struct Float3 { private float _element0; // 名字一定要這樣寫. 編譯器會再依序定義 _element1~2 } void Demo() { Float3 v = new Float3(); v[0] = 1; v[1] = 2; v[2] = 3; foreach (var x in v) Console.WriteLine(x); } ``` #### Default lambda parameters 允許在 Lambda 參數清單中賦予參數預設值 ```csharp // C# 12 之前 (不允許,會編譯錯誤) // var addWithDefault = (int a, int b = 1) => a + b; // Error! // C# 12 之後 (允許) var addWithDefault = (int a, int b = 1) => a + b; Console.WriteLine(addWithDefault(5)); // 輸出: 6 (使用 b 的預設值 1) Console.WriteLine(addWithDefault(5, 10)); // 輸出: 15 (使用傳入的 10) ``` #### ref readonly parameters 允許用 ref 傳遞,但完全禁止修改裡面的資料,故不會產生 defensive copy ```csharp public struct Vector { public double X, Y; public void Normalize() => X /= Math.Sqrt(X*X + Y*Y); } //沒有 ref readonly 以前, 只能用 in void Test(in Vector v) { // ⚠ defensive copy(偷偷複製一份 v 再做 Normalize). 若 struct 很大, 會很花效能 v.Normalize(); } Test(v); // 編譯器會自動轉成下面那一行 Test(in v); //合法 //有 ref readonly 後 void Test(ref readonly Vector v) { // 編譯器偵測到會修改 this 的值, 會報錯 (絕對不會有防禦性複製) v.Normalize(); } Test(ref v); // 可這樣呼叫 Test(ref readonly v); // 可這樣呼叫 ``` ![image](https://hackmd.io/_uploads/HJUU4Pie-e.png) #### Alias any type Alias any type(別名任何類型)是一個語法糖 (syntactic sugar) 功能,可以對 任何型別 下 type alias,包含泛型型別、tuple、陣列、指標、delegate、nested type —— 全部都能 alias. > C# 12 將 using alias 擴充到所有型別 ```csharp using MyMap = System.Collections.Generic.Dictionary<string, int>; // C# 12 前可以 using Pair<T> = (T First, T Second); // ❌ C# 12 前不行 using ListOf<T> = List<T>; // ❌ C# 12 前不行 using RefInt = int*; // ❌ C# 12 前不行 using StringSpan = Span<char>; // ❌ C# 12 前不行 //所以 C#12後, 可以這樣用 Pair<int> p = (1, 2); MyList<string> names = new(); using Vec2 = (double X, double Y); Vec2 v = (3.0, 4.0); unsafe { using Ptr = int*; Ptr p; } ``` #### Experimental attribute [Experimental] 屬性,讓你可以把 API 標記為「實驗性、可能變動、不穩定、不保證相容」, 使用該 API 的程式碼會收到 警告,而不是錯誤 Experimental 用來表示: * ✔ 這功能還沒穩定 * ✔ 不保證未來版本不會改變 * ✔ 使用它的人必須知道自己在冒險 * ✔ 適合預覽、未定案、實驗功能 ```csharp! using System.Diagnostics.CodeAnalysis; [Experimental("MyFeature")] // MyFeature 是 id , 使用者若想要關掉這個警告, 會需要 public class NewAlgorithm { public static int Compute(int x) => x * 42; } // user int r = NewAlgorithm.Compute(10); // warning MyFeature: 'NewAlgorithm' is marked as experimental and may change or be removed in future versions. (會依照 SDK 不同而不一樣) ``` ```csharp // 如果使用者想關掉錯誤訊息 #pragma warning disable MyFeature int r = NewAlgorithm.Compute(10); // 在 .csproj 設定 <Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <NoWarn>$(NoWarn);MyFeature</NoWarn> </PropertyGroup> </Project> ``` ### C#13 #### params collections params 可以接收「任何可轉成集合的型別」,不再只能接陣列。呼叫端也可以用 collection expression ([ ... ]) 傳入 ```csharp // 以前只能這樣寫 void Log(params int[] items) // C# 13 以下都合法 void Log(params IEnumerable<int> items) {} void Log(params List<string> items) {} void Log(params Span<int> items) {} void Log(params ReadOnlySpan<char> text) {} // 可以用 C# 12 collection expression 傳入 void Print(params List<int> values) { foreach (var v in values) Console.WriteLine(v); } // 呼叫 Print([1, 2, 3, 4]); ``` #### [New lock object](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-13#new-lock-object) 提供了一個專門設計用來鎖定的新類別 Lock,用來取代過去我們習慣使用的 new object() ```csharp // 以前 private static readonly object _gate = new(); void DoWork() { lock (_gate) { // 臨界區 } } // 以上相當於 //Monitor.Enter(_gate); //try { ... } //finally { Monitor.Exit(_gate); } ``` ```csharp // C#13 using System.Threading; private static readonly Lock _gate = new(); // 新的 Lock 類 void DoWork() { lock (_gate) { // 臨界區 } } // 以上相當於 using (_gate.EnterScope()) { // 臨界區 } // Dispose 自動釋放鎖 ``` #### New escape sequence C# 13 引入新的字元跳脫序列 * \e — ESC (Escape) 字元 ```csharp //假設要在終端機印出「綠色粗體文字」,需要發送 ANSI Escape Code (ESC [ 1;32m) string oldWay = "\u001b[1;32mThis is Green Text\u001b[0m"; // old string newWay = "\e[1;32mThis is Green Text\e[0m"; // C# 13 ``` #### Method group natural type Method group natural type 是 C# 13 的一項語言改進 ```csharp public class Example { public static int M(int x) => x + 1; public static string M(string s) => s + "!"; } // C# 13 以前 var f = Example.M; // 編譯錯誤 — 因為 M 有兩個 overload,不知道要哪個. Func<int,int> f = Example.M; //必須明確指定型別 // C# 13 以後 int x = 10; var f = Example.M; // 不會編譯錯誤 var r = f(x); // 編譯器會推斷 f 是 Func<int,int> ``` #### Implicit index acces 在「物件初始化子(object initializer)」裡,現在可以用 ^ 這個 Index 索引運算子來存取一維集合的元素 ```csharp public class TimerRemaining{ public int[] buffer { get; set; } = new int[10]; } var countdown = new TimerRemaining(){ // 以前只能這樣初始化 buffer = new int[10] { 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 } }; //countdown.buffer[0] = 9; 或一個一個指派 //countdown.buffer[1] = 8; // ... //countdown.buffer[9] = 0; // C# 13 以前這樣寫, 會出錯 var countdown = new TimerRemaining() { buffer = { [^1] = 0, // ❌ 編譯錯誤:初始化子裡不能用 ^ , 只允許寫正向索引 } }; var countdown = new TimerRemaining() // C# 13 可以這樣寫 { buffer = { [^1] = 0, // 倒數第 1 個(index 9) [^2] = 1, // 倒數第 2 個(index 8) [^3] = 2, [^4] = 3, [^5] = 4, [^6] = 5, [^7] = 6, [^8] = 7, [^9] = 8, [^10] = 9, // 倒數第 10 個(index 0) } }; ``` #### ref and unsafe in iterators and async methods 允許在 iterator(yield return)與 async 方法中使用 ref , ref struct 與 unsafe ```csharp // C# 13 才可使用 ref 在 async 裡 public async Task<int> SumAsync(ref int x) { await Task.Delay(10); return x + 1; } // unsafe 能出現在 async 裡 public async Task FooAsync() { unsafe { int x = 10; int* p = &x; await Task.Delay(1); // ✔ 現在可共存 } } ``` #### ref struct interfaces C# 13 允許 ref struct 可以實作介面, - 但仍然不能被裝箱,所以不能直接轉型成介面型別 ```csharp public interface IMyInterface{ void Do(); } // C# 13 之後:ref struct 可以實作一般介面 public ref struct MyData : IMyInterface { public void Do() { // ... } } ``` #### allows ref struct C# 13 之前 ref struct 不能當泛型型別引數. 但現在可以透過語法去允許 ref struct ```csharp // T 除了以往的簡單型別也能是 class, struct , 然後 ref struct static T Identity<T>(T value) where T : allows ref struct => value; // T 是 Span<int> Span<int> span = stackalloc int[10]; Span<int> result = Identity(span); ``` #### More partial members 允許在「屬性 (Properties)」和「索引子 (Indexers)」上使用 partial 關鍵字 - 之前 partial 只能用在 class / struct / interface / record / method ```csharp public partial class C // file A { // Declaring declaration public partial string Name { get; set; } } public partial class C // file B { // Implementation declaration private string _name; public partial string Name { get => _name; set => _name = value; } } ``` #### Overload resolution priority 加了一個 OverloadResolutionPriorityAttribute 屬性,讓你可以「手動告訴編譯器:這個多載比較優先」。數字越大,優先權越高。 - 主要是套件開發者用的, 可以把舊版本的 API priority 設定的很低 ```csharp class C3 { // 新版本不標註,priority = 0 public void M1(int i) { Console.WriteLine("int"); } [OverloadResolutionPriority(1)] public void M1(long l) { Console.WriteLine("long"); } } int i = 1; var c = new C3(); c.M1(i); // 會印 "long" !! ``` --- You can find me on - [GitHub](https://github.com/s0920832252) - [Facebook](https://www.facebook.com/fourtune.chen) 若有謬誤 , 煩請告知 , 新手發帖請多包涵 # :100: :muscle: :tada: :sheep: