C# 多态性
静态多态(重载)与动态多态(虚方法、接口),以及 `is`/`as` 转型。
两种多态
| Kind | Mechanism | Decided |
|---|---|---|
| 静态多态 | 方法重载、泛型 | 编译期 |
| 动态多态 | 虚方法、接口 | 运行期 |
动态多态
abstract class Payment
{
public abstract string Pay(decimal amount);
}
class Alipay : Payment
{
public override string Pay(decimal amount) => $"支付宝支付 {amount:C}";
}
class Wechat : Payment
{
public override string Pay(decimal amount) => $"微信支付 {amount:C}";
}
class Card : Payment
{
public override string Pay(decimal amount) => $"银行卡支付 {amount:C}";
}
// 调用方只依赖抽象,不关心具体实现
void Checkout(Payment p, decimal amount)
{
Console.WriteLine(p.Pay(amount));
}
Checkout(new Alipay(), 99.5m);
Checkout(new Wechat(), 99.5m);
Checkout(new Card(), 99.5m);支付宝支付 ¥99.50
微信支付 ¥99.50
银行卡支付 ¥99.50Checkout never changed yet supports three payment methods. That is the value of polymorphism: adding an implementation needs no change to the caller — the open/closed principle.
接口多态
interface ILogger
{
void Log(string msg);
}
class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine($"[控制台] {msg}");
}
class FileLogger : ILogger
{
public void Log(string msg) => Console.WriteLine($"[文件] {msg}");
}
class Service
{
private readonly ILogger _log;
public Service(ILogger log) => _log = log; // 构造注入
public void Run() => _log.Log("服务启动");
}
new Service(new ConsoleLogger()).Run();
new Service(new FileLogger()).Run();[控制台] 服务启动
[文件] 服务启动That is dependency injection: the class depends on an interface, implementations arrive from outside. In tests you pass a fake ILogger and assert how often it was called.
转型
Payment p = new Alipay();
// is:判断
Console.WriteLine(p is Alipay);
Console.WriteLine(p is Wechat);
// is 带模式变量:判断并转型
if (p is Alipay a)
{
Console.WriteLine($"确实是支付宝:{a.GetType().Name}");
}
// as:转型,失败返回 null
var w = p as Wechat;
Console.WriteLine(w == null);
// 强转:失败抛异常
try
{
var bad = (Wechat)p;
}
catch (InvalidCastException)
{
Console.WriteLine("强转失败");
}
// 类型判断的 switch
string kind = p switch
{
Alipay => "支付宝",
Wechat => "微信",
Card => "银行卡",
_ => "其他"
};
Console.WriteLine(kind);True
False
确实是支付宝:Alipay
True
强转失败
支付宝重载:静态多态
class Formatter
{
public string Format(int x) => $"整数 {x}";
public string Format(double x) => $"浮点 {x:F2}";
public string Format(string x) => $"字符串 {x}";
public string Format(int x, int width) => $"补零 {x.ToString($"D{width}")}";
}
var f = new Formatter();
Console.WriteLine(f.Format(42));
Console.WriteLine(f.Format(3.14159));
Console.WriteLine(f.Format("abc"));
Console.WriteLine(f.Format(42, 5));整数 42
浮点 3.14
字符串 abc
补零 00042object o = 42; f.Format(o) fails — the compiler sees object with no matching overload. Only dynamic defers to runtime.多态的三个条件
- 父类/接口声明(
Payment/ILogger) - 子类重写实现(
override/ 实现接口) - 用父类/接口类型的引用调用(
Payment p = new Alipay())
The third is the one people miss: Alipay a = new Alipay(); a.Pay(...) is statically bound — no polymorphism. The variable must be declared as the base type for runtime dispatch.
两种多态
| 类型 | 机制 | 决定时机 |
|---|---|---|
| 静态多态 | 方法重载、泛型 | 编译期 |
| 动态多态 | 虚方法、接口 | 运行期 |
动态多态
abstract class Payment
{
public abstract string Pay(decimal amount);
}
class Alipay : Payment
{
public override string Pay(decimal amount) => $"支付宝支付 {amount:C}";
}
class Wechat : Payment
{
public override string Pay(decimal amount) => $"微信支付 {amount:C}";
}
class Card : Payment
{
public override string Pay(decimal amount) => $"银行卡支付 {amount:C}";
}
// 调用方只依赖抽象,不关心具体实现
void Checkout(Payment p, decimal amount)
{
Console.WriteLine(p.Pay(amount));
}
Checkout(new Alipay(), 99.5m);
Checkout(new Wechat(), 99.5m);
Checkout(new Card(), 99.5m);支付宝支付 ¥99.50
微信支付 ¥99.50
银行卡支付 ¥99.50Checkout 一行代码都没改,就支持了三种支付方式。这就是多态的价值:新增实现不需要修改调用方,符合开闭原则。
接口多态
interface ILogger
{
void Log(string msg);
}
class ConsoleLogger : ILogger
{
public void Log(string msg) => Console.WriteLine($"[控制台] {msg}");
}
class FileLogger : ILogger
{
public void Log(string msg) => Console.WriteLine($"[文件] {msg}");
}
class Service
{
private readonly ILogger _log;
public Service(ILogger log) => _log = log; // 构造注入
public void Run() => _log.Log("服务启动");
}
new Service(new ConsoleLogger()).Run();
new Service(new FileLogger()).Run();[控制台] 服务启动
[文件] 服务启动这就是依赖注入的原理:类依赖接口而不是具体类,实现从外部传进来。测试时传一个假的 ILogger 就能断言「日志被调用了几次」。
转型
Payment p = new Alipay();
// is:判断
Console.WriteLine(p is Alipay);
Console.WriteLine(p is Wechat);
// is 带模式变量:判断并转型
if (p is Alipay a)
{
Console.WriteLine($"确实是支付宝:{a.GetType().Name}");
}
// as:转型,失败返回 null
var w = p as Wechat;
Console.WriteLine(w == null);
// 强转:失败抛异常
try
{
var bad = (Wechat)p;
}
catch (InvalidCastException)
{
Console.WriteLine("强转失败");
}
// 类型判断的 switch
string kind = p switch
{
Alipay => "支付宝",
Wechat => "微信",
Card => "银行卡",
_ => "其他"
};
Console.WriteLine(kind);True
False
确实是支付宝:Alipay
True
强转失败
支付宝重载:静态多态
class Formatter
{
public string Format(int x) => $"整数 {x}";
public string Format(double x) => $"浮点 {x:F2}";
public string Format(string x) => $"字符串 {x}";
public string Format(int x, int width) => $"补零 {x.ToString($"D{width}")}";
}
var f = new Formatter();
Console.WriteLine(f.Format(42));
Console.WriteLine(f.Format(3.14159));
Console.WriteLine(f.Format("abc"));
Console.WriteLine(f.Format(42, 5));整数 42
浮点 3.14
字符串 abc
补零 00042object o = 42; f.Format(o) 会编译失败——编译期看到的是 object,找不到匹配的重载。dynamic 才会推迟到运行期。多态的三个条件
- 父类/接口声明(
Payment/ILogger) - 子类重写实现(
override/ 实现接口) - 用父类/接口类型的引用调用(
Payment p = new Alipay())
第三条最容易漏:Alipay a = new Alipay(); a.Pay(...) 是静态绑定,没有多态。必须把变量声明成父类型,调用才会在运行期解析。