扩展(Extension)
extension 给既有类型注入新方法:类型的源码不用动,方法面的补全与调用都像原生成员。标量、用户类型都能扩展。
标量接收者
extension string
{
public func Shout() -> string => self.ToUpper() + "!";
}
extension int
{
public func Squared() -> int => self * self;
}
print("哼".Shout());
val n = 5;
print(n.Squared());哼!
25self 指接收者本身。字面量上直接调(7.Squared())、变量上调用,都成立。
用户类型扩展
struct Point
{
public var x: int = 0;
public var y: int = 0;
}
extension Point
{
public func Manhattan() -> int
{
return (x < 0 ? -x : x) + (y < 0 ? -y : y);
}
public func Show() -> string => $"({x}, {y})";
}
val p = new Point() { x: 3, y: -4 };
print(p.Manhattan(), p.Show());7 (3, -4)扩展体里直接访问目标类型的字段(含 private 的同模块字段面)。Point 自己不用改一行。
扩展体能声明什么
扩展体里能声明的是「注入面」:func、operator 重载、属性、static func 与 static event、event 与 event prop(含 before / after / finally 管线段,随注入接线发射)、外加 impl 子句。运算符与属性经扩展注入后就是目标类型的一员:
struct Pt
{
public var x: int = 0;
public var y: int = 0;
}
extension Pt
{
public operator +(self, o: Pt) -> Pt => new Pt() { x: x + o.x, y: y + o.y };
public prop Norm: int => x * x + y * y;
}
val a = new Pt() { x: 1, y: 2 };
val b = new Pt() { x: 10, y: 20 };
val s = a + b;
print(s.x, s.y, a.Norm);11 22 5a + b 走扩展提供的 operator +,a.Norm 走扩展属性,调用位与原生成员无异。反向边界同样清楚:
init与deinit解析拒:构造是类型本体的事。static/const的存储形成员(static val / var / prop、const)定向拒(静态存储挂不到被扩展类型上),静态面只放static func与static event。event与event prop经扩展注入全链路可用:声明、emit、~>订阅、写位广播、管线段发射照常。bind不在扩展体收,绑定声明留在类型体。
激活规则
扩展成员经 import 所在模块自动激活:定义在哪个模块,import 那个模块就可用。
泛型目标
extension Crate<T> // 注入泛型声明本体
extension Crate<int> // 具体化:只注入 Crate<int> 实例化裸标识符永远是类型参数,想具体化到某个实例化就写全 Crate<int>。
具体化版与泛型版同签名共存时具体化版胜出。想具体化到用户命名类型必须点分前缀(Crate<Mod.Foo>)。目标类型实参要么全为类型参数、要么全为具体类型。
遮蔽与重载共存
类型自有方法遮蔽扩展的同名同签名方法,签名不同则正常重载共存:
class Slot<T>
{
public var item: T;
public func Describe() -> string => $"自有 {item}";
}
extension Slot
{
public func Describe() -> string => $"扩展 {item}";
public func DescribeLoud() -> string => $"扩展 {item}!";
}
val s = new Slot<int>();
s.item = 3;
print(s.Describe());
print(s.DescribeLoud());自有 3
扩展 3!Describe 调到自有版本,扩展版被遮蔽。DescribeLoud 只有扩展提供,直接可用。
扩展与协议
extension T impl P 把协议满足性直接传播给目标类型:扩展体提供 P 要求的成员,T 在协议位与泛型约束位都按满足判定,T 自己不用写 impl:
protocol Pinger
{
func Ping(self) -> string;
}
extension Holder impl Pinger
{
public func Ping(self) -> string => "ext";
}
class Holder
{
}
val p: Pinger = new Holder();
print(p.Ping());ext扩展注入的成员同样可以满足类型自己声明的 impl P 要求。反过来,挂到协议身上的扩展成员不会变成协议的新要求,协议的要求集只看协议声明链。协议的 static 成员也惯用扩展实现,见协议的 Lerpable 例。
标量目标有一条硬边界:extension int impl P 的成员直调可用,但标量进不了协议位(val p: P = 5; 报 MS3101)。
注意
- 本类型自有方法遮蔽扩展的同名同签名方法,同名不同签名是正常重载。
- 内建容器(list / set / map / array)方法面封闭,不可作扩展目标(MS4008)。
- 扩展不能加字段、不能改类型的存储布局。
