Skip to content

C# - 多态

多态(polymorphism)一词意为“多种形式”。在面向对象编程中,它允许将不同类的对象视为一个共同超类的对象来处理。它通常被表述为“一个接口,多种实现”。

多态使您能够编写更灵活、更解耦、更易于维护的代码。C# 中主要有两种类型:

  • 静态多态(编译时):在编译时决定调用哪个方法。这通过方法重载和运算符重载实现。
  • 动态多态(运行时):在运行时才决定调用哪个方法。这通过使用 virtual、override 和 abstract 关键字进行方法重写实现,通常与继承和接口结合使用。

静态多态也称为早期绑定,由编译器解析。

方法重载允许您在同一个类中定义多个同名方法,只要它们的参数列表不同。参数列表的不同体现在参数的数量或参数的类型上。仅返回类型不能用于重载方法。

public class Logger
{
// 重载 1:接受一个字符串
public void Log(string message)
{
Console.WriteLine($"LOG: {message}");
}
// 重载 2:接受一个字符串和一个异常
public void Log(string message, Exception ex)
{
Console.WriteLine($"ERROR: {message} | Exception: {ex.Message}");
}
// 重载 3:接受一个整数
public void Log(int code)
{
Console.WriteLine($"Status Code: {code}");
}
}
// 使用示例
var logger = new Logger();
logger.Log("User logged in.");
logger.Log(404);
logger.Log("Failed to load data.", new InvalidOperationException());
// Output:
// LOG: User logged in.
// Status Code: 404
// ERROR: Failed to load data. | Exception: Operation is not valid due to the current state of the object.

C# 允许您为自定义类型重新定义运算符(如 +、-、*、/、==、!=)的行为。这通过使用 operator 关键字声明一个 static 方法来实现。

这里,我们定义了一个 Vector 类型并重载了 + 运算符以执行向量加法。我们使用 record struct 来表示简洁、不可变的值类型。

// 记录结构体非常适合简单、不可变的数据结构
public record struct Vector(int X, int Y);
{
// 为两个 Vector 对象重载 + 运算符
public static Vector operator +(Vector v1, Vector v2)
{
return new Vector(v1.X + v2.X, v1.Y + v2.Y);
}
}
// 使用示例
var vector1 = new Vector(2, 3);
var vector2 = new Vector(5, 1);
// 在这里调用了重载的 + 运算符
Vector resultVector = vector1 + vector2;
Console.WriteLine($"Vector 1: {vector1}");
Console.WriteLine($"Vector 2: {vector2}");
Console.WriteLine($"Resultant Vector: {resultVector}");
// Output:
// Vector 1: Vector { X = 2, Y = 3 }
// Vector 2: Vector { X = 5, Y = 1 }
// Resultant Vector: Vector { X = 7, Y = 4 }

动态多态也称为后期绑定,在运行时解析,是灵活和可扩展的面向对象设计的基石。

使用 virtual 和 override 进行方法重写

Section titled “使用 virtual 和 override 进行方法重写”

基类可以将方法声明为 virtual(虚方法),允许派生类使用 override 关键字提供其特有的实现。在运行时,执行哪个方法的决定是基于对象的实际类型。

// 带有虚方法的基类
public class Shape
{
public virtual void Draw()
{
Console.WriteLine("Drawing a generic shape.");
}
}
// 覆盖方法的派生类
public class Circle : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a circle.");
}
}
// 另一个派生类
public class Square : Shape
{
public override void Draw()
{
Console.WriteLine("Drawing a square.");
}
}
// 演示多态
var shapes = new List<Shape>
{
new Shape(),
new Circle(),
new Square()
};
foreach (var shape in shapes)
{
shape.Draw(); // 在运行时调用正确的 Draw() 方法
}
// Output:
// Drawing a generic shape.
// Drawing a circle.
// Drawing a square.

一个 abstract(抽象)类不能被实例化,并作为其他类的基类。它可以包含没有实现且必须由任何非抽象派生类重写的 abstract 成员(方法、属性)。

public abstract class Animal
{
// 一个抽象属性 - 派生类必须实现它
public abstract string Sound { get; }
// 使用抽象属性的普通方法
public void Speak()
{
Console.WriteLine($"The animal says: {Sound}");
}
}
public class Dog : Animal
{
// 实现抽象属性
public override string Sound => "Woof!";
}
public class Cat : Animal
{
// 实现抽象属性
public override string Sound => "Meow!";
}
// 使用示例
Animal myDog = new Dog();
Animal myCat = new Cat();
myDog.Speak();
myCat.Speak();
// Output:
// The animal says: Woof!
// The animal says: Meow!

一个 interface(接口)定义了一个类或结构可以实现的公共成员契约。它是一种在不使用类继承的情况下实现多态的强大方式,从而实现了一种形式的多重继承。

// 定义可发送事物的契约接口
public interface ISendable
{
void Send();
}
// Email 类实现了该接口
public class Email : ISendable
{
public void Send() => Console.WriteLine("Sending an email...");
}
// SMS 类也实现了该接口
public class Sms : ISendable
{
public void Send() => Console.WriteLine("Sending an SMS...");
}
// 此方法适用于任何实现了 ISendable 接口的对象
public void SendNotification(ISendable message)
{
message.Send();
}
// 使用示例
SendNotification(new Email());
SendNotification(new Sms());
// Output:
// Sending an email...
// Sending an SMS...

一个常见的问题是何时使用抽象类,何时使用接口。这是一个快速指南:

  • 在以下情况使用抽象类:
    • 您希望在几个紧密相关的类之间共享代码(方法实现、字段)。
    • 您期望扩展您抽象类的类有一个共同的基类,一个“is-a”关系(例如,Dog 是一种 Animal)。
    • 您需要提供 protected 或 internal 成员。
  • 在以下情况使用接口:
    • 您期望不相关的类将实现该接口。它定义了一种能力,一种“can-do”关系(例如,Bird 和 Plane 都可以 Fly)。
    • 您想指定数据类型的行为,但不需要关心谁实现其行为。
    • 您希望利用类型的多重继承。