带方法体的Interface与Abstract class对比:C#新特性下抽象类有何用?
即使C# 8+给接口增加了默认方法、静态字段/属性这些特性,抽象类依然是不可替代的——核心原因在于它和接口的设计目标本质不同:抽象类是为了构建类的层次、承载实例状态,而接口是为了定义行为契约、实现多态能力。具体来说,抽象类的独特价值体现在这些场景:
保存实例状态:接口永远不能定义实例字段,哪怕有默认方法,也只能访问接口的静态成员或方法参数。但抽象类可以声明实例变量,用来存储每个对象的独有状态。比如做动物类体系时,抽象类
Animal可以定义_age、_weight这些实例字段,子类Dog、Cat继承后直接复用这些状态,这是接口完全做不到的。
示例代码:public abstract class Animal { protected int _age; protected double _weight; public Animal(int age, double weight) { _age = age; _weight = weight; } public abstract void MakeSound(); } public class Dog : Animal { public Dog(int age, double weight) : base(age, weight) { } public override void MakeSound() => Console.WriteLine("Woof!"); }表达强“is-a”继承关系:C#是单继承多实现,抽象类天生适合构建严格的类层次结构,清晰表达“是一种”的关系。比如
Vehicle->Car->ElectricCar,这种层级用抽象类能明确传递继承链的语义;而接口是“具备某种能力”的关系(比如IElectric、IHasGPS),如果强行用接口做层次,会导致语义混乱——毕竟一个类可以实现多个接口,没法体现唯一的父类层级。统一初始化逻辑:抽象类可以定义构造函数(通常是protected),强制子类在实例化时执行统一的初始化步骤。比如上面的
Animal构造函数,要求子类必须传入age和weight,确保所有动物实例都具备这些必要属性。接口没有构造函数,无法强制子类做这类初始化操作。灵活的访问权限控制:抽象类的成员可以用
public、protected、private(C# 8+支持私有抽象成员)等修饰符,能精准控制子类对成员的访问权限。比如你可以定义一个protected的辅助方法,只有子类能调用;而接口的成员默认是public,即使C# 8+允许私有方法,也只能在接口内部使用,没法给子类提供受保护的成员。限制为引用类型:抽象类只能被引用类型继承,而接口可以被值类型(比如struct)实现。如果你的设计是针对引用类型的场景(比如需要多态传递、共享状态),抽象类能确保所有子类都是引用类型,避免值类型带来的装箱/拆箱开销,或者不符合设计意图的情况。
更友好的版本扩展:当你需要给现有抽象类添加新功能时,可以添加带默认实现的非抽象成员,只要不修改原有抽象成员,子类不需要做任何改动就能兼容;而接口如果添加新成员,所有实现该接口的类都必须实现它(哪怕用默认方法,也是接口层面的实现,不是子类的实例成员),抽象类的扩展对现有代码的破坏性更小。
内容的提问来源于stack exchange,提问作者Darshit Gandhi

