不學 JAVA 換學 C# 之覺得心累 - L1:ch10 類別和物件 (二)

AdSense

前言

不學 JAVA 換學 C# 之覺得心累 - L1:ch9 類別和物件 (一)裡面介紹了類別和物件如何建立、this 代表的意義和用處、非靜態欄位和靜態欄位以及欄位的常數 constreadonly等。

封裝與存取修飾詞

C# 提供多種存取修飾詞來控制類別成員的可見性存取範圍。這是用來保護一些,不想或不用對外暴露的資料和演算法邏輯。

  • public:在程式的任何地方被存取。
  • private:只能在同一個類別內存取。命名規則為,建議在變數前加下底線 _
  • protected:只能在同一個類別內或其子類別內存取。
  • internal:只能在同一個組件 (assembly) 內存取。

封裝欄位 Field 與定義屬性 Property

通常實作上,我們會不希望欄位 (Field) 可以被直接修改,原因是:

  1. 封裝性 (Encapsulation):保護資料,不讓外部直接修改
    當欄位是 public,任何外部的程式碼都能直接修改它的值,這可能會導致錯誤或不安全的資料操作。
  2. 控制存取權限 (Read-Only / Write-Only 權限)
    有些時候,我們希望欄位只能被讀取,不能被修改,或只能被內部設定,不能被外部改變。
  3. 可以額外執行邏輯 (如:記錄變更日誌、計算等)
  4. 易於維護,未來可以修改實作方式

早期,我們會希望透過 GetXXX or SetXXX 方法來取得或修改資料,範例:

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
// 類別
public class Dog
{
// 欄位 Field
private string _color; // 僅類別內部可以使用

public string GetColor()
{
return _color;
}

public void SetColor(string color)
{
this._color = color;
}
}

// 程式進入點
class Program
{
public static void Main()
{
Dog lucky = new Dog();
lucky.SetColor("white");
string colorOfLucky = lucky.GetColor();
}
}

屬性 Property 的 getset

但是如果有很多欄位,而且大部分都沒有額外的邏輯,就會變成寫很多類似的程式碼,這樣很沒有效率,中期就演變出屬性 Property,和欄位 Field 一樣名稱,差別僅開頭大小寫不同,然後可以這樣寫:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 類別
public class Dog
{
// 欄位 Field (開頭小寫)
private string _color; // 僅類別內部可以使用

// 屬性 Property (開頭大寫)
public string Color // 記得後面不用(),因為不是建構式,也不是 Method
{
get { return _color; }
set { this._color = value; } // 如果需要額外的邏輯,寫在 set 的 {} 內
}
}

// 程式進入點
class Program
{
public static void Main()
{
Dog lucky = new Dog();
lucky.Color = "black"; // 透過屬性設定值
string colorOfLucky = lucky.Color; // 透過屬性取得值
}
}

再一個範例

一間電影院裏面同時上映好幾部電影,想要紀錄電影的資料 title, director, Rating 和總共幾部電影 count

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
using System;

namespace GetterSetter;

public class Movie
{
public string title;
public string director;
private string _rating; // 因為不允許分級制度被改成奇怪的字串,我們使用 private
public static int count = 0;

public Movie(string title, string director, string rating)
{
this.title = title;
this.director = director;
this._rating = rating;
count++;
}

public string Rating // 記得後面不用(),因為不是建構式,也不是 Method
{
// 用 get 和 set 取得或重新設置內容
get { return _rating; }
set
{
if (value == "G" || value == "PG" || value == "PG-13" || value == "R" || value == "NR")
{
_rating = value;
}
else
{
_rating = "NR";
}
}
}

public int GetCount()
{
return count;
}
}

但是要寫的東西還是有點多,所以現在主要是使用自動實作屬性 (Automatically Implemented Properties)

自動實作屬性 (Automatically Implemented Properties)

是一種簡化屬性宣告的方式,讓你無需手動定義欄位 (Field) 來存取屬性背後的資料。

它是語法糖的一種,語法糖是一種讓程式設計更加簡潔易讀的工具,不改變功能但是提升了可讀性與寫程式的效率。

語法

只需使用 getset 存取器,不需要自訂邏輯,編譯器會自動生成隱藏的欄位。

1
2
3
4
5
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}

對比:

1
2
3
4
5
6
7
8
9
public string Name { get; set; } // 語法糖:自動實作屬性

// 不用語法糖:用 get 和 set
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}

優點

  • 簡化程式碼:減少樣板程式碼 (boilerplate code)。
  • 提高可讀性:讓程式碼更清晰易懂。

缺點

如果需要增加邏輯,就無法使用自動屬性,必須改用完整屬性宣告,例如:

1
2
3
4
5
6
private int _age;
public int Age
{
get { return _age; }
set { if (value > 0) _age = value; }
}

參考資料:
自動實作的屬性 (C# 程式設計手冊)

物件初始化器 (Object Initializer)

物件初始化器允許在建立物件時直接設定可存取的屬性值 (Property),無需建立很多不同的建構式,無須呼叫多個建構式或設定方法,從而簡化物件的初始化過程。但是物件初始化器還是基於建構式,若是相對應的建構式不對,會報錯。

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
public class Person
{
public string Name { get; set; }
public int Age { get; set; }

public Person ()
{}
}

// 以前的寫法:使用建構式並設定屬性值
Person student = new Person();
student.Name = "Jenifer";
student.Age = 10;

// 使用物件初始化器,直接設定屬性值
// 寫法一:有括弧 (基於 line 6 的建構式)
Person student2 = new Person()
{
Name = "Jenifer",
Age = 10
};
// 寫法二:沒括弧 (基於 line 6 的建構式)
Person student3 = new Person
{
Name = "Jenifer",
Age = 10
};

MS 的範例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Cat
{
public int Age { get; set; }
public string? Name { get; set; }

public Cat()
{}
public Cat(string name)
{
this.Name = name;
}
}

Cat cat1 = new Cat { Age = 10, Name = "Fluffy" }; // 基於 line 6 的建構式
Cat cat2 = new Cat("Fluffy"){ Age = 10 }; // 基於 line 8 的建構式

參考資料:
物件和集合初始設定式 (C# 程式設計手冊)

物件的陣列

先宣告一個物件陣列,宣告時即保留記憶體位址以便將來儲存 reference,並且都先初始化為 null,但是還沒有實例化物件。利用 for 迴圈實例化物件。

以下的例子直接用上面 Person 類別:

1
2
3
4
5
Person[] people = new Person[5]; // 先保留五個位址
for (int i = 0; i < people.Length; i++)
{
people[i] = new Person { Name = "John", Age = 100 };
}