一、Readonly members 在 C# 8.0,readonly 修飾詞可以用於 property 或 indexer。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 using System;class Example { static void Main (string [] args ) { Console.WriteLine(new Point().Origin_two.X); } } public readonly struct Point{ public double X { get ; } public double Y { get ; } public Point (int x, int y ) { X = x; Y = y; } private static readonly Point origin = new Point(0 , 0 ); public readonly Point Origin_two { get { return origin; } } }
readonly 修飾詞也可用於 get accessors。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 using System;class Example { static void Main (string [] args ) { Console.WriteLine(new Point().Origin_two.X); } } public readonly struct Point{ public double X { get ; } public double Y { get ; } public Point (int x, int y ) { X = x; Y = y; } private static readonly Point origin = new Point(0 , 0 ); public Point Origin_two { readonly get { return origin; } set { Origin_two = value ; } } }
但 readonly member 有其限制, 1、readonly 修飾詞不可用於 static methods or properties。 2、readonly 修飾詞不可用於建構式。
二、Default interface methods
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { IDefaultInterfaceMethod anyClass = new AnyClass(); anyClass.DefaultMethod(); Console.ReadKey(); } } interface IDefaultInterfaceMethod { public void DefaultMethod () { Console.WriteLine("I am a default method in the interface!" ); } } class AnyClass : IDefaultInterfaceMethod { } }
在 C# 8.0,介面也可以有自己的預設實作方法了。
三、Pattern matching enhancements 1、Switch expressions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 using System;class Example { static void Main (string [] args ) { RGBColor color = FromRainbow(Rainbow.Red); Console.ReadKey(); } public static RGBColor FromRainbowClassic (Rainbow colorBand ) { switch (colorBand) { case Rainbow.Red: return new RGBColor(0xFF , 0x00 , 0x00 ); case Rainbow.Orange: return new RGBColor(0xFF , 0x7F , 0x00 ); case Rainbow.Yellow: return new RGBColor(0xFF , 0xFF , 0x00 ); case Rainbow.Green: return new RGBColor(0x00 , 0xFF , 0x00 ); case Rainbow.Blue: return new RGBColor(0x00 , 0x00 , 0xFF ); case Rainbow.Indigo: return new RGBColor(0x4B , 0x00 , 0x82 ); case Rainbow.Violet: return new RGBColor(0x94 , 0x00 , 0xD3 ); default : throw new ArgumentException(message: "invalid enum value" , paramName: nameof (colorBand)); }; } public static RGBColor FromRainbow (Rainbow colorBand ) => colorBand switch { Rainbow.Red => new RGBColor(0xFF , 0x00 , 0x00 ), Rainbow.Orange => new RGBColor(0xFF , 0x7F , 0x00 ), Rainbow.Yellow => new RGBColor(0xFF , 0xFF , 0x00 ), Rainbow.Green => new RGBColor(0x00 , 0xFF , 0x00 ), Rainbow.Blue => new RGBColor(0x00 , 0x00 , 0xFF ), Rainbow.Indigo => new RGBColor(0x4B , 0x00 , 0x82 ), Rainbow.Violet => new RGBColor(0x94 , 0x00 , 0xD3 ), _ => throw new ArgumentException(message: "invalid enum value" , paramName: nameof (colorBand)), }; } public class RGBColor { public byte red { get ; set ; } public byte green { get ; set ; } public byte blue { get ; set ; } public RGBColor (byte r, byte g, byte b ) { red = r; green = g; blue = b; } } public enum Rainbow{ Red, Orange, Yellow, Green, Blue, Indigo, Violet }
這個 Recursive patterns 的意思是表達把 switch 陳述式的語法再更精簡化,重點如下 a、把 switch 陳述式改成 switch 表達式了。 b、每一個 case 都改成像是表達式的寫法。 c、default case 也簡化成「_」底線了。
2、Property patterns
使用 switch 運算式(expression)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { Address Address = new Address() { State = "MI" }; decimal result = ComputeSalesTax(Address, 100 ); Console.WriteLine(result); } public static decimal ComputeSalesTax (Address location, decimal salePrice ) => location switch { { State: "WA" } => salePrice * 0.06 M, { State: "MN" } => salePrice * 0.75 M, { State: "MI" } => salePrice * 0.05 M, _ => 0 M }; } class Address { public string State { get ; set ; } } }
結果畫面為
使用 is 運算式(expression) 如下範例使用巢狀方式做 is 運算式模式比對。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 namespace ConsoleApp1 { class Program { static void Main (string [] args ) { Student a = new Student() { StudentId = 1 , StudentName = "Bill" , MyStudentClass = new StudentClass { Grade = "三年" , ClassNo = "二班" }, Exams = new List<Exam> { new Exam { ExamId = 1 , Subject = "math" , Score = 100 } } }; if (a is { MyStudentClass: { Grade: "三年" } and { ClassNo: "二班" } }) Console.WriteLine("與周董同班級" ); } } class Student { public int StudentId { get ; set ; } public string StudentName { get ; set ; } public StudentClass MyStudentClass { get ; set ; } public List<Exam> Exams { get ; set ; } } class StudentClass { public string Grade { get ; set ; } public string ClassNo { get ; set ; } } class Exam { public int ExamId { get ; set ; } public string Subject { get ; set ; } public int Score { get ; set ; } } }
註:範例中有用到 C#9.0 的 not、and 和 or 模式結合器技術。
3、Tuple patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { Console.WriteLine(FuncBinary(1 , 1 )); } public static string FuncBinary (int first, int second ) => (first, second) switch { (0 , 0 ) => "zero" , (0 , 1 ) => "one" , (1 , 0 ) => "two" , (1 , 1 ) => "three" , (_, _) => "error" }; } }
結果畫面為 說明: 此語法使用 Tuple 的型式來做 switch 比對,Tuple patterns 是用 function 的型式來呈現。
4、Positional patterns
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { Point Point = new Point(1 , 1 ); Console.WriteLine(GetQuadrant(Point)); } static Quadrant GetQuadrant (Point point ) => point switch { (0 , 0 ) => Quadrant.Origin, var (x, y) when x > 0 && y > 0 => Quadrant.One, var (x, y) when x < 0 && y > 0 => Quadrant.Two, var (x, y) when x < 0 && y < 0 => Quadrant.Three, var (x, y) when x > 0 && y < 0 => Quadrant.Four, var (_, _) => Quadrant.OnBorder, _ => Quadrant.Unknown }; } public class Point { public int X { get ; } public int Y { get ; } public Point (int x, int y ) => (X, Y) = (x, y); public void Deconstruct (out int x, out int y ) => (x, y) = (X, Y); } public enum Quadrant { Unknown, Origin, One, Two, Three, Four, OnBorder } }
結果畫面為 說明: 此語法使用物件解構成 Tuple 的型式來做 switch 比對,Positional patterns 是用 function 的型式來呈現。
四、using statement 在 C# 8.0,using statement 可寫成
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 using System;using System.IO;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { { using StreamReader sr = new StreamReader(@"mytext.txt" ); Console.WriteLine(sr.ReadToEnd()); } Console.ReadKey(); } } }
被添加 using 修飾詞的變數 sr,將存活在{}範圍內, 超出其範圍,該變數將會被自動解構。
五、Static local functions local function 在 C# 7.0 被推出, local function 有能力去存取 local function 外的同層變數 y。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int y = 0 ; LocalFunction(); Console.WriteLine(y); void LocalFunction () => y = 100 ; } } }
執行結果為
但在 C# 8.0,可添加 static 修飾詞在 local function 上, 使 local function 只能存取 local function 內的變數, 而不能去操作 local function 外的同層變數了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int y = 10 ; Console.WriteLine(LocalFunction(y)); static int LocalFunction (int x ) => x * x; } } }
執行結果為
六、Nullable reference types 在 C# 8.0 reference type 也可以使用 Nullable 表示法了。
1 2 3 4 5 6 7 8 9 10 11 12 using System.Collections.Generic;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { List<int >? a; } } }
如果是 C# 8.0 之前的版本將不支援
七、Asynchronous streams 先假裝在操作 IO 如讀取或寫入檔案的時候, 使用常規同步的 IEnumerable 寫法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using System;using System.Threading;using System.Collections.Generic;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { foreach (var number in GenerateSequence ()) { Console.WriteLine(number); } } public static IEnumerable<int > GenerateSequence () { Random rdm = new Random(); for (int i = 0 ; i < 20 ; i++) { Thread.Sleep(rdm.Next(1 , 3 ) * 1000 ); yield return i; } } } }
現在在 C# 8.0 提供了 Asynchronous streams,使用非同步的 IAsyncEnumerable
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 using System;using System.Threading.Tasks;using System.Collections.Generic;namespace ConsoleApp1 { class Program { static async Task Main (string [] args ) { await foreach (var number in GenerateSequence ()) { Console.WriteLine(number); } } public static async IAsyncEnumerable<int > GenerateSequence () { Random rdm = new Random(); for (int i = 0 ; i < 20 ; i++) { await Task.Delay(rdm.Next(1 , 3 ) * 1000 ); yield return i; } } } }
要使用 Asynchronous streams 的寫法有三個重點: 1 、foreach 前,要加上 await。 2、改用 IAsyncEnumerable 介面。 3、使用 yield return。 小結:我覺得這範例不是非常清楚地表達,不過…就先這樣子吧…
八、Indices and ranges
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int [] sequence = new int [] { 0 , 1 , 2 , 3 , 4 }; Console.WriteLine($"sequence[0] is {sequence[0 ]} " ); Console.WriteLine($"sequence[^1] is {sequence[^1 ]} " ); Console.WriteLine(); foreach (var item in sequence[2. .]) { Console.WriteLine($"sequences[2..] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[..2 ]) { Console.WriteLine($"sequences[..2] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[^2. .]) { Console.WriteLine($"sequences[^2..] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[..^2 ]) { Console.WriteLine($"sequences[..^2] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[1. .4 ]) { Console.WriteLine($"sequences[1..4] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[^4. .4 ]) { Console.WriteLine($"sequences[^4..4] have {item} " ); } } } }
執行結果為 說明: sequence[n],其 n 的意思為索引,從 0 算起。 sequence[^n],其 ^n 的意思為個數,倒數第 n 個,從 1 算起。 sequence[n..],其意思為從第 n 個索引開始到最後的範圍,也包含 n。 sequence[..n],其意思為從開始到第 n 個索引的範圍,不包含 n。 sequence[^n..],其意思為從倒數第 n 個個數到最後的範圍,也包含 ^n。 sequence[..^n],其意思為從開始到倒數第 n 個個數的範圍,不包含 ^n。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int [] sequence = new int [] { 0 , 1 , 2 , 3 , 4 }; Index index = ^4 ; Range phrase = index..4 ; foreach (var item in sequence[phrase]) { Console.WriteLine($"sequences[^4..4] have {item} " ); } } } }
執行結果為 說明: 1、這「^」符號稱為 index from end operator。 2、這「..」符號稱為 range operator。 3、System.Index 型別用來表示一陣列的索引。 4、System.Range 型別用來表示一陣列的範圍。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int [] sequence = new int [] { 0 , 1 , 2 , 3 , 4 }; foreach (var item in sequence[0. .^0 ]) { Console.WriteLine($"sequences[0..^0] have {item} " ); } Console.WriteLine(); foreach (var item in sequence[..]) { Console.WriteLine($"sequences[..] have {item} " ); } } } }
執行結果為 說明: 1、「[0..^0]」意思為對一陣列裡的元素全部選取。 2、「[..]」也是為對一陣列裡的元素全部選取的意思。
九、Null-coalescing assignment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { int ? a = null ; a ??= 10 ; Console.WriteLine(a); a ??= 333 ; Console.WriteLine(a); } } }
執行結果為 說明: 如果左邊等於 null,則將右邊的值 assign 給左邊。
十、Enhancement of interpolated verbatim strings
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 using System;namespace ConsoleApp1 { class Program { static void Main (string [] args ) { var i1 = 10 ; var i2 = 5 ; Console.WriteLine($@"{i1} \{i2} " ); Console.WriteLine(@$"{i1} \{i2}" ); } } }
執行結果為 說明: 在 C# 8.0 之前,同時使用「$」和「@」時,「$」必須位於「@」的前方, 但在 C# 8.0 開始「@」在「$」則是不分順序了