C# 與 C++ 語法高度相似,但運行時模型、記憶體管理、類型系統存在本質差異。許多資深 C++ 開發者在轉型時,會下意識套用 C++ 思維模式,導致寫出「能跑但不地道、有隱患、效率低」的 C# 代碼。本文梳理 10 大核心差異點,對比 C++ 與 C# 行為,提供正確寫法與最佳實踐,助力平滑遷移。
C++ 思維:構造函數獲取資源,解構函數 ~Class() 確定性釋放 (RAII)。
C# 現實:
~Class() (IL 中為 Finalize),由 **GC 非確定性調用**。IDisposable 並提供 Dispose() 供調用者**確定性釋放**。Dispose。
public class ResourceHolder : IDisposable {
private IntPtr _nativeHandle; // 非托管資源
private bool _disposed = false;
// 公開 Dispose
public void Dispose() {
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不必再呼叫 Finalize
}
// 核心邏輯:disposing=true 來自 Dispose(),可釋放托管+非托管資源
// disposing=false 來自 Finalize,僅釋放非托管資源 (托管資源已被 GC 處理)
protected virtual void Dispose(bool disposing) {
if (!_disposed) {
if (disposing) {
// 釋放托管資源 (其他 IDisposable 物件)
}
// 釋放非托管資源
if (_nativeHandle != IntPtr.Zero) {
NativeMethods.CloseHandle(_nativeHandle);
_nativeHandle = IntPtr.Zero;
}
_disposed = true;
}
}
// 終結器 (Finalize)
~ResourceHolder() {
Dispose(false);
}
}
using (var resource = new ResourceHolder()) {
// 使用資源
} // 編譯器自動生成 try/finally 呼叫 resource.Dispose()
| 特性 | C++ 類/結構體 | C# class (參考類型) | C# struct (值類型) |
|---|---|---|---|
| 記憶體位置 | 堆/棧均可 | **托管堆** | **棧** (或內嵌在物件/陣列中) |
| 賦值/傳參 | 預設淺拷貝 (需自定義) | **複製參考** (指標) | **複製值** (位元組逐位拷貝) |
| 繼承 | 支援 | 支援 | **不支援** (隱式繼承 ValueType/Object) |
| Null 能力 | 指標可為 null | 可為 null | **不可為 null** (除非用 Nullable<T>) |
| 裝箱/拆箱 | 無 | 無 | **頻繁發生** (作為 object/介面傳遞時) |
建議:struct 僅用於**小型、不可變、邏輯上為值**的類型 (如 Point, DateTime, decimal)。預設使用 class。
值類型轉為 object 或介面類型時發生**裝箱** (堆分配、拷貝);轉回需**拆箱** (類型檢查、拷貝)。
int i = 123;
object o = i; // 裝箱: 堆分配、拷貝 123
int j = (int)o; // 拆箱: 類型檢查、拷貝回棧
// 災難場景: 非泛型集合
ArrayList list = new ArrayList();
for (int k = 0; k < 100000; k++) list.Add(k); // 10萬次裝箱!
// 正確: List list = new List(); // 泛型無裝箱
規則:嚴禁在熱路徑使用非泛型集合 (ArrayList, Hashtable) 存值類型;全面拥抱泛型 (List<T>, Dictionary<TKey,TValue>)。
C++:派生類同名虛函數自動覆蓋基類 (隱藏風險)。
C#:**必須加 override 關鍵字** 才構成覆蓋;否則視為**隱藏** (new),編譯器發出警告 (CS0114)。
public class Base { public virtual void Foo() {} }
public class Derived : Base {
public override void Foo() {} // 正確覆蓋
// public new void Foo() {} // 故意隱藏,需加 new
}
這消除了 C++ 著名的「脆弱基類問題」與隱式覆蓋風險。
C++:構造函數初始化列表 Derived(int a) : Base(a), _member(a) {}。
C#:**宣告處直接初始化** 或構造函數體內賦值。無初始化列表語法。
public class Employee : Person {
private int _salaryLevel = 3; // 宣告時初始化
public Employee(int age, int level) : base(age) { // 僅初始化基類
_salaryLevel = level; // 或這裡賦值
}
}
C++:if (func())、if (ptr)、if (x = 5) (賦值即真假判斷)。
C#:if 條件**必須是 bool 類型**。int、指標、賦值表達式**不能**隱式轉 bool。
// 錯誤
if (someFuncReturningInt()) { } // CS0029
if (x = 5) { } // CS0029 賦值結果是 int,非 bool
// 正確
if (someFuncReturningInt() != 0) { }
if ((x = 5) != 0) { } // 罕見但合法
bool flag = someFuncReturningBool();
if (flag) { }
這徹底杜絕了 C/C++ 經典的 if (x = y) 誤寫 == 的 Bug。
C++:case 1: case 2: Do(); break; 或有代碼的 case 間可貫穿。
C#:**有代碼的 case 必須以 break/return/throw/goto case 結束**,否則編譯錯誤 (CS0163)。
// 錯誤
switch (i) {
case 1: Do1();
case 2: Do2(); // 錯誤:控制流不能從 case 1 貫穿到 case 2
}
// 正確:明確 goto
switch (i) {
case 1: Do1(); goto case 2;
case 2: Do2(); break;
}
// 正確:空 case 可貫穿
switch (i) {
case 1:
case 2: Do1or2(); break;
}
C++:未初始化區域變數值不確定,用不報錯 (運行時亂碼)。
C#:編譯器**靜態分析確定賦值性**。未賦值讀取直接**編譯錯誤** (CS0165)。
int x;
Console.WriteLine(x); // 錯誤:使用了未賦值的區域變數 'x'
// out 參數解決「僅作為輸出」場景
public void GetTime(out int hour, out int min, out int sec) {
hour = DateTime.Now.Hour; // 必須在返回前賦值
min = DateTime.Now.Minute;
sec = DateTime.Now.Second;
}
int h, m, s;
GetTime(out h, out m, out s); // h,m,s 無需預先初始化
DateTime dt; // 非 null,值為 DateTime.MinValue (0001/1/1 0:00:00)
int i; // 0
bool b; // false
MyStruct s; // 所有欄位遞歸預設值 (0/null/false)
// 如需可為 null:DateTime? dt = null; (Nullable)
IDisposable、使用 using、SafeHandle 衍生類封裝原生句柄。~Class(),它會延長物件壽命、增加 GC 壓力、引發執行緒併發問題。WeakReference<T>) 解決快取/觀察者模式循環引用。using + IDisposable (非托管資源)。class),值類型需顯式 struct。Dispose。unsafe 區塊 + 固定緩衝區 (極少用)。const (編譯期) / readonly (運行期)。#define 仍保留但少用)。掌握這 10 大差異與遷移清單,C++ 開發者即可快速寫出地道、安全、高效能的 C# 代碼,充分發揮 .NET 生態優勢。