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

C# 呼叫基底類別方法:new vs override 與 base 關鍵字完整解析

     編輯:關於C#

前言

C# 繼承體系中,衍生類別如何呼叫基底類別實作是常見困惑點。本文系統解析 virtual/override、new 隱藏、base 關鍵字、強制轉型呼叫等機制差異與適用場景。

核心概念比較

機制關鍵字多型行為呼叫基底實作適用場景
虛擬方法覆寫virtual / override✓ 執行期多型base.Method()擴充/修改基底行為
方法隱藏new✗ 編譯期靜態繫結((Base)derived).Method()相同名稱但語意不同
介面實作介面成員✓ 介面多型顯式介面實作契約導向設計

情境一:標準虛擬方法覆寫 (推薦)


public class Animal
{
    public virtual void Speak()
    {
        Console.WriteLine("動物發出聲音");
    }
}

public class Dog : Animal
{
    public override void Speak()
    {
        base.Speak(); // 呼叫基底實作
        Console.WriteLine("汪汪!");
    }
}

public class Cat : Animal
{
    public override void Speak()
    {
        Console.WriteLine("喵~"); // 不呼叫基底
    }
}

// 使用
Animal a = new Dog();
a.Speak(); 
// 輸出:
// 動物發出聲音
// 汪汪!

情境二:new 隱藏基底方法


public class A
{
    public virtual void Func()
    {
        Console.WriteLine("A");
    }
}

public class B : A
{
    public new void Func() // 隱藏 A.Func
    {
        Console.WriteLine("B");
    }
}

public class C : B
{
    public void Func() // 隱藏 B.Func (隱含 new)
    {
        // 如何呼叫 A.Func?
        A a = this; // 隱含轉型為 A
        a.Func();   // 呼叫 A.Func (虛擬調度,實際仍呼叫 C.Func!)
        
        // 正確方式:非虛擬呼叫需透過基底型別實例
        // 但 this 無法直接轉為 A 實例...
    }
}

// 測試
C c = new C();
c.Func();        // 輸出:C (C.Func)
((B)c).Func();   // 輸出:B (B.Func)  
((A)c).Func();   // 輸出:C (虛擬調度!A.Func 是 virtual)

關鍵問題:new 隱藏無法透過 base 呼叫


public class B : A
{
    public new void Func()
    {
        // base.Func() 編譯錯誤!base 只能用於 override
        // ((A)this).Func() 仍會虛擬調度到最衍生類別
        
        // 唯一解法:建立基底實例 (但這不是呼叫同一物件的基底實作)
        A baseInstance = new A();
        baseInstance.Func();
    }
}

情境三:正確呼叫祖父類別方法


public class GrandParent
{
    public virtual void Method()
    {
        Console.WriteLine("GrandParent");
    }
}

public class Parent : GrandParent
{
    public override void Method()
    {
        Console.WriteLine("Parent");
    }
}

public class Child : Parent
{
    public override void Method()
    {
        // 1. 呼叫直接父類別
        base.Method(); // 輸出:Parent
        
        // 2. 呼叫祖父類別 - 方法一:轉型 (但會虛擬調度回 Child!)
        // ((GrandParent)this).Method(); // 輸出:Child (無限遞迴風險)
        
        // 3. 呼叫祖父類別 - 方法二:非虛擬委派 (需父類別配合)
        // 父類別需暴露非虛擬包裝:
        // public void GrandParentMethod() => base.Method();
        // 然後:((Parent)this).GrandParentMethod();
        
        // 4. 呼叫祖父類別 - 方法三:反射 (效能差)
        MethodInfo m = typeof(GrandParent).GetMethod("Method");
        m.Invoke(this, null); // 輸出:GrandParent (非虛擬調用)
        
        Console.WriteLine("Child");
    }
}

最佳實踐模式

模式一:Template Method 模板方法 (推薦)


public abstract class BaseProcessor
{
    // 公開非虛擬進入點
    public void Process()
    {
        Initialize();
        ExecuteCore();
        Cleanup();
    }
    
    // 受保護虛擬擴充點
    protected virtual void Initialize() { }
    protected abstract void ExecuteCore();
    protected virtual void Cleanup() { }
}

public class ConcreteProcessor : BaseProcessor
{
    protected override void Initialize()
    {
        // 基底無實作,直接擴充
        Console.WriteLine("初始化...");
    }
    
    protected override void ExecuteCore()
    {
        Console.WriteLine("核心邏輯");
    }
    
    protected override void Cleanup()
    {
        base.Cleanup(); // 可選呼叫基底
        Console.WriteLine("清理...");
    }
}

模式二:基底方法非虛擬包裝


public class ServiceBase
{
    public virtual void Save()
    {
        Validate();
        SaveCore();
        Log();
    }
    
    // 供衍生類別呼叫的非虛擬包裝
    protected void BaseValidate() => Validate();
    protected void BaseSaveCore() => SaveCore();
    protected void BaseLog() => Log();
    
    protected virtual void Validate() { }
    protected virtual void SaveCore() { }
    protected virtual void Log() { }
}

public class UserService : ServiceBase
{
    protected override void SaveCore()
    {
        BaseValidate(); // 明確呼叫基底驗證
        // 自訂儲存邏輯
        BaseLog(); // 明確呼叫基底日誌
    }
}

模式三:組合優於繼承


// 介面定義契約
public interface ILogger
{
    void Log(string message);
}

// 基底實作
public class DefaultLogger : ILogger
{
    public void Log(string message) => Console.WriteLine($"[LOG] {message}");
}

// 衍生功能透過組合
public class TimestampLogger : ILogger
{
    private readonly ILogger _inner;
    
    public TimestampLogger(ILogger inner)
    {
        _inner = inner;
    }
    
    public void Log(string message)
    {
        _inner.Log($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] {message}");
    }
}

// 使用
ILogger logger = new TimestampLogger(new DefaultLogger());
logger.Log("測試"); // 可無限組合,無繼承複雜度

常見錯誤與陷阱

錯誤代碼問題正確做法
base.Method() 在 new 方法中編譯錯誤:base 僅用於 override改用 override 或組合模式
((Base)this).VirtualMethod()仍會虛擬調度到最衍生類別使用非虛擬包裝方法或反射
忘記 base.Call()基底邏輯未執行 (如 Dispose、Initialize)Template Method 模式強制流程
多層繼承深度過深維護困難、呼叫鏈複雜扁平化繼承、改用組合/介面

總結

呼叫基底類別方法的核心原則:

  1. 優先使用 virtual/override + base.Method():語意清晰、支援多型、編譯器保護
  2. 避免 new 隱藏:破壞多型、無法使用 base、易造成困惑
  3. 需要呼叫祖父類別時:在父類別提供非虛擬包裝方法,或重構為 Template Method
  4. 複雜繼承鏈改用組合:介面 + 裝飾模式 取代深層繼承
  5. Template Method 模式:定義演算法骨架,延遲特定步驟到衍生類別

理解 virtual、override、new、base 的語意差異,是設計可維護繼承體系的關鍵。

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