c# 抽象方法和虚方法的区别

抽象方法必须定义在抽象类中且无方法体,虚方法可存在于普通类并带默认实现;前者是强制契约,后者是可选建议。

抽象方法必须定义在抽象类里,且不能有方法体

这是最硬的限制:只要写了 abstract,就必须把整个类也标成 abstract,否则编译直接报错。而且抽象方法声明后只能跟个分号,不能写大括号和实现逻辑。

  • 错误写法:
    public class Animal { public abstract void Speak(); }
    → 编译器会提示“非抽象类不能包含抽象成员”
  • 正确写法:
    public abstract class Animal { public abstract void Speak(); }
  • 子类如果不 override 所有抽象方法,自己也会变成抽象类,无法 new 实例

虚方法可以存在于普通类,且必须带默认实现

virtual 方法本质是“可选覆盖的已实现方法”,它不要求继承者一定重写,也不强制父类是抽象的。

  • 你可以直接实例化含 virtual 方法的类:
    var a = new Animal(); a.Speak(); // 输出默认行为
  • 子类不写 override 也没问题,调用时自动走基类逻辑
  • 但注意:virtual 不能和 private

    staticabstract 同时出现 —— 这些修饰符语义冲突

重写语法一样,但约束力度天差地别

两者都用 override,但背后意图完全不同:抽象方法是“契约”,虚方法是“建议”。

  • 抽象方法未被重写 → 编译失败(CS0534):“类型未实现继承的抽象成员”
  • 虚方法未被重写 → 完全合法,运行时自动回退到基类实现
  • 想阻止子类再重写?虚方法可用 sealed override,抽象方法做不到(因为没实现可封)

什么时候该用 abstract,什么时候该用 virtual?

看设计意图:如果某个行为在所有子类中“必然不同”,且基类根本没法提供合理默认值,就用 abstract;如果基类能给出一个通用兜底行为,只是允许子类按需定制,就用 virtual

  • 比如 Draw() 对图形基类:不同图形绘制逻辑完全无关 → 抽象方法更合适
  • 比如 ToString()GetHashCode():基类已有合理默认实现 → 虚方法更自然
  • 混用常见:抽象类里既有 abstract 方法(强制实现),也有 virtual 方法(提供可选扩展点)
抽象方法不是“没写完的虚方法”,而是“故意留白的契约”;虚方法也不是“弱化的抽象方法”,而是“自带备胎的灵活接口”。漏掉这个区分,很容易在重构时把抽象类强行实例化,或在不该加 override 的地方硬塞重写逻辑。