抽象类必须用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 属性是子类共享状态的常用方式,但要注意封装边界如果多个类型需要共享字段、构造逻辑、部分实现代码,或者你希望强制子类走某个初始化流程(比如必须传参进构造函数),就用抽象类。接口只描述“能做什么”,不提供状态或实现。
典型判断信号:
protected 字段或 internal 辅助方法?→ 抽象类LogCreated() 默认日志行为?→ 抽象类 + virtual 方法IDisposable、IComparable),且不同实现完全无关?→ 接口别为了“听起来高级”而硬套抽象类。很多初学者把所有基类都设成 abstract,结果发现子类全得写重复构造逻辑,反而增加维护成本。