Class類別觀念
- 類別(Class) 是一種用來描述物件(Object)特徵與行為的藍圖或模板。
- 物件則是依據類別建立的實例(Instance)。
換句話說,類別就像「設計圖」,而物件是根據設計圖打造出來的「實體」。
類別的主要要素:
- 欄位(Field):用來儲存資料或物件狀態的變數。
- 屬性(Property):為欄位提供封裝,並允許在存取時執行邏輯(如驗證或計算)。
- 方法(Method):定義物件的行為與功能,類似於函式,用於執行一個明確的動作或演算。
- 建構函式(Constructor):當物件被建立時,會自動被呼叫,用來初始化物件。
- 解構函式(Destructor)(比較少用) :物件被回收時自動執行的程式碼區塊,通常不常手動實作,在 .NET 中交由垃圾回收(Garbage Collector)處理。
簡單範例
以下是一個最簡單的類別範例:public class Person
{
// 欄位(Field)
private string name;
private int age;
// 屬性(Property)
public string Name
{
get { return name; }
set { name = value; }
}
public int Age
{
get { return age; }
set
{
if (value >= 0)
age = value;
}
}
// 建構函式(Constructor)
public Person(string name, int age)
{
this.name = name;
this.age = age;
}
// 方法(Method)
public void Introduce()
{
Console.WriteLine($"Hello, my name is {name}, and I'm {age} years old.");
}
}
public class Person
:使用public
表示此類別可以被其他程式碼存取。類別名稱為Person
。private string name;
、private int age;
:私有欄位,只能在此類別內被使用。- 屬性
Name
與Age
:用來封裝欄位,常用的好處是可以在get
與set
做額外的檢查或運算。 - 建構函式
Person(string name, int age)
:用來在建立物件時,初始化name
與age
。 - 方法
Introduce()
:類別定義的行為,這裡只是單純地印出訊息。
class Program
{
static void Main(string[] args)
{
// 使用建構函式建立 Person 物件
Person person1 = new Person("Alice", 25);
// 透過屬性存取與設定欄位
person1.Name = "Alice Chen";
person1.Age = 30;
// 呼叫方法
person1.Introduce();
}
}
輸出結果:
Hello, my name is Alice Chen, and I'm 30 years old.
C# 的存取修飾子
C# 類別宣告的成員變數或方法可以使用private、public、protected、internal、protected internal幾種修飾子指定成員的存取階級,其說明如下:
private
:成員變數或方法只能在類別本身呼叫或存取,如果沒有使用修飾子,預設是private
。public
:成員變數或方法是此類別建立物件對外的使用介面,任何程式碼都可存取。protected
:僅能在同一個類別或其子類別(繼承類別)中存取。internal
:在相同 C# 專案的程式檔案可以呼叫或存取,但不包含其它專案。protected internal
: 在相同 C# 專案的程式檔案可以呼叫或存取,也能和其它專案繼承此類別的子類別來呼叫和存取。
實務上,主要是使用private
、public
、protected
三種存取修飾子來控制類別成員的存取,例如,類別對外的使用介面宣告成public
,需要隱藏的資料或只供類別本身呼叫的方法(工具方法)則宣告成private
。
屬性的進階用法
唯讀屬性(Read-Only Property)
如果只有 get 存取器,沒有 set 存取器,則該屬性是唯讀的。
public class Person
{
private string name = "Unknown";
public string Name
{
get { return name; }
}
}
使用時只能讀取,不能寫入:
Person person = new Person();
Console.WriteLine(person.Name); // 可以讀取
person.Name = "Alice"; // 編譯錯誤,因為沒有 set 存取器
唯寫屬性(Write-Only Property)
如果只有 set
存取器,沒有 get
存取器,則該屬性是唯寫的。
public class Person
{
private string name;
public string Name
{
set { name = value; }
}
}
使用時只能寫入,不能讀取:
Person person = new Person();
person.Name = "Alice"; // 可以寫入
Console.WriteLine(person.Name); // 編譯錯誤,因為沒有 get 存取器
自動屬性(Automatic Property)
如果屬性的 get
和 set
存取器沒有額外的邏輯,可以使用自動屬性,編譯器會自行實作屬性的get
和set
程式區塊
public class Person
{
public string Name { get; set; } // 自動實作屬性
}
上面程式碼中,Name
屬性只有get
和set
,沒有程式碼區塊,C#編譯器會自動宣告private
欄位來實作屬性。
使用方式與普通屬性相同:
Person person = new Person();
person.Name = "Alice";
Console.WriteLine(person.Name);
屬性初始化
你可以在宣告屬性時直接初始化:
public class Person
{
public string Name { get; set; } = "Unknown";
}
最常見的情況下:
- 欄位通常標記為
private
。 - 提供
public
屬性或方法進行間接操作。
建構函式(Constructor)與多載(Overloading)
- 與類別同名,沒有回傳值。
- 物件建立時會自動被呼叫,可用來初始化欄位或執行必須的動作。
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
// 預設建構函式(沒有參數)
public Person()
{
Name = "Unknown";
Age = 0;
}
// 有參數的建構函式
public Person(string name, int age)
{
Name = name;
Age = age;
}
}
建構函式多載(Overloading)可以定義多個同名建構函式,只要參數數量或型別不同即可。呼叫哪個建構函式取決於傳入的參數形態。
Person p1 = new Person(); // 呼叫無參數建構函式
Person p2 = new Person("Bob", 28); // 呼叫有參數建構函式
繼承(Inheritance)
繼承讓我們可以基於現有的類別延伸出新的類別,使我們能夠重複使用並擴充程式碼。繼承的使用場景為is-a關係,例如,狗是一個動物。基本用法如下:
public class Animal
{
public string Name { get; set; }
public void Eat()
{
Console.WriteLine($"{Name} is eating.");
}
}
public class Dog : Animal
{
public void Bark()
{
Console.WriteLine($"{Name} is barking.");
}
}
Dog : Animal
表示Dog
類別繼承自Animal
類別,Dog
便擁有Animal
的屬性與方法(Name
和Eat()
)。- 可以在
Dog
內再新增專屬於Dog
的方法或屬性,例如Bark()
。
使用範例:
Dog dog = new Dog();
dog.Name = "Lucky";
dog.Eat(); // 繼承自 Animal
dog.Bark(); // Dog 自身方法
關於繼承的建構子呼叫,當衍生類別的物件被建立時,建構子的呼叫順序如下:
- 基類的建構子:先呼叫基類的建構子。
- 衍生類別的建構子:然後呼叫衍生類別的建構子。
如果基類有一個無參數的建構子(預設建構子),並且衍生類別沒有顯式呼叫基類的建構子,則編譯器會自動呼叫基類的無參數建構子。
public class Animal
{
public Animal()
{
Console.WriteLine("Animal constructor called.");
}
}
public class Dog : Animal
{
public Dog()
{
Console.WriteLine("Dog constructor called.");
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog();
}
}
輸出:
Animal constructor called.
Dog constructor called.
Dog
類別繼承自Animal
類別。- 當
Dog
的物件被建立時,會先呼叫Animal
的建構子,然後呼叫Dog
的建構子。
如果基類沒有無參數的建構子,或者你想呼叫基類的特定建構子,可以使用 base
關鍵字來顯式呼叫基類的建構子。
public class Animal
{
private string name;
public Animal(string name)
{
this.name = name;
Console.WriteLine($"Animal constructor called with name: {name}");
}
}
public class Dog : Animal
{
public Dog(string name) : base(name) // 顯式呼叫基類的建構子
{
Console.WriteLine("Dog constructor called.");
}
}
class Program
{
static void Main(string[] args)
{
Dog dog = new Dog("Buddy");
}
}
輸出:
Animal constructor called with name: Buddy
Dog constructor called.
多型
多型指的是相同的方法名稱,在不同類別中可以有不同實作。C# 透過關鍵字 virtual
、override
來實現方法的覆寫。
public class Animal
{
public string Name { get; set; }
// 將此方法標示為 virtual,使子類別可覆寫
public virtual void MakeSound()
{
Console.WriteLine("Some generic animal sound");
}
}
public class Dog : Animal
{
// 使用 override 覆寫父類別的 MakeSound 方法
public override void MakeSound()
{
Console.WriteLine("Woof!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
}
當你呼叫 MakeSound()
時,會根據物件實際型別去執行相對應的方法:
Animal myAnimal = new Animal();
myAnimal.MakeSound(); // 輸出:Some generic animal sound
Animal myDog = new Dog();
myDog.MakeSound(); // 輸出:Woof!
Animal myCat = new Cat();
myCat.MakeSound(); // 輸出:Meow!
你可以宣告一個基類類型的陣列,並在其中存放衍生類別的物件。當你呼叫陣列中物件的方法時,實際執行的是衍生類別的方法。
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Bark!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
}
class Program
{
static void Main(string[] args)
{
// 宣告基類類型的陣列
Animal[] animals = new Animal[3];
animals[0] = new Animal();
animals[1] = new Dog();
animals[2] = new Cat();
// 遍歷陣列,呼叫 MakeSound 方法
foreach (Animal animal in animals)
{
animal.MakeSound(); // 實際執行的是衍生類別的方法
}
}
}
輸出:
Animal sound
Bark!
Meow!
你也可以將基類作為函式的參數類型,並傳入衍生類別的物件。在函式中呼叫方法時,實際執行的是衍生類別的方法。
public class Animal
{
public virtual void MakeSound()
{
Console.WriteLine("Animal sound");
}
}
public class Dog : Animal
{
public override void MakeSound()
{
Console.WriteLine("Bark!");
}
}
public class Cat : Animal
{
public override void MakeSound()
{
Console.WriteLine("Meow!");
}
}
class Program
{
static void Main(string[] args)
{
Animal animal = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();
// 傳入不同的物件
MakeAnimalSound(animal); // 輸出:Animal sound
MakeAnimalSound(dog); // 輸出:Bark!
MakeAnimalSound(cat); // 輸出:Meow!
}
// 函式參數為基類類型
static void MakeAnimalSound(Animal animal)
{
animal.MakeSound(); // 實際執行的是衍生類別的方法
}
}
輸出:
Animal sound
Bark!
Meow!
另外,有一關鍵字: new
,new
關鍵字用於隱藏(hide)基類的成員,而不是覆寫它。
- 這意味著衍生類別的物件會呼叫衍生類別的方法,而不是基類的方法。
- 如果透過基類的參考來呼叫該方法,則會呼叫基類的版本。
public class Animal
{
public void MakeSound() { Console.WriteLine("Animal sound"); }
}
public class Dog : Animal
{
public new void MakeSound() { Console.WriteLine("Bark!"); }
}
使用時:
Animal animal = new Animal();
animal.MakeSound(); // 輸出:Animal sound
Dog dog = new Dog();
dog.MakeSound(); // 輸出:Bark!
Animal animalDog = new Dog();
animalDog.MakeSound(); // 輸出:Animal sound(因為隱藏了基類的方法)
當你在衍生類別中覆寫了基類的方法,但仍然想執行基類的邏輯時,可以在衍生類別的方法中使用 base.方法名稱()
來呼叫基類的版本。或者即使你使用 new
關鍵字隱藏了基類的成員,你仍然可以在衍生類別中使用 base
關鍵字來呼叫基類的版本。
使用 base
的時機:
- 擴展基類的功能:當你想在衍生類別中擴展基類的方法,而不是完全取代它時。
範例:
public class Vehicle
{
public virtual void Start()
{
Console.WriteLine("Vehicle is starting.");
}
}
public class Car : Vehicle
{
public override void Start()
{
base.Start(); // 呼叫基類的 Start 方法
Console.WriteLine("Car is ready to drive.");
}
}
class Program
{
static void Main(string[] args)
{
Car car = new Car();
car.Start();
}
}
輸出:
Vehicle is starting.
Car is ready to drive.
抽象類別(Abstract Class)
- 無法直接被實例化,只能被繼承。
- 可以包含抽象方法(只定義方法簽名、不包含方法實作)和一般已實作的方法。
- 適合用來定義核心邏輯與基本架構,讓子類別實作細節。
- 一個類別只能繼承一個抽象類別。
public abstract class Shape
{
public abstract double GetArea(); // 抽象方法,沒有方法體
}
public class Circle : Shape
{
private double radius;
public Circle(double radius)
{
this.radius = radius;
}
public override double GetArea()
{
return Math.PI * radius * radius;
}
}
介面(Interface)
- 只包含方法、屬性的宣告,沒有任何實作。
- 與抽象類別最大的不同是:介面不允許定義任何欄位,也不可包含具體方法。
- 類別可以同時實作多個介面,意即允許一個類別實現多個介面。
多重介面的使用場景
包括:
- 組合多種行為:例如一個類別可以同時實現
IFlyable
和ISwimmable
,表示它既能飛又能游泳。 - 不同的類別可以實現相同的介面。
- 介面讓類別之間的依賴更加鬆散,便於擴展和維護。
public interface IFlyable
{
void Fly();
}
public interface ISwimmable
{
void Swim();
}
public class Duck : IFlyable, ISwimmable
{
public void Fly()
{
Console.WriteLine("Duck is flying.");
}
public void Swim()
{
Console.WriteLine("Duck is swimming.");
}
}
Duck
類別實現了IFlyable
和ISwimmable
兩個介面。
多重介面的實現細節
- 必須實現所有介面成員:一個類別實現多個介面時,必須實現所有介面中定義的成員。
- 解決名稱衝突:如果多個介面中有相同名稱的成員,可以使用顯式介面實現來區分。
public interface ILogger1
{
void Log();
}
public interface ILogger2
{
void Log();
}
public class Logger : ILogger1, ILogger2
{
// 顯式實現 ILogger1 的 Log 方法
void ILogger1.Log()
{
Console.WriteLine("Logger1 is logging.");
}
// 顯式實現 ILogger2 的 Log 方法
void ILogger2.Log()
{
Console.WriteLine("Logger2 is logging.");
}
}
使用時:
Logger logger = new Logger();
((ILogger1)logger).Log(); // 輸出:Logger1 is logging.
((ILogger2)logger).Log(); // 輸出:Logger2 is logging.
靜態類別(Static Class)與靜態成員(Static Members)
靜態類別(Static Class)
- 無法被實例化。
- 所有成員都必須是靜態的。
- 常用於工具或輔助功能,例如
Math
類別。
靜態成員
- 靜態欄位、靜態屬性或方法,直接跟隨著類別本身存在,而不屬於任何個別物件。
- 直接用
類別名稱.成員
的方式呼叫。
public static class MathHelper
{
public static int Add(int a, int b)
{
return a + b;
}
}
// 使用方式
int sum = MathHelper.Add(3, 5); // 不需要建立 MathHelper 物件
好,以上就是我濃縮有關C#類別的觀念知識,雖然本來不想在一篇文章放太多內容,讀者可能沒辦法消化,不過我自己本身在學東西或查資料時,最討厭的就是內容分散,讓人很難有效連貫學習,成效自然不好,當然每個人習慣不一樣,我也不能下定論,總而言之,希望這篇對讀者在快速學習C#這門程式語言上,能夠產生幫助。
本頻道持續更新中(內容涵蓋前端程式設計入門、大學必備程式設計入門、電子系專業課程入門、數學微積分題解)如果身旁有相關科系的學生,不妨推薦一下喔~
相信這裡會是家教或線上課程之外,高中、大學生系統性綜合學習的好選擇。
最後感謝您的觀看!