首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 開發者轉 C# 必知的十大陷阱(中文完整版)

     編輯:關於C語言

前言

C# 與 C++ 語法相似,但執行模型截然不同。本文整理自 MSDN Magazine 經典文章,針對 C++ 程式設計師遷移 C# 時最易踩的十大陷阱詳細解析,環境基於 Visual Studio .NET 2002 (7.0 Beta 2)。

陷阱 1:非確定性終結與解構函式

核心差異

  • C++:~Class() 確定性呼叫,RAII 模式管理資源
  • C#:無解構函式,只有 Finalize() (終結器),由 GC 非決定性呼叫

C# 終結器語法糖

~MyClass() { /* 清理非受控資源 */ }
// 等同於
protected override void Finalize()
{
    try { /* 清理 */ }
    finally { base.Finalize(); }
}

關鍵規則

  1. 僅釋放非受控資源(檔案控制代碼、COM 物件、Unmanaged 記憶體)
  2. 勿參考其他受控物件(可能已被回收)
  3. 不可直接呼叫 Finalize(),由 GC 排程
  4. 實作終結器有效能成本,僅在必要時實作

陷阱 2:Finalize 與 Dispose 模式

Dispose 模式標準實作

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 區塊自動呼叫 Dispose

using (var font = new Font("Arial", 10))
{
    // 使用 font
} // 編譯器自動產生 try/finally 呼叫 font.Dispose()

陷阱 3:值型別 vs 參考型別

特性值型別 (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 (未受影響)

陷阱 4:隱含裝箱/拆箱效能陷阱

裝箱:值型別 → object (參考型別)

int i = 123;
object o = i; // 隱含裝箱:配置物件、複製值
int j = (int)o; // 顯式拆箱:型別檢查、複製回值型別

效能影響

  • 配置記憶體、GC 壓力
  • 集合 (ArrayList) 存值型別大量裝箱 → 改用 List<T> 泛型
  • 避免在迴圈/高頻路徑裝箱

陷阱 5:結構行為大不同

  • 無繼承、無解構函式
  • 不可為 null (除非 Nullable<T>)
  • 預設建構子固定零初始化,不可自訂
  • 傳遞給方法會複製,修改不影響原本
  • 介面呼叫會裝箱:IInterface i = myStruct; → 裝箱

陷阱 6:建構子鏈結與初始化順序

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(...) 明確指定。

陷阱 7:虛擬方法在建構子中呼叫

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# 編譯器不阻止,但極度危險)。

陷阱 8:運算子多載與隱含轉換

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,建議慎用,偏好顯式轉換或工廠方法。

陷阱 9:陣列協變風險


string[] strs = new string[10];
object[] objs = strs; // 合法:陣列協變
objs[0] = 123; // 編譯通過,執行期拋出 ArrayTypeMismatchException

C# 陣列為協變,但非型別安全。泛型 List<T> 為不變,避免此問題。

陷阱 10:委派與事件的閉包陷阱


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非確定性終結解構函式 RAIIIDisposable + using
2Finalize/Dispose手動 deleteDispose 模式 + 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#》深入研讀。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved