DAY 14 類別(三)

類別

上次講的類別都屬於參考型別,今天會介紹實質型別的類別,如果忘記型別差異的話,可以回去看第二天的文章。

結構 struct

結構包含建構函式、常數、欄位、方法、屬性、索引子、運算子、事件和巢狀型別,不過如果需要數個這類成員,您應該考慮以類別來取代類型。

結構的用法與 class 相似,但結構使用時,無法初始化屬性,在下方用 class 示範如何初始化屬性

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
class Program {
static void Main (string[] args) {
Member member = new Member (1, "Mio");
member.Print ();
Memberc memberc = new Memberc ();
memberc.Print ();
}
}
struct Member {
public Member (int id, string name) {
ID = id;
Name = name;
}
public int ID { get; }
public string Name { get; }
public void Print () {
Console.WriteLine ($"ID: {id}, Name: {name}");
}
}
class Memberc {
public Memberc () { }
public Memberc (int id, string name) {
ID = id;
Name = name;
}
public int ID { get; } = 1;
public string Name { get; } = "Mio";
public void Print () {
Console.WriteLine ($"ID: {ID}, Name: {Name}");
}
}

也就是當今天沒有傳入任何參數時,則會預設是 ID = 0, Name = Mio,隨便舉例而已。get 是存取子,還有一個是 set,如果只有 get 就是唯讀,只有 set 就是唯寫,詳細操作可以看下方連結。

結構往往是用在不可變的狀態下,像範例中的屬性成員是唯讀的,不能去修改。

列舉

包含一組稱為列舉程式清單之具名常數的不同類型

以資料庫連線狀態來舉例,假設連線狀態有未連接、嘗試連接與連接中三種狀態

如果不使用列舉,我們稱狀態為 0, 1, 2

1
2
3
4
5
6
7
8
9
10
11
12
13
int connectionState;
// ...
switch (connectionState) {
case 0:
// ...
break;
case 1:
// ...
break;
case 2:
// ...
break;
}

使用列舉後

1
2
3
4
5
6
7
8
9
10
11
12
13
ConnectionState connectionState;
// ...
switch (connectionState) {
case connectionState.Disconnected:
// ...
break;
case connectionState.Connecting:
// ...
break;
case connectionState.Connected:
// ...
break;
}

可讀性是不是差很多呢?兩者在運行時效能完全一樣,列舉的關鍵在於編譯時能宣告一組可以透過名稱來使用的整數值

1
2
3
4
5
enum ConnectionState {
Disconnected,
Connecting,
Connected
}

在使用時,需要加入列舉名稱前綴如ConnectionState.Connected,如果沒有任何預設值的話,列舉的數值從 0 開始排,也就是說

1
2
3
4
5
6
7
8
9
10
11
static void Main (string[] args) {
Console.WriteLine ($"Name = {MyEnum.A}, Value = {(int)MyEnum.A}");
Console.WriteLine ($"Name = {MyEnum.B}, Value = {(int)MyEnum.B}");
Console.WriteLine ($"Name = {MyEnum.C}, Value = {(int)MyEnum.C}");
Console.ReadKey ();
}
enum MyEnum {
A,
B,
C
}

但如果今天加入預設值後

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static void Main (string[] args) {
Console.WriteLine ($"Name = {MyEnum.A}, Value = {(int)MyEnum.A}");
Console.WriteLine ($"Name = {MyEnum.B}, Value = {(int)MyEnum.B}");
Console.WriteLine ($"Name = {MyEnum.C}, Value = {(int)MyEnum.C}");
Console.WriteLine ($"Name = {MyEnum.D}, Value = {(int)MyEnum.D}");
Console.WriteLine ($"Name = {MyEnum.E}, Value = {(int)MyEnum.E}");
Console.ReadKey ();
}
enum MyEnum {
A,
B = 8,
C = 6,
D,
E
}

可以自己玩玩看,列舉不是只有 int 而已,也可以宣告成 short 如

1
2
3
4
5
6
7
enum MyEnum : short {
A,
B = 8,
C = 6,
D,
E
}

除了像是這種分開的狀態外,也會使用在可疊加狀態的,像是一個標誌來使用,以 System.IO.FileAttributes 舉例

1
2
3
4
5
6
7
8
9
10
11
[Flags]
public enum FileAttributes
{
ReadOnly = 1, // 1 << 0 0000001
Hidden = 2, // 1 << 1 0000010
System = 4, // 1 << 2 0000100
Directory = 8,// 1 << 3 0001000
/*
* 以下省略
*/
}

題外話:在設定初始值時,也可以像右邊的移位運算子(<<),會比較整齊。

這種列舉可以想成是二進位的加減,當你今天需要多重狀態時,就可以使用 OR 運算子來疊加,假設我今天需要前面兩種狀態的話,如

1
2
3
4
5
static void Main (string[] args) {
System.IO.FileAttributes FA = System.IO.FileAttributes.Hidden | System.IO.FileAttributes.ReadOnly;
Console.WriteLine ($"Name = {FA}, Value = {(int)FA}");
Console.ReadKey ();
}

可以看到名稱很完整的印出了每個狀態,因為在使用這個列舉時有加一個 Attribute(屬性) [Flags],可以自己撰寫一個以二進位方式的列舉,加入這個屬性來看差異。

題外話:雖然中文都翻屬性,但我覺得有時候講起來很容易混淆,不知道有沒有路過的大大可以提通我更好的中文字詞。

一些小結論

今天把實質型別的類別做了個大概,本來在實務上是很少使用結構的,都是使用 class 類別來取代,所以在說類別時,往往就是指 class 了。而列舉就比較容易遇到,因為可以讓程式碼在讀的時候比較好懂。

感謝閱讀。

參考連結
struct (C# 參考) | Microsoft Docs
get (C# 參考) | Microsoft Docs
enum 關鍵字 (C# 參考) | Microsoft Docs