第 1 章:現代環境與基礎

本章會先介紹基本的開發環境,再探討 .NET 執行環境與 C# 型別系統的核心運作原理。這裡不談簡單的 if/else 和迴圈,而是直接切入後續學習 C# 進階特性所需的基礎觀念。
1.1 C# 的設計哲學
C# 是物件導向(object-oriented)語言,強調封裝、繼承和多型。但從 C# 3.0 開始,它大量引入了函數式程式設計(functional programming)的精華:
| 函數式特性 | C# 中的實作 |
|---|---|
| 函式可作為值傳遞 | 委派(delegate)與 lambda 運算式 |
| 宣告式資料處理 | LINQ 查詢運算式 |
| 不可變性 | record、readonly struct、init 存取子 |
| Pattern matching | switch 運算式、is 運算子 |
這就是 OOP + FP 混合設計:C# 兼具了物件導向的組織力與函數式的表達力。本書後續介紹的 record、pattern matching 和 LINQ,都源自這個哲學。
名詞釋疑
在程式語言中,expression(運算式)通常指的是「可以被計算(evaluate)並回傳一個值的程式碼片段」。另一方面,statement(陳述式)則是執行一個動作,通常不直接傳回值。
接著來看它的型別系統。
型別安全:編譯器是你的第一道防線
C# 是一門強型別(strongly typed)語言,型別規則非常嚴格:
- 靜態型別(static typing):變數的型別在編譯時期就已確定,編譯器會在程式執行前檢查型別錯誤。
- 強型別(strong typing):不允許隱含的危險型別轉換。例如,你不能把
double直接賦值給int,必須明確轉型。
這樣不僅能提早發現錯誤,也能降低執行時期出錯的機率。現代 C# 的許多功能(如 Nullable Reference Types)都在強化這張安全網。
下面這段程式碼示範了 C# 如何阻止可能遺失資訊的隱含轉型:
在這個例子裡,編譯器不是禁止你轉型,而是要求你把「可能遺失資訊」這件事寫清楚。這種設計讓危險操作不會在程式碼裡悄悄發生。
也就是說:讓編譯器幫你減少潛在 bugs。從型別安全、null 安全、pattern matching 到 record 的值相等性,每一項功能都在減少執行時期的意外。
1.2 快速上手
本節會建立一個最簡單的 .NET 專案,並觀察它如何編譯與執行。我們先使用命令列介面(CLI),藉此理解 .NET 專案結構與建置流程(build pipeline)。
必備工具
在開始之前,請先安裝以下工具:
- .NET 10 SDK(或更新版本):開發 .NET 應用程式必備。
- 開發工具(IDE、編輯器),選擇你覺得合用的:
- Visual Studio 2026 Community:Windows 平台最強大的 IDE,適合大型專案與企業級開發。
- Visual Studio Code:跨平台(Windows、macOS、Linux)、輕量且生態系豐富的編輯器,適合喜歡快速編輯與自訂 workflow 的開發者。
- JetBrains Rider:跨平台(Windows、macOS、Linux)的整合開發工具,非商業用途可免費使用。
你的第一個 .NET 專案
請暫且忘掉 File > New Project 這類滑鼠操作,打開終端機(底下指令以 Windows 環境為例),按照以下步驟操作:
Step 1:建立 Console 應用程式
dotnet new console --name HelloCSharp這會以預設的 Console 專案範本建立一個名為「HelloCSharp」的 C# 專案,專案裡面會有一個預設的主程式,檔案名稱是 Program.cs,裡面只有一行程式碼(註解不算):
// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");Step 2:進入專案目錄
cd HelloCSharpStep 3:執行程式
dotnet run當你輸入 dotnet run 時,.NET SDK 會自動還原套件(restore)、編譯(build),然後執行應用程式。接著,你會看到終端機輸出:
Hello, World!
雖然你剛才實際輸入的 .NET CLI 命令只有 dotnet new 和 dotnet run(cd 不算),但整個流程包含三個階段:
dotnet new:根據範本產生專案檔(.csproj)和程式檔(Program.cs)。dotnet build:將 C# 原始碼編譯成中介語言(IL)並打包成.dll或.exe。dotnet run:啟動 .NET Runtime(Common Language Runtime,CLR)執行編譯好的程式。

什麼是 IL(Intermediate Language)?
C# 程式碼不會直接編譯成機器碼,而是先編譯成一種稱為 IL(Intermediate Language,中介語言)的中間格式。IL 是一種與平台無關的指令集,讓同一份編譯後的程式可以在 Windows、macOS、Linux 等不同作業系統上執行。當程式執行時,.NET Runtime(CLR)會將 IL 即時編譯(JIT, Just-In-Time compilation)成該作業系統的機器碼。詳細資訊請參閱微軟官方文件。
如果你想試試用整合開發環境(IDE)來編寫和執行剛才的範例程式,可參考微軟的線上教學文件:使用 Visual Studio 建立 .NET 控制台應用程式。
Note
現代 C# 專案通常啟用「頂層語句(top-level statements)」,這就是為什麼你在
Program.cs中看不到舊版 C# 的寫法,如class Program或static void Main,而只有簡潔的一行Console.WriteLine("Hello, World!");。
1.3 .NET 執行環境架構
當你輸入 dotnet run 指令,看到程式跑起來的那一刻,背後其實是一整套分工精細的系統在協同運作。.NET 平台採用分層設計,兼顧高階框架的便利與底層效能的掌控。
具體來說,.NET 執行環境可拆解為三個核心層次:
| 層次 | 名稱 | 功能 |
|---|---|---|
| 最上層 | Application Layer | 應用程式框架(ASP.NET、MAUI、WinUI 等) |
| 中間層 | BCL | 基礎類別庫(集合、I/O、網路、加密等) |
| 最底層 | CLR | 通用語言執行環境(記憶體管理、JIT 編譯、例外處理) |

CLR(Common Language Runtime) 是整個 .NET 的心臟,負責:
- 將 IL 編譯成機器碼(JIT 編譯)
- 自動記憶體管理(垃圾回收)
- 型別安全檢查
- 例外處理
BCL(Base Class Library) 提供開發者日常需要的基礎功能:
- 集合(
List<T>、Dictionary<TKey,TValue>) - 字串處理、正規運算式(regular expressions)
- 檔案 I/O、網路
- 非同步程式設計(
async/await)
Application Layer 則是針對特定應用場景的框架:
- ASP.NET Core:Web 應用程式與 API
- MAUI:跨平台行動裝置與桌面應用
- WinUI / WPF:Windows 桌面應用
理解這幾個層次後,就比較容易看懂同一份 C# 程式碼為什麼能在不同環境中執行。
跨平台支援
現代 .NET(從 .NET 5 開始)支援多種平台:
| 執行平台 | 支援的應用類型 |
|---|---|
| Windows | Console、Web、桌面(WPF/WinUI)、服務 |
| macOS | Console、Web、桌面(Mac Catalyst) |
| Linux | Console、Web、服務 |
| iOS / Android | 行動應用(透過 MAUI) |
| 瀏覽器 | WebAssembly(透過 Blazor) |
這意味著你用 C# 寫的程式碼,只要不依賴特定平台的 API,就可以在這些平台上執行。
從執行環境往下看,下一個會直接影響程式行為與效能的基礎,就是記憶體如何配置。
1.4 堆疊與堆積
要寫出高效能且少 bug 的程式,得先認識記憶體配置。最基礎的兩個概念是 堆疊(stack) 和 堆積(heap)。
為了方便理解,我們可以這樣想像:
- Stack(堆疊)像是你的辦公桌:
- 空間有限,但存取速度極快(伸手就拿得到)。
- 用來處理當下正在進行的工作(函式呼叫、區域變數)。
- 當工作結束(函式執行完畢),桌上的東西就會立刻被清空,準備處理下一件工作。
- Heap(堆積)像是公司的倉庫:
- 空間很大,但存取速度較慢(要走去倉庫找)。
- 用來存放長期保存的資料或大型物件。
- 當你不再需要某個物品時,不會立刻消失,而是等待清潔人員(garbage collector)在適當時機回收清理。

堆疊和堆積都是電腦當中的記憶體區塊,只是按照用途和特性給予不同的名稱而已。以下分別說明。
堆疊(stack)
- 特性:
- 極快:配置與釋放只需移動指標。
- 確定性:變數離開作用域(scope)即自動釋放。
- 空間小:通常只有 1~4 MB(視平台與設定而定),用盡會導致
StackOverflowException。最常見的原因是無限遞迴(infinite recursion)或遞迴層次過深。
- 存放內容:
- 方法呼叫的執行脈絡(參數、回傳位址等)。
- 某些與目前呼叫相關的區域資料;實際配置位置也可能受 JIT 最佳化影響。
堆積(heap)
- 特性:
- 成本較高:配置與釋放的成本均高於 stack;釋放依賴垃圾回收器(garbage collector, GC)不定期批次回收,而非每次物件不再被使用時立刻清理——這是為了避免頻繁清理帶來的效能負擔。
- 靈活:空間大,適合存放生命週期較長或大小不定的資料。
- 存放內容:
- 較大或生命週期較長的資料、物件(objects)與集合(collections)等。
1.5 實值型別與參考型別
前面提過,C# 程式執行於 CLR(Common Language Runtime) 之上。CLR 的任務很多,包括管理記憶體、處理例外、提供安全機制等等。不過,你不需要立刻了解 CLR 的內部運作和所有功能,只要先知道它肩負以下兩個重要工作:
- 管理型別資訊
- 配置與回收記憶體(垃圾回收)
在記憶體配置的部分,CLR 會根據型別特性、使用情境,以及執行時期最佳化來決定資料的實際配置方式。實值型別(value types) 和 參考型別(reference types) 是理解這件事的重要切入點,但它們並不是判斷資料一定落在 stack 或 heap 的唯一依據。

實值型別(Value Types)
實值型別有這些:
- 所有數值型別(
int、double、byte、decimal…) bool、charenum(列舉)struct(結構),包含DateTime、Guid、Span<T>
Note:
Span<T>是一個 ref struct。這是一種 stack-only 的特殊 struct:Span<T>這個值本身不能逃逸到 heap,因此不能被裝箱(box)、也不能作為class的欄位。不過,它所表示的資料則可能在 stack 上,也可能在 heap 上。本書〈第 13 章:高效能記憶體操作〉的 13.2 節有更詳細的介紹。
以下是它們的行為特性:
- 直接包含資料。
- 指派給另一個變數時,會複製整個值(copy by value)。
你可以把這想像成影印文件:當你把文件 A 影印一份給同事(變數賦值),同事在影本上塗寫(修改變數),並不會影響你原本的那份文件。
以下範例展示了實值型別的特性,即指派時會複製值本身:
第 3 行修改 b 的值並不會改變 a 的值,因為 int 是實值型別。
原始碼: DemoValueTypes
參考型別(Reference Types)
以下都是參考型別:
class(包含string,object, 陣列如int[])interfacedelegate(第 8 章會介紹委派與 Lambda,並在第 9 章介紹事件)record(預設為參考型別,詳見本書第 4 章)
Note
string雖然是參考型別,但因為它是不可變的(immutable),所以實際使用時,會呈現類似實值型別的效果。不過在型別分類上,它仍然屬於參考型別。
行為特性:
- 儲存參考(reference),你可以把它想成用來找到 heap 上物件的位置資訊。
- 指派給變數時,只複製參考,兩個變數可指向同一個物件。
這好比分享雲端文件連結:你把文件的連結傳給同事(變數賦值),你們兩個人手上拿的都只是連結(reference),但連結到的都是同一份雲端文件(object)。當同事透過連結修改了文件內容,你打開連結時也會看到修改後的結果。

