C# 與 C++ 語法相似,但執行模型截然不同。本文整理自 MSDN Magazine 經典文章,針對 C++ 程式設計師遷移 C# 時最易踩的十大陷阱詳細解析,環境基於 Visual Studio .NET 2002 (7.0 Beta 2)。
~Class() 確定性呼叫,RAII 模式管理資源Finalize() (終結器),由 GC 非決定性呼叫~MyClass() { /* 清理非受控資源 */ }
// 等同於
protected override void Finalize()
{
try { /* 清理 */ }
finally { base.Finalize(); }
}
public class ResourceHolder : IDisposable
{
private IntPtr _handle; // 非受控資源
private bool _disposed;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this); // 告訴 GC 不需再呼叫 Finalize
}
protected virtual void Dispose(bool disposing)
{
if (_disposed) return;
if (disposing)
{
// 釋放受控資源
}
// 釋放非受控資源
CloseHandle(_handle);
_handle = IntPtr.Zero;
_disposed = true;
}
~ResourceHolder() => Dispose(false);
}
using (var font = new Font("Arial", 10))
{
// 使用 font
} // 編譯器自動產生 try/finally 呼叫 font.Dispose()
| 特性 | 值型別 (struct, enum, 基本型別) | 參考型別 (class, interface, delegate, array) |
|---|---|---|
| 儲存位置 | Stack / 內嵌 | Heap (參考在 Stack) |
| 指派 | 複製資料 | 複製參考 |
| 預設值 | 零初始化 | null |
| 繼承 | 不可繼承 (隱含 sealed) | 支援繼承 |
| 傳遞給方法 | 傳值 (複製) | 傳參考 (高效) |
struct Point { public int X, Y; }
Point p1 = new Point { X = 10 };
Point p2 = p1; // 複製
p2.X = 20;
Console.WriteLine(p1.X); // 10 (未受影響)
int i = 123;
object o = i; // 隱含裝箱:配置物件、複製值
int j = (int)o; // 顯式拆箱:型別檢查、複製回值型別
ArrayList) 存值型別大量裝箱 → 改用 List<T> 泛型Nullable<T>)IInterface i = myStruct; → 裝箱class Base
{
public Base() { Console.WriteLine("Base()"); }
public Base(int x) : this() { Console.WriteLine("Base(int)"); }
}
class Derived : Base
{
public Derived() { Console.WriteLine("Derived()"); }
public Derived(int x) : base(x) { Console.WriteLine("Derived(int)"); }
}
// new Derived(5) 輸出:
// Base()
// Base(int)
// Derived(int)
C# 強制基底建構子先執行,使用 base(...) 明確指定。
class Base
{
public Base() { Initialize(); } // 危險!
protected virtual void Initialize() => Console.WriteLine("Base.Init");
}
class Derived : Base
{
private string _data = "Init";
protected override void Initialize() => Console.WriteLine(_data.Length); // _data 尚未初始化!
}
// new Derived() 可能拋出 NullReferenceException
規則:建構子中勿呼叫虛擬方法 (C# 編譯器不阻止,但極度危險)。
struct Digit
{
public int Value;
public static implicit operator int(Digit d) => d.Value; // 隱含轉換
public static explicit operator Digit(int i) => new Digit { Value = i }; // 顯式轉換
public static Digit operator +(Digit a, Digit b) => new Digit { Value = a.Value + b.Value };
}
濫用隱含轉換易導致難以追蹤的 Bug,建議慎用,偏好顯式轉換或工廠方法。
string[] strs = new string[10];
object[] objs = strs; // 合法:陣列協變
objs[0] = 123; // 編譯通過,執行期拋出 ArrayTypeMismatchException
C# 陣列為協變,但非型別安全。泛型 List<T> 為不變,避免此問題。
var actions = new List();
for (int i = 0; i < 3; i++)
{
actions.Add(() => Console.WriteLine(i)); // 捕捉變數 i,非值!
}
foreach (var a in actions) a(); // 全輸出 3
for (int i = 0; i < 3; i++)
{
int copy = i; // 每次迴圈新變數
actions.Add(() => Console.WriteLine(copy));
}
// 輸出 0, 1, 2
| # | 陷阱 | C++ 習慣 | C# 正確做法 |
|---|---|---|---|
| 1 | 非確定性終結 | 解構函式 RAII | IDisposable + using |
| 2 | Finalize/Dispose | 手動 delete | Dispose 模式 + GC.SuppressFinalize |
| 3 | 值/參考型別 | 指標/參考 | struct vs class,理解複製語意 |
| 4 | 裝箱/拆箱 | 無對應 | 用泛型避免,注意效能 |
| 5 | 結構限制 | struct ≈ class | 無繼承、不可 null、會裝箱 |
| 6 | 建構子鏈結 | 初始化列表 | base() 強制呼叫基底建構子 |
| 7 | 建構子呼叫虛擬 | 常見模式 | 嚴禁 |
| 8 | 隱含轉換 | 單參數建構子 | 慎用 implicit,偏好 explicit |
| 9 | 陣列協變 | 指標轉換 | 改用 List<T>,理解執行期風險 |
| 10 | 閉包捕捉變數 | 無對應 | 區域副本捕捉值 |
語法相似 ≠ 語意相同。理解 Managed 執行環境、GC、型別系統核心差異,才能寫出正確、高效、安全的 C# 程式碼。建議配合《CLR via C#》深入研讀。