C# virtual 和 abstract 详解

简介

C# 中,virtualabstract 关键字都用于面向对象编程中的继承和多态,它们主要用于方法、属性和事件的定义,但在用法上存在一些重要的区别。

virtual 关键字

virtual 表示可重写的方法,但可以提供默认实现,派生类可以选择是否重写。

使用规则

  • 只能在类成员(方法、属性、事件)上使用,不能用于字段。
  • 必须有默认实现,子类可以选择是否 override 进行重写。
  • 在基类中调用时,调用的是基类的方法,但如果子类重写了,则会调用子类的实现(运行时多态)。
  • 不能用于 static 方法,但可以用于属性和事件。

示例:virtual 方法

csharp 复制代码
using System;

class BaseClass
{
    public virtual void ShowMessage()
    {
        Console.WriteLine("基类的 ShowMessage() 方法");
    }
}

class DerivedClass : BaseClass
{
    public override void ShowMessage()
    {
        Console.WriteLine("子类的 ShowMessage() 方法");
    }
}

class Program
{
    static void Main()
    {
        BaseClass obj1 = new BaseClass();
        obj1.ShowMessage(); // 输出: 基类的 ShowMessage() 方法

        DerivedClass obj2 = new DerivedClass();
        obj2.ShowMessage(); // 输出: 子类的 ShowMessage() 方法

        BaseClass obj3 = new DerivedClass();
        obj3.ShowMessage(); // 输出: 子类的 ShowMessage() 方法 (运行时多态)
    }
}

virtual 属性

csharp 复制代码
class Animal
{
    public virtual string Name { get; set; } = "Unknown Animal";
}

class Dog : Animal
{
    public override string Name { get; set; } = "Dog";
}

class Program
{
    static void Main()
    {
        Animal a = new Dog();
        Console.WriteLine(a.Name); // 输出: Dog
    }
}

abstract 关键字

abstract 表示抽象成员,没有实现,必须在派生类中重写。它只能出现在 abstract 类中。

使用规则

  • 只能在 abstract 类中使用,不能用于 sealed 类(密封类)。
  • 没有方法体,子类必须 override 提供具体实现。
  • 抽象方法不能有 private 修饰符,但可以是 protectedpublic
  • 抽象方法在基类中不能有默认实现,必须在子类实现。
  • 抽象类本身不能被实例化,但可以有构造函数。

示例:abstract 方法

csharp 复制代码
using System;

abstract class Animal
{
    public abstract void MakeSound(); // 没有方法体,子类必须实现
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪汪!");
    }
}

class Cat : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("喵喵喵!");
    }
}

class Program
{
    static void Main()
    {
        Animal dog = new Dog();
        dog.MakeSound(); // 输出: 汪汪汪!

        Animal cat = new Cat();
        cat.MakeSound(); // 输出: 喵喵喵!
    }
}

abstract 属性

csharp 复制代码
abstract class Animal
{
    public abstract string Name { get; set; }
}

class Dog : Animal
{
    public override string Name { get; set; } = "Dog";
}

class Program
{
    static void Main()
    {
        Animal a = new Dog();
        Console.WriteLine(a.Name); // 输出: Dog
    }
}

virtual vs abstract 的区别

关键字 virtual abstract
是否有实现 有默认实现 没有默认实现,必须在子类实现
是否必须重写 可以重写,也可以不重写 必须被子类重写
是否能在非 abstract 类中使用 可以 只能用于 abstract 类
能否实例化 可以实例化基类 抽象类不能实例化
适用范围 方法、属性、事件 方法、属性、事件

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

virtual

  • 适用于提供默认行为,但允许子类覆盖的场景。
  • 例如:基类提供 virtual 方法 SaveToDatabase(),子类可以重写也可以直接使用。

abstract

  • 适用于基类无法提供合理默认实现,必须由子类提供具体实现的情况。
  • 例如:Animal 类的 MakeSound() 方法,每种动物的叫声都不同,所以必须由子类实现。

综合示例

csharp 复制代码
using System;

abstract class Animal
{
    public abstract void MakeSound(); // 必须由子类实现

    public virtual void Sleep()
    {
        Console.WriteLine("动物正在睡觉...");
    }
}

class Dog : Animal
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪汪!");
    }

    public override void Sleep()
    {
        Console.WriteLine("狗狗正在睡觉...");
    }
}

class Program
{
    static void Main()
    {
        Animal myDog = new Dog();
        myDog.MakeSound(); // 汪汪汪!
        myDog.Sleep();     // 狗狗正在睡觉...
    }
}

virtual 方法如何调用基类方法?

当子类重写了 virtual 方法后,仍然可以在 override 方法中调用 基类的实现,这在 扩展父类功能 时特别有用。

在子类 override 方法中调用 base 方法

csharp 复制代码
using System;

class BaseClass
{
    public virtual void ShowMessage()
    {
        Console.WriteLine("基类的 ShowMessage() 方法");
    }
}

class DerivedClass : BaseClass
{
    public override void ShowMessage()
    {
        base.ShowMessage();  // 先调用基类方法
        Console.WriteLine("子类的 ShowMessage() 方法");
    }
}

class Program
{
    static void Main()
    {
        DerivedClass obj = new DerivedClass();
        obj.ShowMessage();
    }
}

输出:

复制代码
基类的 ShowMessage() 方法
子类的 ShowMessage() 方法
  • 这里 base.ShowMessage(); 先执行基类的方法,再执行子类的逻辑。
  • 这种方式避免了完全覆盖,而是基于已有逻辑做扩展。

abstract 和 interface 的区别

C# 中,abstractinterface 都可以定义必须由子类实现的方法,但它们有一些关键区别。

abstract interface
是否有方法实现 不一定,可以有 abstract 也可以 virtual C# 8.0+ 支持默认实现,但大多数情况下没有
是否可以包含字段 可以(非 static) 不可以
是否可以有构造函数 可以 不可以
继承方式 只能继承一个 可以实现多个
csharp 复制代码
abstract class Animal
{
    public abstract void MakeSound();
    public virtual void Sleep()
    {
        Console.WriteLine("睡觉中...");
    }
}

interface IAnimal
{
    void MakeSound();
    void Sleep();
}

class Dog : Animal, IAnimal  // 可以同时继承抽象类和接口
{
    public override void MakeSound()
    {
        Console.WriteLine("汪汪汪!");
    }

    public void Sleep()
    {
        Console.WriteLine("狗狗正在睡觉...");
    }
}

abstract class 适用于有共享逻辑的场景,而 interface 适用于不同类之间的通用行为。

virtual 和 abstract 结合使用

在某些情况下,抽象类可以定义 virtual 方法,让子类选择是否覆盖。

csharp 复制代码
abstract class Shape
{
    public abstract void Draw(); // 必须重写

    public virtual void Move()
    {
        Console.WriteLine("Shape is moving...");
    }
}

class Circle : Shape
{
    public override void Draw()
    {
        Console.WriteLine("画一个圆形");
    }

    public override void Move()
    {
        Console.WriteLine("圆形在移动...");
    }
}

class Program
{
    static void Main()
    {
        Shape shape = new Circle();
        shape.Draw(); // 画一个圆形
        shape.Move(); // 圆形在移动...
    }
}
  • Draw() 是抽象方法,必须重写。
  • Move() 是虚方法,子类可以选择是否重写。

virtual 和 abstract 在性能上的考虑

C# 中,virtual 方法会在运行时通过虚方法表(VTable) 进行调用,而 abstract 只是提供一个强制实现的约定,子类实现后仍然是 virtual 方法。

普通方法调用(非 virtual

csharp 复制代码
public class MyClass
{
    public void NormalMethod() { }
}

// 直接调用,无额外开销。

virtual 方法调用

csharp 复制代码
public class MyClass
{
    public virtual void VirtualMethod() { }
}

// 通过 VTable 进行间接调用,可能稍微慢一点(但一般无影响)。

abstract 方法调用

csharp 复制代码
public abstract class MyBaseClass
{
    public abstract void AbstractMethod();
}

public class MyClass : MyBaseClass
{
    public override void AbstractMethod() { }
}

// abstract 方法在子类实现后,仍然是虚方法(virtual),运行时仍然使用 VTable 机制。

sealed 和 override 一起使用

sealed 关键字可以阻止子类进一步重写 virtual 方法。

csharp 复制代码
class Parent
{
    public virtual void Show()
    {
        Console.WriteLine("父类方法");
    }
}

class Child : Parent
{
    public sealed override void Show()
    {
        Console.WriteLine("子类方法,不能再被重写");
    }
}

class GrandChild : Child
{
    // 这里会报错,因为 `Show` 方法被 `sealed` 了
    // public override void Show() { }
}
  • 子类 Child 已经 sealed override,所以 GrandChild 不能再重写 Show() 方法。

  • 适用于限制特定方法的继承,比如框架设计时控制扩展性。

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

virtual

  • 当希望提供一个默认实现,但允许子类根据需要重写时。

abstract

  • 当基类无法提供默认实现,必须要求子类强制实现时。

sealed override

  • 当希望限制某个方法的进一步重写时。

用 abstract + virtual 组合

  • 适用于部分方法必须实现,部分方法可以选择重写的情况。
相关推荐
FAREWELL000752 小时前
C#进阶学习(一)简单数据结构类之ArrayList、Stack、Queue、Hashtable
数据结构·学习·c#·queue·arraylist·stack·hash table
春生野草2 小时前
0413-多态、Object类方法、访问权限修饰符、装箱拆箱、128陷阱
java·开发语言
烁3472 小时前
每日一题(小白)暴力娱乐篇26
java·开发语言·算法·娱乐
周周记笔记2 小时前
探索R语言:在线学习资源汇总
开发语言·r语言
zuoming1202 小时前
c# 系列pdf转图片 各种处理3--net3.1到net8 PDFtoImage
开发语言·pdf·c#
FreeLikeTheWind.3 小时前
Qt问题之 告别软件因系统默认中文输入法导致错误退出的烦恼
开发语言·c++·windows·经验分享·qt
余瑾瑜3 小时前
如何在CentOS部署青龙面板并实现无公网IP远程访问本地面板
开发语言·后端·golang
wangnaisheng3 小时前
【C#】CAN通信的使用
开发语言·c#
小白的一叶扁舟3 小时前
Java设计模式全解析(共 23 种)
java·开发语言·设计模式·springboot
小文数模3 小时前
2025年认证杯数学建模C题完整分析论文(共39页)(含模型、可运行代码)
c语言·开发语言·数学建模