換成參考型別時,指派的效果就不同了。以下範例中,user2 取得的是同一個物件的參考:
因此,透過 user2 修改 Name 時,user1 看到的也是同一個物件被修改後的狀態。
原始碼: DemoReferenceTypes
Ask AI:那些容易搞混的型別
很多 bugs 都源自對型別分類的誤解(面試考題也常問)。你可以把這段提示詞丟給 AI 測試觀念:
Prompt
在 C# 中,
int[](整數陣列)是 value type 還是 reference type?如果是 reference type,但它裡面裝的又是int(value type),那這些int是存在 stack 還是 heap?請解釋底層記憶體配置。
依你使用的 AI 工具而定,每次得到的答案可能有些差異。以下是 AI 回答的參考範例(經過簡化):
AI 回答
int[]本身是陣列,屬於 Reference Type。因此,整個陣列物件(包含它裡面的所有int元素)都儲存在 Heap 上,即使int本身是 Value Type。
陣列的記憶體配置:元素型別的差異
許多開發者以為陣列就是「連續的記憶體空間」,但這個觀念只對了一半。
事實上,陣列的底層記憶體配置方式,會因為元素是實值型別還是參考型別而有顯著差異。這個差異會影響程式的執行效能(CPU cache 命中率)與垃圾回收(GC)的負擔。接著從記憶體配置的觀點來看兩者有什麼不同。
Value Type 元素陣列
以底下的範例來說:
在 heap 上,CLR 會配置一塊連續的記憶體空間,三個 long 值直接儲存在陣列中。具體來說:
- 陣列結構:包含陣列長度等標頭資訊。
- 內容:直接存放數值本身(如
12345),緊密排列。 - 結果:一次記憶體配置,資料集中,效率高。
打個比方,就像是健身房的一整排置物櫃:你打開第 0 號櫃子,裡面直接放著你的東西(數值);打開第 1 號櫃子,裡面也直接放著東西。存取非常快速且直接。
Reference Type 元素陣列
接著看元素型別是 StringBuilder 的陣列:
在這種情況下,陣列中不是直接存放物件本身,而是存放指向物件的參考。具體來說:
- 陣列結構:包含陣列長度等標頭資訊。
- 內容:存放的是指向
StringBuilder物件的參考。 - 實際物件:分散放在堆積(heap)裡。
- 結果:多次記憶體配置(陣列一次 + 每個物件各一次),資料非連續,存取成本較高。
如同一排信箱:打開第 0 號信箱,裡面只有一張紙條(參考),上面寫著「你的包裹在倉庫 B 區 3 號架」。你必須再跑去倉庫(heap 的其他位置)才能真正拿到包裹(物件)。如果每個信箱裡的紙條都指向倉庫的不同角落,你就得跑來跑去,這當然比較慢。
這帶來兩個重要影響:
- 記憶體區域性(locality):value type 陣列的元素在記憶體中是連續存放,故 CPU 快取命中率高,存取效能更好。
- GC 壓力:reference type 陣列會產生多個散落物件,增加垃圾回收的負擔。

本節重點整理:
- 無論是實值型別還是參考型別,資料最終都必須有實際的儲存位置。
- 方法呼叫相關資料與許多區域變數常由 stack frame 或暫存器承載,但這屬於 runtime / JIT 的實作細節。
- 實值型別的重點是「直接包含資料」;它可能出現在區域變數、物件欄位、或陣列元素中,不一定只在堆疊(stack)上。
- 參考型別變數的值是一個 reference;該 reference 所指向的物件通常配置於堆積(heap)上。
1.6 Boxing 與 Unboxing
Boxing 和 unboxing 是為了讓實值型別(value types)也能當作物件處理的機制,但它有額外的效能成本。
Boxing(裝箱)
所謂的 boxing(裝箱)就是把一個 value type 轉換為 object(reference type)的過程。
就好像你買了一顆蘋果(value type),原本是直接以「蘋果本體」來使用;但如果現在要把它當成一件可統一管理的「物件」送進倉庫(heap),你就必須把它裝進一個箱子(boxing),並且貼上標籤。這個「找箱子」和「打包」的過程,就是額外的成本。
Boxing 過程發生了什麼事:CLR 會在 heap 上配置一塊記憶體,把 value type 的值複製進去。這個步驟是必要的,因為 object 是參考型別,需要在 heap 上有一個位址;而 int 等值型別本身沒有 heap 位址,所以必須先把它包裝成一個 heap 上的物件。
先看一個最簡單的裝箱範例:
這裡的 o 是一個參考,指向 heap 上新建立的 boxed int 物件。
Unboxing(拆箱)
跟 boxing(裝箱)相反,unboxing(拆箱)指的是將 object 轉回 value type 的過程。
Unboxing 實際做的事情:檢查 object 裡面是否真的裝了該型別的值,如果是,便將裡面的值取出並複製到目標位置。
拆箱時,必須明確指定要取回的實值型別:
int j = (int)o; // Unboxing如果 o 裡面裝的不是 int,這行程式會在執行時期丟出例外。

