C# 抽象类与接口的区别详解

85

在 C# 中,抽象类(Abstract Class) 和 接口(Interface) 都用于实现面向对象编程中的抽象和多态,但它们的设计目的和使用场景有显著差异。

1. 定义与核心目的

抽象类

  • 表示一种 "是什么"(Is-A) 关系,用于定义一组相关类的 基类。

  • 可以包含 具体方法(有实现) 和 抽象方法(无实现)。

  • 强调代码复用,提供部分通用实现,子类通过继承扩展或重写。

接口

  • 表示一种 "能做什么"(Can-Do) 关系,定义一组 行为契约。

  • 仅包含方法、属性、事件或索引器的 签名(无具体实现,C# 8.0+ 允许默认实现)。

  • 强调多态性,用于解耦实现和定义

2. 成员类型

抽象类

接口

字段

✅ 可以包含字段

❌ 不能包含字段(但可以有属性)

构造函数

✅ 可以有构造函数

❌ 不能有构造函数

方法实现

✅ 可以包含具体方法和抽象方法

❌ 默认无实现(C# 8.0+ 允许默认实现)

访问修饰符

可以指定 public/protected

成员默认是 public,不可用其他修饰符

3. 继承与实现

抽象类

  • 一个类 只能继承一个抽象类(C# 不支持多类继承)。

  • 子类必须实现所有抽象方法(除非子类本身也是抽象类)。

接口

  • 一个类 可以实现多个接口。

  • 实现接口的类必须提供所有成员的实现(除非使用默认实现)。

  • 接口可以继承其他接口(允许多继承)。

4. 设计场景

使用抽象类

  • 当多个类有 共享的代码逻辑 时(复用基类代码)。

  • 需要定义 非公共成员(如 protected 方法)。

  • 需要为子类提供 默认实现 或 强制约束。

public abstract class Animal {
    protected int Age;  // 字段
    public abstract void MakeSound();  // 抽象方法
    public void Sleep() {  // 具体方法
        Console.WriteLine("Sleeping...");
    }
}

使用接口

  • 当多个 不相关类需要相同行为 时(如 IDisposable)。

  • 需要定义 跨不同继承树的类的共同契约。

  • 实现轻量级多态(避免继承层次过深)。

public interface IFlyable {
    void Fly();  // 默认无实现(C# 8.0+ 允许默认实现)
}

public class Bird : Animal, IFlyable {
    public void Fly() => Console.WriteLine("Flying with wings");
}

public class Drone : IFlyable {
    public void Fly() => Console.WriteLine("Flying with propellers");
}

5. 版本控制

抽象类

  • 添加新方法时,可以设为 virtual 并提供默认实现,不影响现有子类。

  • 修改基类可能影响所有子类。

接口

  • 添加新成员会破坏所有实现该接口的类(C# 8.0+ 的默认实现可缓解此问题)。

  • 更适合稳定、无需频繁变更的契约。

6. 多态性

抽象类:通过继承实现多态,强调 纵向扩展(父子类层次)。

接 口: 通过实现多个接口实现多态,强调 横向扩展(不同类共享行为)。

抽象类

接口

代码复用

✅ 优先选择

❌ 不适合

多继承

❌ 不支持

✅ 支持多接口实现

定义公共契约

❌ 不灵活

✅ 理想选择

需要字段或构造函数

✅ 支持

❌ 不支持

实际开发中:

  • 优先用 接口 定义行为契约(如 IEnumerable, IDisposable)

  • 用 抽象类 封装共享逻辑(如 Stream、ControllerBase)

  • 两者可以结合使用(如抽象类实现接口,提供部分通用逻辑)