扩展方法:让 IntelliSense 为你打工
第 15 课末尾露过脸的扩展函数,其实是官方 Verse Code Style Guide 点名推荐的写法:与其让调用者背函数名,不如让 IDE 在点号后替他想。这一页拆解它的语法、规矩和几个立刻能抄走的实用范式。
第 15 课末尾露过脸的扩展函数,其实是官方 Verse Code Style Guide 点名推荐的写法:与其让调用者死记函数名,不如让编辑器在你打出点号后自动把可用方法列出来(IntelliSense / 自动补全,就像蓝图里从引脚拉线时弹出的候选节点菜单)。这一页拆解它的语法、规矩和几个立刻能抄走的实用范式。
一、一个来自官方 style guide 的建议
Epic 官方的 Verse Code Style Guide 里有一条很具体的建议:与其写单参数工具函数 Normalize(MyVector),不如写成扩展方法 MyVector.Normalize()。理由不是性能,也不是格调,而是可发现性:输入 MyVector. 之后,UEFN 的 Verse 编辑器和 VS Code 会逐字符列出这个类型的所有可用方法——工具函数散落在各个文件里全靠记性,扩展方法却会自己排队出现在补全列表里。官方的 Objective Marker 教程更是整篇采用这种写法,给标记类型挂上一串「像原生 API 一样」的方法。
Epic 官方的 Verse Code Style Guide(可以理解成官方的写法/连线规范)里有一条很具体的建议:与其写只吃一个参数的工具函数 Normalize(MyVector),不如写成扩展方法 MyVector.Normalize()。理由不是性能,也不是格调,而是好不好找:打出 MyVector. 之后,UEFN 的 Verse 编辑器会当场把这个类型能用的方法一条条列出来——就像你在蓝图里从一个引脚拉出线时,弹出的那个「这里能接哪些节点」的候选菜单。工具函数散落在各个文件里全靠记性,扩展方法却会自己排队出现在这份候选清单里。官方的 Objective Marker 教程更是整篇采用这种写法,给标记类型挂上一串「像引擎自带节点一样」的方法。
# 工具函数版:调用者必须记得 Doubled 这个名字
Doubled(N:int):int = N * 2
# 扩展方法版:输入 Score. 之后,IDE 替你想名字
(N:int).Doubled():int = N * 2
两行代码功能完全一样,差别全在调用现场:前者写 Doubled(Score),后者写 Score.Doubled()。项目小的时候感觉不出来;等你的工具函数攒到几十个,「点号即目录」的价值就是每天省下的几十次全局搜索。
二、语法拆解与三条规矩
扩展方法的语法只比普通函数多一步:把接收者用括号写在函数名前面——「名字:类型」的形状,跟参数一模一样。函数体里就用这个接收者名引用对象。
using { /UnrealEngine.com/Temporary/Diagnostics }
# 给 int 挂一个「血量夹紧」方法:体内用接收者名 Value 引用对象
(Value:int).ClampHealth():int =
if (Value > 100):
100
else if (Value < 0):
0
else:
Value
point := struct:
X:int = 0
Y:int = 0
# struct 体内不允许定义方法,扩展方法是官方唯一途径
(P:point).Describe():void =
Print("坐标 ({P.X}, {P.Y})")
读图翻译:第一段给 int 挂了个 ClampHealth 方法,里面就是一个把血量夹在 0~100 之间的判断(高于 100 取 100、低于 0 取 0、否则原样),体内用接收者名 Value 指代那个 int。第二段先建了一个结构体资产 point(有 X、Y 两个字段),再用扩展方法给它挂了个 Describe,用 Print String 打出坐标。
三条规矩要记牢。其一,函数体里用你声明的接收者名(上面的 Value、P),不要写 Self——Self 只在 class 体内的方法里有意义,扩展方法不在类的身体里。其二,它可以挂到任何对你可见的类型上:引擎类型、别人的类、struct、enum,完全不需要子类化;其中 struct 和 enum 本身不允许在体内定义方法,扩展方法是给它们加行为的唯一官方途径。其三,别把它当成「真的长进了类里」:扩展方法只能访问对它可见的成员,碰不到那个类型的 private 内部状态——它本质上还是一个函数,只是换了个更好找的挂载点。
三条规矩要记牢。其一,函数体里用你声明的接收者名(上面的 Value、P),不要写 Self——Self 只有在类(蓝图类)自己身体里的方法中才有意义,扩展方法是从外面挂上去的,不在类的身体里。其二,它可以挂到任何对你可见的类型上:引擎类型、别人的蓝图类、结构体(struct)、枚举(enum),完全不用新建子蓝图去继承;其中结构体和枚举本身不允许在体内写方法,扩展方法是给它们加行为的唯一官方途径。其三,别把它当成「真的长进了类里」:扩展方法只能碰到对它公开的成员,摸不到那个类型藏起来的私有(private)内部状态——它本质上还是一个普通函数,只是换了个更好找的挂载点。
三、装进设备里跑一圈
把上一节的两个扩展方法装进设备验收。注意调用姿势:全部是「对象点方法」,读起来像在用引擎自带的 API。
using { /Fortnite.com/Devices }
using { /UnrealEngine.com/Temporary/Diagnostics }
extension_demo_device := class(creative_device):
OnBegin<override>()<suspends>:void =
var Health:int = 130
set Health = Health.ClampHealth()
Print("夹紧后的血量:{Health}")
Home := point{X := 3, Y := 7}
Home.Describe()
读图翻译:先建一个值 130 的血量变量,用 Set 把它换成 Health.ClampHealth() 夹紧后的结果(130 会被压到 100);再造一个坐标 (3, 7) 的 point,调它的 Describe 把坐标打出来。两次调用都是「对象点方法」,读起来跟用引擎自带节点一样顺。
输出依次是「夹紧后的血量:100」和「坐标 (3, 7)」。团队协作时这套写法还有个隐藏加成:新同事拿到一个 point,输一个点号就能看见你为它准备的全部动作,连文档都省了半份。当你发现自己第三次写 某函数(某对象) 形状的调用时,考虑把它翻转成扩展方法——未来的你会感谢现在的你。
官方 style guide 推荐用扩展方法取代单参数工具函数,核心理由是什么?
官方 style guide 推荐用扩展方法取代只吃一个参数的工具函数,核心理由是什么?
想给一个 struct 添加方法,正确途径是?
想给一个结构体(struct)加一个方法,正确途径是?
来源与延伸
本文整理自 Epic 官方文档与教程:
- Verse Code Style Guide ↗ —— 扩展方法优于单参数函数的原始建议。
- Objective Marker Gameplay Tutorial ↗ —— 整篇采用扩展方法风格的官方教程。
- Verse Language Quick Reference ↗ —— 语法速查。