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();
// 輸出:
// 動物發出聲音
// 汪汪!
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)
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");
}
}
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 模式強制流程 |
| 多層繼承深度過深 | 維護困難、呼叫鏈複雜 | 扁平化繼承、改用組合/介面 |
呼叫基底類別方法的核心原則:
理解 virtual、override、new、base 的語意差異,是設計可維護繼承體系的關鍵。