C# 虚函数机制详解:virtual、override 与 new 的区别及调用原理
在 C# 面向对象编程中,理解虚函数(Virtual Methods)的调用机制是实现多态的关键。本文深入解析 virtual 关键字的语法约束、方法分发的底层逻辑,并重点区分子类的 override(重写)与 new(隐藏)在实际代码执行中的不同表现。
C# 虚函数的定义与约束
虚方法是允许在派生类中被替换实现的实例方法。声明时需在方法前添加 virtual 关键字。
在使用 virtual 时,必须遵守以下限制:
- 不可静态化:不能使用
static修饰符。 - 不可为私有:不能使用
private修饰符,否则派生类无法访问。 - 非抽象非覆盖:虚方法本身不能是
abstract(抽象)或override(覆盖)状态,它们是父类提供的默认实现。
虚方法的执行机制
普通方法在编译阶段进行静态绑定,地址固定;而虚方法采用动态绑定(Dynamic Dispatch),其最终执行的方法体取决于对象在内存中的实际类型(实例类),而非声明时的类型(申明类)。
第1步:检查声明类
当程序执行方法调用时,首先检查该方法是否在“声明类”中被标记为 virtual。
第2步:动态路由至实例类
若确认为虚方法,运行时环境将不再执行声明类的当前版本,而是转向对象的“实例类”。系统会在实例类中向下检索:查看是否有使用 override 的实现。
第3步:向上回溯查找
如果当前实例类没有重写该虚方法,系统将沿着继承链向上查找父类,直到找到第一个实现了 override 的版本并执行。若整条链上均未重写,则执行声明类或最顶层基类的原始虚方法。
不同关键字的行为对比实战
Scenario A: 使用 override 实现真正的多态
当子类使用 public override 时,无论通过何种类型的引用变量调用,只要实例是该子类,就会执行子类的逻辑。
class A
{
public virtual void Print()
{
Console.WriteLine("A is Virtual");
}
}
class B : A
{
// 正确重写,激活多态机制
public override void Print()
{
Console.WriteLine("B is Override");
}
}
class Program
{
static void Main(string[] args)
{
// 即使声明为 A,实例化为 B,依然执行 B 的逻辑
A a = new B();
a.Print(); // 输出: B is Override
}
}
Scenario B: 使用 new 隐藏成员
如果使用 new 关键字定义同名方法,这被称为"隐藏"(Hiding)。此时虚方法的动态链接机制被阻断。方法的执行结果将严格取决于**引用变量的声明类型**。
注意:此处展示了关键的引用类型陷阱
情况 1:基类引用指向子类实例
// 同上 Base A, Derived B 使用 new
A a = new B();
a.Print(); // 输出: A is Virtual (退化为基类逻辑)
情况 2:派生类引用指向子类实例
// 引用类型为 B
B b = new B();
b.Print(); // 输出: B is New (执行子类新逻辑)
Scenario C: 继承链中的未重写传递
若在多级继承中,中间层未进行重写,将自动继承上一层的 override 结果:
class C : B // B 已经 override 了 A
{
// 未做任何操作
}
// 执行
A c = new C();
c.Print(); // 输出: B is Override (继承了 B 的重写逻辑)
特殊场景处理
抽象类重写虚函数
> 允许在抽象类中将基类的虚函数再次声明为抽象函数,要求继续使用abstract override。这意味着该实现将被推迟给最终的派生类,且抽象类自身不提供具体代码。
abstract class A { public virtual void M() {} }
abstract class B : A
{
// 重新定义为抽象,强制后代实现
public abstract override void M();
}
密封类(Sealed Class)的影响
> 密封类不能被继承,因此它可以包含虚函数,但无法再向更下层的派生类提供多态能力。它继承了基类的虚函数通常会自动降级为非虚行为,因为其作为继承链的终点,不可能再被override。免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