為什麼要在意?
Boxing 有兩個成本:
- CPU 計算:複製資料與記憶體配置。
- GC 壓力:Boxing 會產生額外的 heap 物件,增加垃圾回收的工作量。
隱性 boxing 陷阱常見於舊式集合(如 ArrayList)或格式化字串。ArrayList 因為將所有元素存為 object 型別,所以加入值型別時都會自動觸發裝箱。以下是一個常見的例子:
原始碼: DemoBoxingUnboxing
要提醒的是,「字串插補是否會造成 boxing」取決於編譯器版本與具體寫法;在現代 C#/.NET 中,多數常見情境(例如插補 int)通常不會再退回到 string.Format(object, ...) 那種會 boxing 的路徑,但仍可能產生字串配置(allocation)。
如果你遇到的是「大量格式化輸出」的熱點(hot path),且要求高效能的場景,更可靠的做法通常是:
- 盡量避免走到以
object參數為主的 API(例如某些params object[]形式)。 - 善用可寫入緩衝區的格式化 API(例如搭配
Span<char>的TryFormat),以減少暫時字串配置(詳見第 13 章)。 - 如果是集合類型,優先採用 泛型(generics) 集合,例如用
List<int>取代ArrayList。
- 字串插補(string interpolation)會在本書第 2 章介紹(第 2.7 節)。
- 泛型(generics)在本書第 7 章。
1.7 物件與陣列的複製
複製物件或陣列時,得先搞懂淺層複製(shallow copy)和深層複製(deep copy)是怎麼運作的,因為 C# 沒有內建的深層複製機制。這個主題橫跨實值型別與參考型別,是記憶體管理的重要概念。
物件的淺層複製與深層複製
淺層複製只複製物件本身,裡面的參考型別欄位還是指向同一個物件:
class Team
{
public string Name { get; set; } = string.Empty;
public List<string> Members { get; set; } = new();
// 使用 object.MemberwiseClone() 逐一複製成員(淺層複製)
public Team ShallowCopy() => (Team)MemberwiseClone();
}
var team1 = new Team
{
Name = "Dev",
Members = new List<string> { "Alice", "Bob" }
};
var team2 = team1.ShallowCopy(); // 淺層複製
team2.Name = "QA"; // team2 有自己的 Name 副本
team2.Members.Add("Charlie"); // 但 Members 仍指向同一個 List!
Console.WriteLine(team1.Members.Count); // 輸出 3(受影響!)此範例展示了淺層複製可能造成的潛在 bug:內層的參考物件只是複製參考,因此對 team2.Members 串列加入新元素時,等同於改動 team1.Members 串列。
原始碼: DemoShallowCopy
.NET 早期版本提供了 ICloneable 介面作為複製物件的標準做法:當類別需要提供複製操作時,只要實作該介面的 Clone() 方法即可。但此設計有個明顯缺點:Clone() 方法本身的語意不明確,它沒有明確規定是「淺層複製」還是「深層複製」。於是,當你呼叫一個第三方程式庫的 Clone(),往往需要去查文件來確認它到底是執行淺層複製,還是複製了整個物件樹。
因此,實務上通常不建議把 ICloneable 當成公開 API 的複製契約;如果物件要提供複製功能,較好的做法是在類別裡面定義名稱明確的方法,例如 ShallowCopy 或 DeepCopy,就如前面範例所展示的。
Note
其他複製物件的方法還有複製建構式(copy constructor),以及使用
record型別搭配with運算式來做 non-destructive mutation(詳見本書第 4 章)。請注意:with預設仍是淺層複製;如果成員裡面有可變的巢狀參考型別,仍然可能共用同一份內部物件。
如果要提供深層複製,就必須明確決定哪些成員需要建立新實例。以下範例示範的是以複製建構式(copy constructor)來實作深層複製。
class Team
{
public string Name { get; set; } = string.Empty;
public List<string> Members { get; set; } = new();
public Team() { }
public Team(Team original)
{
// string 是 immutable,可直接複製參考。
Name = original.Name;
// 建立新的 List,避免與原物件共用同一個 Members 集合。
Members = new List<string>(original.Members);
}
}
var team1 = new Team
{
Name = "Dev",
Members = new List<string> { "Alice", "Bob" }
};
var team2 = new Team(team1); // 使用複製建構式來建立物件的副本
team2.Members.Add("Charlie");
Console.WriteLine(team1.Members.Count); // 輸出 2(team1 不受影響)其中的這行程式碼會建立新的 List,避免與原物件共用同一個 Members 集合:
這裡由於元素是不可變的 string,故只需複製元素參考,不必另外複製字串物件。
在這個範例中:
Name屬性直接沿用原本的string參考,但不會造成問題。為什麼?因為string雖然是參考型別,但它是不可變(immutable)的——雖然寫法上看起來字串是可修改的,但其實任何對字串的修改操作(如Replace、ToUpper)都會產生一個新的字串物件。相對地,Members是List<string>,屬於可變(mutable)的集合,必須建立新實例才能避免共用同一份資料。- 複製建構式(copy constructor)特別適合需要明確控制複製邏輯的類別。雖然名稱不像
ShallowCopy或DeepCopy那樣明確,但它的好處是強型別(輸入參數和回傳值都是),而且是現代 .NET 實作物件複製的常見作法。
原始碼: DemoDeepCopy
請注意,new List<string>(original.Members) 這種寫法會建立一個新的串列容器。由於串列中的元素是不可變的 string,這樣就已足以達到此範例所需的隔離效果。如果串列包含的是可變的參考型別,例如 List<Person>,那麼 new List<Person>(original.Members) 其實只複製了串列容器本身,容器裡面的 Person 物件參考仍然指向原本的物件。在這種情況下,你必須逐一複製每個 Person 物件,才能達到真正的完全隔離。
總之,記住這個原則:如果物件內有巢狀的、可變的(mutable)參考型別(例如 Team 內有 Manager 物件,Manager 內又有其他物件),就需要遞迴地為每一層建立新實例。實務上,可使用序列化/反序列化(例如 System.Text.Json)或其他第三方函式庫來自動處理深層複製。
Note
有關「不可變」(immutable)的相關議題,詳見〈第 4 章:不可變設計〉。
以下整理剛才介紹的幾種物件複製方法:
- 實作
ICloneable.Clone():不建議。缺點:語意不明確,不是強型別(回傳object)。 - 提供複製建構式:可明確控制複製邏輯,且具強型別,是普遍接受的實務做法。缺點是成員數量眾多時,程式碼較繁瑣。
record型別搭配with:很適合不可變資料與 non-destructive mutation,但它不是通用的深層複製機制。(第 4 章)- 序列化:最簡便,但效能可能較慢、需要注意私有成員是否遺漏。
Ask AI
我想知道在 .NET 中的這幾種複製物件的方法有何優缺點:ICloneable、Record (with)、複製建構式、序列化。請整理一張比較表。
然後,請用 .NET 內建的
System.Text.Json實作物件的深層複製。
陣列的淺層複製與深層複製
陣列複製的原理與物件相同:Array.Clone() 執行的是淺層複製。
如果陣列中的元素型別本身不含可變的參考狀態,例如 int 或 string,那麼淺層複製通常已經足夠。下面這個範例複製的是 int[]:
不過要注意,這裡的前提是元素本身像 int 這樣直接就是值本體。如果陣列元素是某個 struct,而該 struct 內部又含有參考型別欄位,那麼 Array.Clone() 仍然只會淺層複製那些內部參考,不會自動幫你做到完全隔離。
如果陣列元素是 reference type,則必須留意:
輸出結果是 A!。Array.Clone() 雖然建立了一個新的陣列物件,但陣列內的元素仍然指向與 original 相同的 StringBuilder 物件,所以修改 copy[0] 的內容也會影響 original[0]。
若要實現深層複製,必須逐一複製每個元素:
var original = new StringBuilder[]
{
new StringBuilder("A"),
new StringBuilder("B")
};
var deepCopy = new StringBuilder[original.Length];
for (int i = 0; i < original.Length; i++)
{
deepCopy[i] = new StringBuilder(original[i].ToString());
}
deepCopy[0].Append("!");
Console.WriteLine(original[0]); // 輸出 "A"(不受影響)這次輸出結果是 A,顯示 deepCopy 跟 original 是兩個不同的陣列。
原始碼: DemoDeepCopyArray
本章重點回顧
- C# 設計哲學:OOP + FP 混合,強調型別安全,讓編譯器幫你避免潛在 bug。
- 執行機制:原始碼編譯為中介語言代碼(IL code),執行時由 CLR 透過 JIT 編譯為機器碼;CLR 亦負責垃圾回收(GC)。
- Stack 與 Heap:Stack 存取快但空間有限,常用於方法呼叫脈絡與部分區域資料;heap 空間大,常見於物件實體,但實際配置仍會受 runtime / JIT 影響。
- Value types vs Reference types:前者直接包含資料,後者儲存物件的參考(reference);你可以把「參考」理解成用來找到物件的位置資訊。
- 陣列記憶體特性:Value type 陣列在記憶體中是連續的(效能佳);Reference type 陣列則是儲存參考(可能增加 GC 壓力)。
- Boxing/Unboxing:Boxing 是把 Value type 轉成
object;Unboxing 是把 boxed value 取回為原本的 Value type,兩者都可能帶來額外成本。 - 物件複製:C# 僅內建淺層複製。若需要深層複製,必須手動實作或透過序列化(如
System.Text.Json)達成。
本章術語
| 英文 | 中文 | 說明 |
|---|---|---|
| BCL | 基礎類別庫 | Base Class Library,提供集合、I/O 等基礎功能 |
| Boxing | 裝箱 | 將 value type 轉換為 object 的過程 |
| CLI (command line interface) | 命令列介面 | 透過文字指令與電腦互動的介面 |
| CLR | 通用語言執行環境 | Common Language Runtime,負責 JIT 編譯與記憶體管理 |
| GC (garbage collector) | 垃圾回收器 | 自動管理記憶體的機制 |
| Heap | 堆積 | 執行期常見的物件配置區域,由 GC 管理 |
| IL (intermediate language) | 中介語言 | C# 編譯後的中間格式,與平台無關 |
| Immutable | 不可變 | 物件建立後內容無法修改的特性(如 string) |
| JIT (Just-In-Time compilation) | 即時編譯 | 執行時將 IL 編譯成機器碼的過程 |
| Reference type | 參考型別;參考類型 | 儲存物件參考的型別(如 class、string) |
| Stack | 堆疊 | 執行期常見的呼叫堆疊區域,通常與方法呼叫脈絡和部分區域資料有關 |
| Top-level statements | 頂層語句 | 現代 C# 語法,可直接撰寫程式碼而不需定義 class |
| Unboxing | 拆箱 | 將 object 轉回 value type 的過程 |
| Value type | 實值型別;值類型 | 直接包含資料的型別(如 int、struct) |