C#8.0

 

一、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.06M,
    { State: "MN" } => salePrice * 0.75M,
    { State: "MI" } => salePrice * 0.05M,
    // other cases removed for brevity...
    _ => 0M
    };
    }

    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("與周董同班級"); // output: 與周董同班級
    }
    }

    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 開始「@」在「$」則是不分順序了