抽象类 和接口是C#"封装、继承、多态"三大特性中比较重要的组成部分。抽象类和接口这两种类型用于完全不同的目的。抽象类主要用作对象的基类,贡献某些主要的特性,例如共同的目的和结构。接口则主要是用于类,为这些类去添加不同的方法(行为)。下面分别对它们的概念,代码定义,特点区别,应用场景进行说明。
概念
抽象类概念
抽象类是一种不能被实例化的类,用于作为其他类的基类。它可以包含抽象方法、非抽象方法、字段、属性等成员,用于定义一种通用的类结构和行为,而具体的实现则留给其派生类来完成。
接口概念
接口是从抽象类演变而来的,接口是一种定义了一组方法、属性、事件或索引器的规范,用于描述类应该具有的行为和功能。类可以实现一个或多个接口,从而遵循接口定义的规范。
代码定义
抽象类定义
抽象类在C#中通过abstract关键字来声明。
internal abstract class Animal
{
//抽象属性声明不提供属性访问器的实现,它声明该类支持属性,而将访问器实现留给派生类
public abstract double height { get; }
//普通属性
public double weidth { get; set; }
//普通字段
public double age;
//抽象方法
public abstract void Run();
//普通方法
public void Eat()
{
Console.WriteLine("Animal can eat.");
}
//虚方法,子类继承重写
public virtual void Move()
{
Console.WriteLine("Animal can move.");
}
}
internal class Dog : Animal
{
private double _height;
public override double height
{
get => _height;
}
public Dog()
{
_height = 200;
}
//抽象方法子类必须重写
public override void Run()
{
Console.WriteLine("Dog can run.");
}
//虚方法子类可以重写也可单继承
public override void Move()
{
Console.WriteLine("Animal move by leg.");
}
}
void Main()
{
Dog dog = new Dog();
Animal animal = dog;
animal.weidth = 50;
animal.age = 10;
animal.Run();
Console.WriteLine($"Dog's age->{dog.age},width->{dog.weidth},height->{dog.height}");
Console.ReadLine();
}
运行结果为:
Dog can run.
Dog's age->10,width->50,height->200
接口定义
接口在C#中通过interface
关键字来声明。
public interface FlyInterface
{
//属性
string Name { get; }
//委托和事件
event EventHandler Clicked;
void Fly();
void Move();
}
public interface RunInterface
{
void Run();
void Move();
}
//可以继承多个接口
internal class Plane : FlyInterface, RunInterface
{
public string Name => "Play";
public event EventHandler Clicked;
/*如果类实现两个接口且两个接口包含具有相同签名的成员,
显示接口实现是通过使用接口名称和一个句点命名该类成员来进行实现*/
void FlyInterface.Move()
{
Console.WriteLine("Plane move by fly.");
}
void RunInterface.Move()
{
Console.WriteLine("Plane move by run.");
}
public void Fly()
{
Console.WriteLine("Plane can Fly.");
}
public void Run()
{
Console.WriteLine("Plane can Run.");
}
}
void Main()
{
Plane plane = new Plane();
FlyInterface fly = plane;
fly.Fly();
fly.Move();
RunInterface run = plane;
run.Run();
run.Move();
Console.ReadLine();
}
运行结果为:
Plane can Fly.
Plane move by fly.
Plane can Run.
Plane move by run.
特点区别
抽象类的特点
- 不能被实例化: 抽象类不能被直接实例化,只能被用作其他类的基类。
- 可以包含抽象方法: 抽象类中可以包含抽象方法,这些方法只有声明,没有实际实现。抽象方法用于强制派生类实现特定的行为。
- 可以包含非抽象方法: 抽象类中也可以包含普通的非抽象方法,这些方法可以有默认的实现。
- 可以包含字段和属性: 抽象类可以包含字段和属性,用于存储数据和提供接口。
接口的特点
- 只能定义方法、属性、事件和索引器: 接口只能包含成员的声明,而不能包含具体的实现。
- 不能包含字段: 接口不能包含字段,因为字段是具体的数据存储,而接口只定义行为。
- 类可以实现多个接口: 一个类可以同时实现多个接口,从而具有多个不同的行为。
- 类必须实现接口成员: 类实现接口后,必须提供接口中定义的所有成员的实现。
主要区别
- 他们的派生类只能继承一个基类,即只能直接继承一个抽象类,但可以继承任意多个接口;
- 抽象类中可以定义成员的实现,但接口中不可以;
- 抽象类中可以包含字段、构造函数、析构函数、静态成员或常量等,接口中不可以;
- 抽象类中的成员可以是私有的(只要他们不是抽象的)、受保护的、内部的或受保护的内部成员(受保护的内部成员只能在应用程序的代码或派生类中访问),但接口中的成员必须是公共的。
应用场景
抽象类应用场景
- 当要定义一个类的通用结构和行为,并为派生类提供一些默认实现时,可以使用抽象类。
- 当要强制派生类实现特定方法,但不需要强制实现所有方法时,可以将部分方法声明为抽象方法。
- 当需要为基类提供一些具体的实现,但又希望派生类能够重写这些实现时,可以使用抽象类。
接口应用场景
- 当需要定义一组方法、属性或事件,以实现不同类的多态性时,可以使用接口。
- 当需要在不同的类中实现共同的行为,而这些类已经继承了其他类时,可以通过实现接口来避免多重继承带来的问题。
- 当需要在一个类中实现多个不相关的功能时,可以通过实现多个接口来达到目的。
在选择使用抽象类还是接口时,需要根据具体的情况选择:
- 如果想要定义一组相关的类,共享一些通用的实现,但又要求派生类实现特定的行为,可以使用抽象类。
- 如果想要定义一组不相关的类,使它们实现共同的行为,可以使用接口。
补充说明
1、接口可以用于定义事件和委托的契约,使不同类能够统一实现事件和委托的处理
2、C#虚方法和抽象方法的区别
- 抽象方法是只有方法名称,没有方法体,即没有方法的具体实现,子类必须重写父类抽象方法才能实现具体功能;虚函数有方法名称也也有方法体,但是子类可以覆盖,也可不覆盖。
- 抽象方法是一种强制派生类覆盖的方法,否则派生类将不能被实例化。
- 抽象方法只能在抽象类中声明,虚方法不是。
- 派生类必须重写抽象类中的抽象方法,虚方法则不必要。
参考: