首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> C# 入門

C# override 與 new 關鍵字差異深度解析:版本控制、多型行為與 IL 層面驗證

     編輯:C# 入門

前言

override (覆寫) 與 new (隱藏) 是 C# 版本控制與多型的核心關鍵字。兩者編譯後 IL 指令、執行期行為、設計意圖截然不同。本文透過代碼實驗、IL 分析、MSDN 官方文件對照,完整釐清兩者差異與適用場景。

核心差異對照表

維度override (覆寫)new (隱藏)
前提條件基底成員需為 virtual/abstract/override基底成員可為任何非靜態成員
多型行為✅ 執行期動態派發 (callvirt)❌ 編譯期靜態綁定 (call)
基底參考呼叫衍生實作✅❌ (呼叫基底實作)
可再次被覆寫✅ (預設 virtual)✅ (需加 virtual)
版本控制意圖擴充/修改基底行為,保持合約引入新成員,避免破壞基底合約
編譯器警告無 (符合預期)若基底有同名成員 → CS0108 (建議加 new 明確意圖)
IL 指令callvirt 方法表槽位call 直接指定方法

代碼實驗:完整驗證範例


using System;

namespace OverrideVsNew
{
    // 基底類別
    class Car
    {
        public virtual void DescribeCar()
        {
            Console.WriteLine("Call DescribeCar() defined in Car class.");
        }
    }

    // 使用 new 隱藏
    class ConvertibleCar : Car
    {
        public new virtual void DescribeCar() // new + virtual 允許進一步覆寫
        {
            Console.WriteLine("Call DescribeCar() defined in ConvertibleCar class.");
        }
    }

    // 使用 override 覆寫
    class Minivan : Car
    {
        public override void DescribeCar()
        {
            Console.WriteLine("Call DescribeCar() defined in Minivan class.");
        }
    }

    class Program
    {
        static void Main()
        {
            Console.WriteLine("=== 以各類別型別宣告變數 ===");
            
            Car car1 = new Car();
            car1.DescribeCar(); // Car
            
            Console.WriteLine("----------");
            
            ConvertibleCar car2 = new ConvertibleCar();
            car2.DescribeCar(); // ConvertibleCar
            
            Console.WriteLine("----------");
            
            Minivan car3 = new Minivan();
            car3.DescribeCar(); // Minivan
            
            Console.WriteLine("----------");
            
            Console.WriteLine("=== 多型測試:基底參考指向衍生實例 ===");
            
            Car poly1 = new ConvertibleCar();
            poly1.DescribeCar(); // ❌ 輸出 Car (new 隱藏,靜態綁定)
            
            Car poly2 = new Minivan();
            poly2.DescribeCar(); // ✅ 輸出 Minivan (override,動態派發)
        }
    }
}

輸出結果

=== 以各類別型別宣告變數 ===
Call DescribeCar() defined in Car class.
----------
Call DescribeCar() defined in ConvertibleCar class.
----------
Call DescribeCar() defined in Minivan class.
----------
=== 多型測試:基底參考指向衍生實例 ===
Call DescribeCar() defined in Car class.      ← new 隱藏:基底參考呼叫基底實作
Call DescribeCar() defined in Minivan class.  ← override:基底參考呼叫衍生實作

IL 層面驗證

Minivan.override 版本 (動態派發)


.method public hidebysig virtual instance void DescribeCar() cil managed
{
    .maxstack 8
    IL_0000: nop
    IL_0001: ldstr "Call DescribeCar() defined in Minivan class."
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: ret
}
// 方法表槽位:繼承 Car 的槽位,覆寫同一槽位

ConvertibleCar.new 版本 (靜態綁定)


.method public hidebysig newslot virtual instance void DescribeCar() cil managed
{
    .maxstack 8
    IL_0000: nop
    IL_0001: ldstr "Call DescribeCar() defined in ConvertibleCar class."
    IL_0006: call void [mscorlib]System.Console::WriteLine(string)
    IL_000b: nop
    IL_000c: ret
}
// 關鍵:newslot = 分配新方法表槽位,不覆寫基底槽位

Main 方法呼叫 IL


// poly1.DescribeCar() - ConvertibleCar 實例,Car 參考
IL_0013: ldloc.0          // 載入 poly1 (Car 參考)
IL_0014: callvirt instance void Car::DescribeCar()  // 呼叫 Car 槽位 → 執行 Car 實作

// poly2.DescribeCar() - Minivan 實例,Car 參考
IL_001f: ldloc.1          // 載入 poly2 (Car 參考)
IL_0020: callvirt instance void Car::DescribeCar()  // 呼叫 Car 槽位 → 執行 Minivan 實作 (方法表已被覆寫)

進階場景:new + virtual 組合


class Base
{
    public virtual void M() { Console.WriteLine("Base.M"); }
}

class Derived : Base
{
    public new virtual void M() { Console.WriteLine("Derived.M"); } // 新槽位 + 可被再覆寫
}

class MoreDerived : Derived
{
    public override void M() { Console.WriteLine("MoreDerived.M"); } // 覆寫 Derived 的新槽位
}

// 測試
Base b1 = new Derived();     b1.M(); // Base.M (new 隱藏)
Derived d1 = new Derived();  d1.M(); // Derived.M
Derived d2 = new MoreDerived(); d2.M(); // MoreDerived.M (多型生效於 Derived 分支)
Base b2 = new MoreDerived(); b2.M(); // Base.M (Base 槽位未被覆寫)

設計決策指南:何時用 override?何時用 new?

✅ 使用 override 的情況

  • **擴充/修改** 基底虛擬方法行為,且**希望基底參考能呼叫新行為** (標準多型)
  • 實作抽象方法 (abstract)
  • 介面成員實作 (隱含 virtual)
  • 框架設計:預期衍生類別自訂行為 (Template Method Pattern)

✅ 使用 new 的情況

  • **引入全新語意** 的同名成員,與基底無關 (非擴充關係)
  • 基底成員**非虛擬** (無法 override),但衍生類別需要同名稱
  • **版本控制**:基底類別新增成員導致衍生類別衝突時,加 new 明確意圖、消除警告
  • 靜態方法隱藏 (靜態成員不支援多型)

⚠️ 避免的反模式


// ❌ 混淆語意:基底虛擬,衍生用 new 卻期望多型
class Base { public virtual void M() {} }
class Bad : Base { public new void M() {} } // 基底參考呼叫 Base.M,極易產生 Bug

// ❌ 新增 new 卻忘記標記,編譯器警告 CS0108
class Base { public void M() {} }
class Bad : Base { public void M() {} } // 警告:隱藏繼承成員,建議加 new

// ✅ 明確意圖
class Good : Base { public new void M() {} } // 清楚表達:這是新成員,非覆寫

官方文件對照 (MSDN)

總結

  • override = 覆寫虛擬方法槽位,**啟用多型**,callvirt 動態派發
  • new = 隱藏基底成員,**分配新槽位** (newslot),call 靜態綁定
  • 基底參考指向衍生實例時:override 顯示衍生行為,new 顯示基底行為
  • new virtual 可建立新的多型分支 (衍生類別間生效,但基底分支不受影響)
  • **永遠加上 new 關鍵字** 當隱藏基底成員,讓意圖明確、消除 CS0108 警告
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved