C# 6 之後的新玩意隨便的學習筆記 === Reference: [The history of C#](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-version-history) ## C# version and .NET version mapping | Target | Version | C# language version default | | --- | --- | --- | | .NET | 7.x | C# 11 | | .NET | 6.x | C# 10 | | .NET | 5.x | C# 9.0 | | .NET Core | 3.x | C# 8.0 | | .NET Core | 2.x | C# 7.3 | | .NET Standard | 2.1 | C# 8.0 | | .NET Standard | 2.0 | C# 7.3 | | .NET Standard | 1.x | C# 7.3 | |.NET Framework | all | C# 7.3 | ## 6 ### [Static imports](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/using-directive#static-modifier) - The using static directive names a type whose static members and nested types you can access without specifying a type name. Its syntax is: ``` C# using static <fully-qualified-type-name>; ``` e.g. ``` C# using static System.Console; using static System.Math; class Program { static void Main() { WriteLine(Sqrt(3*3 + 4*4)); } } ``` ### [Exception filters](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/when) - You use the when contextual keyword to specify a filter condition in the following contexts: ``` C# catch (ExceptionType [e]) when (expr) ``` e.g. ``` C# public static async Task<string> MakeRequest() { var client = new HttpClient(); var streamTask = client.GetStringAsync("https://localHost:10000"); try { var responseText = await streamTask; return responseText; } catch (HttpRequestException e) when (e.Message.Contains("301")) { return "Site Moved"; } catch (HttpRequestException e) when (e.Message.Contains("404")) { return "Page Not Found"; } catch (HttpRequestException e) { return e.Message; } } ``` ### Auto-property initializers ``` C# public class Student { public string Name { { get; set: } = "Akash"; // auto property initializer } } ``` ### [Expression bodied members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/lambda-operator#expression-body-definition) ``` C# member => expression; ``` e.g. ``` C# public override string ToString() => $"{fname} {lname}".Trim(); ``` ### [Null propagator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#null-conditional-operators--and-) * A null-conditional operator applies a member access (?.) or element access (?[]) operation to its operand only if that operand evaluates to non-null; otherwise, it returns null e.g. ``` C# A?.B?.Do(C); A?.B?[C]; ``` ### [String interpolation using $](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/tokens/interpolated) e.g. ``` C# Console.WriteLine($"Hello, {name}! Today is {date.DayOfWeek}, it's {date:HH:mm} now."); ``` ### [nameof expression (C# reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof) ``` C# Console.WriteLine(nameof(System.Collections.Generic)); // output: Generic ``` ## 7 ### [Tuples and deconstruction](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/value-tuples) - 實際上是 [ValueTyuple](https://learn.microsoft.com/zh-tw/dotnet/api/system.valuetuple?view=net-7.0) e.g. ```C# (double, int) t1 = (4.5, 3); Console.WriteLine($"Tuple with elements {t1.Item1} and {t1.Item2}."); // Output: // Tuple with elements 4.5 and 3. (double Sum, int Count) t2 = (4.5, 3); Console.WriteLine($"Sum of {t2.Count} elements is {t2.Sum}."); // Output: // Sum of 3 elements is 4.5. var sum = 4.5; var count = 3; var t = (sum, count); Console.WriteLine($"Sum of {t.count} elements is {t.sum}."); ``` ### [Ref locals](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/declarations#reference-variables) - 可以增加 performance 用 e.g. ``` C# void Display(int[] s) => Console.WriteLine(string.Join(" ", s)); int[] xs = { 0, 0, 0 }; Display(xs); ref int element = ref xs[0]; element = 1; Display(xs); element = ref xs[^1]; element = 3; Display(xs); // Output: // 0 0 0 // 1 0 0 // 1 0 3 ``` #### ref readonly e.g. ``` C# int[] xs = { 1, 2, 3 }; ref readonly int element = ref xs[0]; // element = 100; error CS0131: The left-hand side of an assignment must be a variable, property or indexer Console.WriteLine(element); // output: 1 element = ref xs[^1]; Console.WriteLine(element); // output: 3 ``` ### ref return e.g. ``` C# using System; public class NumberStore { private readonly int[] numbers = { 1, 30, 7, 1557, 381, 63, 1027, 2550, 511, 1023 }; public ref int GetReferenceToMax() { ref int max = ref numbers[0]; for (int i = 1; i < numbers.Length; i++) { if (numbers[i] > max) { max = ref numbers[i]; } } return ref max; } public override string ToString() => string.Join(" ", numbers); } public static class ReferenceReturnExample { public static void Run() { var store = new NumberStore(); Console.WriteLine($"Original sequence: {store.ToString()}"); ref int max = ref store.GetReferenceToMax(); max = 0; Console.WriteLine($"Updated sequence: {store.ToString()}"); // Output: // Original sequence: 1 30 7 1557 381 63 1027 2550 511 1023 // Updated sequence: 1 30 7 1557 381 63 1027 0 511 1023 } } ``` ## 7.2 ### [private protected access modifier](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/private-protected) - 可以用於某些設計的時候 e.g. ``` C# public class BaseClass { private protected int myValue = 0; } public class DerivedClass1 : BaseClass { void Access() { var baseObject = new BaseClass(); // Error CS1540, because myValue can only be accessed by // classes derived from BaseClass. // baseObject.myValue = 5; // OK, accessed through the current derived class instance myValue = 5; } } ``` ## 8 ### [Readonly members](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/struct#readonly-instance-members) - You can also use the readonly modifier to declare that an instance member doesn't modify the state of a struct. If you can't declare the whole structure type as readonly, use the readonly modifier to mark the instance members that don't modify the state of the struct. - 看起來是給 compiler 最佳化用,可以有更好的 performance ### [Default interface methods](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/interface#default-interface-members) - [Tutorial: Update interfaces with default interface methods](https://learn.microsoft.com/en-us/dotnet/csharp/advanced-topics/interface-implementation/default-interface-methods-versions) - 設計上的用途可以參考這篇介紹,但看完還不是很懂在設計上的好處 e.g. ``` C# interface IA { void M() { WriteLine("IA.M"); } } class C : IA { } // OK IA i = new C(); i.M(); // prints "IA.M" new C().M(); // error: class 'C' does not contain a member 'M' ``` ### [Using declarations](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/using) - 現在可以支援不用 brace 的 using e.g. ``` C# static IEnumerable<int> LoadNumbers(string filePath) { using StreamReader reader = File.OpenText(filePath); var numbers = new List<int>(); string line; while ((line = reader.ReadLine()) is not null) { if (int.TryParse(line, out int number)) { numbers.Add(number); } } return numbers; } ``` - 支援多個 using 擺在一起 e.g. ``` C# using (StreamReader numbersFile = File.OpenText("numbers.txt"), wordsFile = File.OpenText("words.txt")) { // Process both files } ``` ### [Nullable reference types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/nullable-reference-types) - Project 要打開 `<nullable>` 的設定 - 設定值: https://learn.microsoft.com/en-us/dotnet/csharp/nullable-references#nullable-contexts - 就是 reference type 也會需要補上 ? 如果那個 reference 是可能為 null 的話 e.g. ``` C# string notNull = "Hello"; string? nullable = default; notNull = nullable!; // null forgiveness ``` ### [Asynchronous streams](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/async-streams) - 有幾個非同步的 interface - IAsyncDisposable - IAsyncEnumerable / IAsyncEnumerator - 就可以搭配語法 - `await foreach (var i in enumerable)` ### [Indices and ranges](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/member-access-operators#range-operator-) - Index from end operator `^` - 從最末端開始的 index 回來找 ``` C# int[] xs = new[] { 0, 10, 20, 30, 40 }; int last = xs[^1]; Console.WriteLine(last); // output: 40 var lines = new List<string> { "one", "two", "three", "four" }; string prelast = lines[^2]; Console.WriteLine(prelast); // output: three string word = "Twenty"; Index toFirst = ^word.Length; char first = word[toFirst]; Console.WriteLine(first); // output: T ``` - Range operator `..` - 從 x 到 y - 開頭結尾也可以只給一邊或都不給 - a.. is equivalent to a..^0 - ..b is equivalent to 0..b - .. is equivalent to 0..^0 e.g. ``` C# int[] numbers = new[] { 0, 10, 20, 30, 40, 50 }; int start = 1; int amountToTake = 3; int[] subset = numbers[start..(start + amountToTake)]; Display(subset); // output: 10 20 30 int margin = 1; int[] inner = numbers[margin..^margin]; Display(inner); // output: 10 20 30 40 string line = "one two three"; int amountToTakeFromEnd = 5; Range endIndices = ^amountToTakeFromEnd..^0; string end = line[endIndices]; Console.WriteLine(end); // output: three void Display<T>(IEnumerable<T> xs) => Console.WriteLine(string.Join(" ", xs)); ``` ### [Null-coalescing assignment](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/assignment-operator#null-coalescing-assignment) - 除了 `??` 外多了一個 `??=` 的 operator ### [Unmanaged constructed types](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/unmanaged-types) - Generic 多了一個 `unmanaged` 的限制條件可以使用 - 其他目的不明 @@ ## 9 比爾叔整理的 [C# 9.0 搶先看](https://dotblogs.com.tw/billchung/Series?qq=C%23%209.0%20%E6%90%B6%E5%85%88%E7%9C%8B) ### [Records](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#record-types) - A reference type - 其實就是 record class - 宣告語法 - [C# 9:Record 詳解](https://www.huanlintalk.com/2022/03/csharp-9-record-explained.html) ``` C# // 1 public record Person(string FirstName, string LastName); // 2 public record Person { public required string FirstName { get; init; } public required string LastName { get; init; } }; // 3 public record Person { public required string FirstName { get; set; } public required string LastName { get; set; } }; ``` - While records can be mutable, they are primarily intended for supporting immutable data models. The record type offers the following features: - Concise syntax for creating a reference type with immutable properties. - 使用簡易語法預設建立的就是 immutable properties - e.g. `public record Person(string FirstName, string LastName);` - A record type is not necessarily immutable - Behavior useful for a data-centric reference type: - Value equality - For types with the record modifier (record class, record struct, and readonly record struct), two objects are equal if they are of the same type and store the same values. - Concise syntax for nondestructive mutation - `with` keyword ``` C# public record Person(string FirstName, string LastName) { public string[] PhoneNumbers { get; init; } } public static void Main() { Person person1 = new("Nancy", "Davolio") { PhoneNumbers = new string[1] }; Console.WriteLine(person1); // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] } Person person2 = person1 with { FirstName = "John" }; Console.WriteLine(person2); // output: Person { FirstName = John, LastName = Davolio, PhoneNumbers = System.String[] } Console.WriteLine(person1 == person2); // output: False person2 = person1 with { PhoneNumbers = new string[1] }; Console.WriteLine(person2); // output: Person { FirstName = Nancy, LastName = Davolio, PhoneNumbers = System.String[] } Console.WriteLine(person1 == person2); // output: False person2 = person1 with { }; Console.WriteLine(person1 == person2); // output: True } ``` - Built-in formatting for display - 呼叫 `ToString` 的時候會依照 `Person { FirstName = Nancy, LastName = Davolio, ChildNames = System.String[] }` 來輸出 - Support for inheritance hierarchies - 只有 `record class` 可以 - A record can inherit from another record. However, a record can't inherit from a class, and a class can't inherit from a record. - Deconstructor behavior in derived records e.g. ``` C# public abstract record Person(string FirstName, string LastName); public record Teacher(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public record Student(string FirstName, string LastName, int Grade) : Person(FirstName, LastName); public static void Main() { Person teacher = new Teacher("Nancy", "Davolio", 3); var (firstName, lastName) = teacher; // Doesn't deconstruct Grade Console.WriteLine($"{firstName}, {lastName}");// output: Nancy, Davolio var (fName, lName, grade) = (Teacher)teacher; Console.WriteLine($"{fName}, {lName}, {grade}");// output: Nancy, Davolio, 3 } ``` ### [Init only setters](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#init-only-setters) - 製作包含 class 自己也是 readonly 的 property e.g. ``` C# public struct WeatherObservation { public DateTime RecordedAt { get; init; } public decimal TemperatureInCelsius { get; init; } public decimal PressureInMillibars { get; init; } public override string ToString() => $"At {RecordedAt:h:mm tt} on {RecordedAt:M/d/yyyy}: " + $"Temp = {TemperatureInCelsius}, with {PressureInMillibars} pressure"; } ``` ### [Support for code generators](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-9#support-for-code-generators) * [Module initializers](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/module-initializers) * [New features for partial methods](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-9.0/extending-partial-methods) ## 10 比爾叔整理的 [C# 10 新功能](https://dotblogs.com.tw/billchung/Series?qq=C%23%2010%20%E6%96%B0%E5%8A%9F%E8%83%BD) ### [Record structs](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#record-structs) - 新增了明確指定把 record 宣告成 struct 的支援,意思就是可以宣告 `struct record` - [C# 10:Record 的改進](https://www.huanlintalk.com/2022/03/csharp-10-record-improvements.html) ### [Improvements of structure types](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#improvements-of-structure-types) - You can declare an instance parameterless constructor in a structure type and initialize an instance field or property at its declaration. For more information, see the Struct initialization and default values section of the Structure types article. - `with` 的表達示的左邊可以是 structure type or an anonymous type. ``` C# using System; public class InheritanceExample { public record Point(int X, int Y); public record NamedPoint(string Name, int X, int Y) : Point(X, Y); public static void Main() { Point p1 = new NamedPoint("A", 0, 0); Point p2 = p1 with { X = 5, Y = 3 }; Console.WriteLine(p2 is NamedPoint); // output: True Console.WriteLine(p2); // output: NamedPoint { X = 5, Y = 3, Name = A } } } ``` - 可以在宣告 strcut 加上 `readonly` 的 keyword `readonly struct` - ### [Global using directives](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#global-using-directives) - 支援 `global using <fully-qualified-namespace>;` - 讓 using 的東西在 project 的所有的檔案都適用 - 也支援 `global using static` - 也可以直接在 project 檔案加上 `<Using>` 的區段 - e.g. `<Using Include="My.Awesome.Namespace" />` ### [Lambda expression improvements](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#lambda-expression-improvements) - 可以正常推論 netrual type - e.g. 現在這樣是支援的,= 的左邊不需要指定明確的型別 ``` C# object parse = (string s) => int.Parse(s); // Func<string, int> Delegate parse = (string s) => int.Parse(s); // Func<string, int> var read = Console.Read; // Just one overload; Func<int> inferred var write = Console.Write; // ERROR: Multiple overloads, can't choose LambdaExpression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>> Expression parseExpr = (string s) => int.Parse(s); // Expression<Func<string, int>> ``` - 資訊不足的狀態下仍然是無法得 - e.g. 下面這樣仍然是不行 ``` C# var parse = s => int.Parse(s); // ERROR: Not enough type info in the lambda ``` ### [Record types can seal ToString](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#record-types-can-seal-tostring) - [C# 10:Record 的改進](https://www.huanlintalk.com/2022/03/csharp-10-record-improvements.html) ### [CallerArgumentExpression attribute diagnostics](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-10#callerargumentexpression-attribute-diagnostics) - [CallerArgumentExpression](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-10.0/caller-argument-expression) - 可以自動帶入 Caller 額外的描述,這個是帶入宣告時的參數名稱,建構子的參數代表的就是名稱 - e.g. ``` C# public static void InRange(int argument, int low, int high, [CallerArgumentExpression("argument")] string argumentExpression = null, [CallerArgumentExpression("low")] string lowExpression = null, [CallerArgumentExpression("high")] string highExpression = null) { if (argument < low) { throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $"{argumentExpression} ({argument}) cannot be less than {lowExpression} ({low})."); } if (argument > high) { throw new ArgumentOutOfRangeException(paramName: argumentExpression, message: $"{argumentExpression} ({argument}) cannot be greater than {highExpression} ({high})."); } } ``` - 同場加映 C# 5.0 就有的同樣目的的 attribute - CallerMemberName - CallerFilePath - CallerLineNumber - [Determine caller information using attributes interpreted by the C# compiler](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/attributes/caller-information#argument-expressions) ## 11 比爾叔整理的 [C# 11 新功能](https://dotblogs.com.tw/billchung/Series?qq=C%23%2011%20%E6%96%B0%E5%8A%9F%E8%83%BD) ### [Raw string literals](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#raw-string-literals) - 字串支援前後 `"""` (就是三個 " )的宣告方式 - 在這個段落裡面的東西都會被當成字串來對待,包含換行等等 - 如果要在中間夾雜 `"` 也不需要用 `""` 就用單個即可 - 換行的開頭的 空白 都會被去掉 - e.g. ``` C# string longMessage = """ This is a long message. It has several lines. Some are indented more than others. Some should start at the first column. Some have "quoted text" in them. """; ``` ### [Generic math support](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#generic-math-support) - [Generic math](https://learn.microsoft.com/en-us/dotnet/standard/generics/math) - [C# 11 新功能 -- static virtual members in interfaces and generic math](https://dotblogs.com.tw/billchung/2023/01/01/015634) - 啟用了這些 featrue 來支援 generic math - static virtual members in interfaces - checked user defined operators - relaxed shift operators - unsigned right-shift operator #### static virtual members in interfaces #### [unsigned right-shift operator](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/bitwise-and-shift-operators#unsigned-right-shift-operator-) - 新的 operator `>>>` - The >>> operator always performs a logical shift. That is, the high-order empty bit positions are always set to zero, regardless of the type of the left-hand operand ``` C# int x = -8; Console.WriteLine($"Before: {x,11}, hex: {x,8:x}, binary: {Convert.ToString(x, toBase: 2), 32}"); int y = x >> 2; Console.WriteLine($"After >>: {y,11}, hex: {y,8:x}, binary: {Convert.ToString(y, toBase: 2), 32}"); int z = x >>> 2; Console.WriteLine($"After >>>: {z,11}, hex: {z,8:x}, binary: {Convert.ToString(z, toBase: 2).PadLeft(32, '0'), 32}"); // Output: // Before: -8, hex: fffffff8, binary: 11111111111111111111111111111000 // After >>: -2, hex: fffffffe, binary: 11111111111111111111111111111110 // After >>>: 1073741822, hex: 3ffffffe, binary: 00111111111111111111111111111110 ``` ### [Generic attributes](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#generic-attributes) ``` C# // Before C# 11: public class TypeAttribute : Attribute { public TypeAttribute(Type t) => ParamType = t; public Type ParamType { get; } } [TypeAttribute(typeof(string))] public string Method() => default; // C# 11 feature: public class GenericAttribute<T> : Attribute { } [GenericAttribute<string>()] public string Method() => default; ``` ### [UTF-8 string literals](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#utf-8-string-literals) - .NET 原生使用 UTF-16 來儲存字串,現在可以指定使用 UTF-8 - UTF-8 string 不是 compile time 的常數,所以不可以當作 method 參數的預設值 ``` C# ReadOnlySpan<byte> AuthWithTrailingSpace = new byte[] { 0x41, 0x55, 0x54, 0x48, 0x20 }; ReadOnlySpan<byte> AuthStringLiteral = "AUTH "u8; // 要把 utf-8 字串轉 byte[] byte[] AuthStringLiteral = "AUTH "u8.ToArray(); ``` ### [Newlines in string interpolations](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#newlines-in-string-interpolations) - 在 `$` 的字串裡面輸入變數的 `{` `}` 中間可以換行了 ### [File local types](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#file-local-types) - 新的 keyword `file`,可以用來宣告在這個檔案限定的東西 e.g. ``` C# file class HiddenWidget { // implementation } ``` - source generator 產生的 type 一般都會使用這個宣告 ### [Required members](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#required-members) - [C# 11 新功能 -- Required members](https://dotblogs.com.tw/billchung/2023/01/10/205859) - 總之就是可以加上 `required` 的關鍵字,讓使用的時候在建構子一定要給值 - [required modifier (C# Reference)](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) - 這邊有一些使用的細項規則,包含繼承之後 `required` 是不能被隱藏 (hide) 的等等 ### [Extended nameof scope](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#extended-nameof-scope) - 支援在 attribute 裡面使用 `nameof` 了 ### [ref fields and ref scoped variables](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#ref-fields-and-ref-scoped-variables) - [scoped ref](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/statements/declarations#scoped-ref) - The contextual keyword scoped restricts the lifetime of a value. The scoped modifier restricts the ref-safe-to-escape or safe-to-escape lifetime, respectively, to the current method. Effectively, **adding the scoped modifier asserts that your code won't extend the lifetime of the variable.** ### [Improved method group conversion to delegate](https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-11#improved-method-group-conversion-to-delegate) - 主要是在使用 delegate 的"方式"的效能改進 - 看 [Improved method group conversion to delegate](https://prographers.com/blog/c-11-improved-method-group) 介紹比較好懂 ## 其他待讀主題 - [Records](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/record) - [Pattern matching](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/patterns) - [Source Generators](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview)