抽象类必须用abstract修饰且不可实例化,可含字段、构造函数、virtual方法等;abstract方法无实现,子类须override;与接口选择取决于是否需共享状态或默认实现。

抽象类必须用 abstract 修饰,且不能被实例化
定义抽象类时,class 前必须加 abstract 关键字,否则编译器会报错 CS0518: Predefined type 'System.Object' is not defined or imported 或更直接的 CS0144: Cannot create an instance of the abstract class or abstract method。哪怕类里一个抽象成员都没有,只要标了 abstract,就不能写 new MyAbstractClass()。
常见误操作:
- 忘记加
abstract却写了abstract void DoSomething();→ 编译失败,提示“包含抽象方法的类必须声明为abstract” - 加了
abstract却试图new它 → 运行前就报错,IDE 通常红色波浪线下划线 - 把
abstract错写成virtual或sealed→ 语义完全错误,行为不可控
abstract 方法不能有方法体,子类必须用 override 实现
abstract 方法只声明签名,不提供实现,连大括号 {} 都不能有。子类继承后,必须用 public override(或符合访问级别的 override)给出具体逻辑,否则子类也得标 abstract。
abstract class Animal
{
public abstract void MakeSound(); // ✅ 正确:无实现
// public abstract void Sleep() { } // ❌ 编译错误:abstract 方法不能有主体
}
class Dog : Animal
{
public override void MakeSound() // ✅ 必须 override,且访问修饰符不能比基类更严格
{
Console.WriteLine("Woof!");
}
}
注意点:
-
abstract方法默认是public,但显式写public abstract更清晰;不能用private abstract(编译器直接拒绝) - 子类若没实现全部
abstract成员,自身必须也声明为abstract -
abstract方法不能是static、virtual或sealed
抽象类可以含普通成员、virtual 方法和构造函数
抽象类不是“空架子”,它可以像普通类一样拥有字段、属性、非抽象方法、virtual 方法,甚至带参数的构造函数——这些都会被子类继承并可直接调用。
abstract class Shape
{
protected string name;
protected Shape(string name) => this.name = name; // ✅ 抽象类可以有构造函数
public abstract double GetArea(); // 子类必须实现
public virtual void Describe() => Console.WriteLine($"This is a {name}"); // ✅ 可被重写,也可直接调用
public void PrintType() => Console.WriteLine("Shape"); // ✅ 普通方法,子类自动继承}
class Circle : Shape
{
private readonly double radius;
public Circle(double r) : base("Circle") // ✅ 必须调用基类构造函数
{
radius = r;
}
public override double GetArea() => Math.PI * radius * radius;
}
关键细节:
- 子类构造函数必须通过
: base(...)调用抽象基类的构造函数(如果有) -
virtual方法在抽象类中很常用:提供默认行为,允许子类选择性重写 - 字段和
protected属性是子类共享状态的常用方式,但要注意封装边界
抽象类 vs 接口:选谁取决于“有没有共同状态或默认实现”
如果多个类型需要共享字段、构造逻辑、部分实现代码,或者你希望强制子类走某个初始化流程(比如必须传参进构造函数),就用抽象类。接口只描述“能做什么”,不提供状态或实现。
典型判断信号:
- 需要
protected字段或internal辅助方法?→ 抽象类 - 想让子类自动获得一个
LogCreated()默认日志行为?→ 抽象类 +virtual方法 - 只是约定一组行为(如
IDisposable、IComparable),且不同实现完全无关?→ 接口 - 一个类要同时满足多种契约(如既可比较又可序列化)?→ 必须用接口,因为 C# 不支持多继承
别为了“听起来高级”而硬套抽象类。很多初学者把所有基类都设成 abstract,结果发现子类全得写重复构造逻辑,反而增加维护成本。










