跳到主要内容

04 — 方法重载

C# 方法重载在 Lua 侧的解析与调用策略。适用于 Il2Cpp(Player)Mono(Editor)
继承与 Bind 规则见 02-TYPE-SYSTEM.md §5;zlua API 见 05-LIB.md


1. 问题与目标

C# 允许同名方法因参数类型/个数不同而重载;Lua 无静态类型,无法仅凭 obj:Run(x) 在编译期选定重载。

目标说明
易用obj:Run(10) 在常见场景下应能工作
精确脚本可显式绑定某一重载,并缓存或注册别名
性能热路径避免每次按字符串键查表;禁止 obj[sig](...)
一致Mono 与 Il2Cpp 选中同一重载,错误信息一致

2. 三层机制(优先级)

  1. 按最终名字分组(§3、§5):绑定时每个方法以其 最终 Lua 名(C# 默认名、[LuaAlias] / XML 别名)进入分组;同名允许多个候选(仅 Bind 期别名机制)。
  2. 单候选 → direct;多候选 → dispatch(§3.6)。
  3. 运行时(§6):[LuaAlias] 单候选键或本地缓存的 direct closure;register_method 仅允许挂到 尚不存在 的新最终名(§6.1),并入已有函数或重载组。

不推荐: 将签名字符串作为元表键做 obj[sig](...) 查找——低效,不保留、不文档化


3. 默认名与 dispatch

3.1 注册规则(按最终名字分组)

在同一类型、同一 is_static 域、同一实例形态(ByVal / ByObj)内,先收集每个方法的 最终 Lua 名集合(见 §5),再按名字聚合:

该最终名下的候选方法数元表键绑定
1该候选的 direct method closure
≥ 2dispatch closure(调用时按 §3.6 选具体重载)

来源可以是:

  • 多个 C# 同名重载(默认名相同);或
  • [LuaAlias] / XML 把不同方法挂到 同一最终名(与默认名或其他别名重复均允许)。

zlua.register_method 除外: 运行时注册 禁止 使用已存在的最终名(见 §6.1),因此不会通过该 API 扩大已有 overload 组。

静态与实例分表存放(staticMap vs byvalInstanceMap / byobjInstanceMap)。C# 允许 static void Foo()void Foo() 同名,二者互不影响。

3.2 重载候选顺序

分派时遍历候选列表,在 applicable 重载中按 §3.6 better function member 选优;不得因 metadata 声明顺序靠前就选中 ImplicitBoxing 而跳过更优的 Identity 重载。

候选遍历顺序(仅用于 同分 tie-break):

  1. Codegen 声明顺序(Il2Cpp / Mono 生成元数据中的顺序)
  2. 反射兜底:确定性排序(如完整签名字典序)

3.3 参数匹配规则

在参数个数可接受的前提下,逐参数判断 Lua 实参是否可绑定到 C# 形参类型。规则与 marshal/ReadValue / TryPop 一致,包括但不限于:

Lua 实参C# 形参规则
integerint / long在目标类型范围内
number(非整数)int不匹配
numberfloat / double允许
stringstring允许
nil引用类型 / Nullable<T>允许
nil值类型(非 Nullable)不匹配
userdata引用类型运行时类型可赋值
基元 / stringobject允许;ImplicitBoxingImplicitReference
ByVal 值类型object / 其实现的 interface可隐式装箱时 ImplicitBoxing
基元class / interface(非 object不匹配
多参 + params T[]params默认打包;[LuaMarshalAs(ParamsTable)] 时单 table

可选 / 默认参数: Lua 实参少于形参时,若剩余形参有 C# 默认值,仍可匹配。

构造函数: Type(...) / SMT.__call 使用与实例方法相同的分派逻辑。

3.4 性能说明

dispatch 每次调用需遍历候选并重算匹配,为低效路径。热点若只需固定重载,应为该重载配置 只含单候选 的最终名(例如独立 [LuaAlias("run_i32")],且该别名下无其它方法),或在脚本内 本地缓存 该 direct closure(如 local run = demo.run_i32)。

3.5 失败错误

无匹配重载时 luaL_error,并列出候选签名,例如:

no overload for Demo.Run matching (number); candidates: Run(System.Int32), Run(System.String)

3.6 隐式转换分类与最优重载选择

重载分派须与 C# better function member 一致。

3.6.1 设计原则

  1. ConversionKind 只描述 C# 隐式转换类别,不描述 Lua userdata 载荷形态。
  2. 选优规则与 C# 一致
  3. Mono / Il2Cpp 对同一组 Lua 实参须选中同一 C# 重载。

3.6.2 ConversionKind

KindC# 对应含义
Identity恒等转换类型相同
ImplicitNumeric隐式数值转换仅拓宽
ImplicitEnum隐式枚举转换integer → enum
NullLiteralnull 字面量nil → 引用 / Nullable
ImplicitReference隐式引用转换子类→父类;stringobject
ImplicitBoxing隐式装箱值类型→object / interface
None不匹配

Kind 优劣链:

IdentityImplicitNumericImplicitEnumNullLiteralImplicitReferenceImplicitBoxing

3.6.3 Better function member

  1. 逐形参计算 GetConversionKind;任一 None → 不适用。
  2. M 优于 N:存在形参 i 使 M 更优,且不存在 j 使 N 更优。
  3. 无 strictly better → §3.2 声明顺序 tie-break。

示例:

Lua 调用结论
Run(10)Run(int) vs Run(object)Run(int)(Identity ≻ Boxing)
SetValue(10, 0)SetValue(object,int) vs SetValue(object,long)(object,int)(p1 Identity ≻ Numeric)

3.6.4 invoke 期隐式 Box

仅当 Kind 为 ImplicitBoxing 时,在 已选定重载TryPopObject::Box禁止GetConversionKind 循环内 Box。


4. 签名字符串规范

4.1 zlua.signature

local sig = __zlua_create_signature(zlua.types.int32)
-- sig == "(System.Int32)"

local sig0 = __zlua_create_signature()
-- sig0 == "()"

约定:

  • 参数为 C# 类型:类型表、zlua.types.* 或 mscorlib 字符串(与 05-LIB.md typeArg 相同)
  • 不包含 方法名
  • 格式:括号包裹、逗号分隔的 Type.FullName 列表
  • 泛型、数组格式与 Codegen 元数据一致

Native 回调:__zlua_create_signatureZLuaLib.cpp)。建议在项目 zlualib 扩展中封装为 zlua.signature(...)

4.2 内部查找键(实现用)

Run + (System.Int32) → 内部键 "Run(System.Int32)"

该键 暴露为 Lua __index 字符串键。


5. 别名机制([LuaAlias]

5.1 模型:换名注册 + 按最终名分组

[LuaAlias] / XML 等价于在绑定时用另一个 Lua 名再注册该方法一次(额外最终名),不是与默认名互斥的「独占键」。

对每个 public 方法,其 最终 Lua 名集合 为:

名称是否始终加入
C# 默认名 MethodInfo.Name
[LuaAlias("…")] / XML alias(可多个来源,见 §5.3)(额外加入)

随后在同一绑定域内:

按 finalName 聚合候选方法 → 候选数 1 → direct;≥ 2 → dispatch(§3.6)

因此:

  • 允许别名与其它别名重复;
  • 允许别名与已有默认方法名重复;
  • 调用该重名键时,与普通 C# 重载相同,走 函数重载规则 选合适候选。

5.2 允许重复(示例)

public class Demo
{
public void Run(int value) { }
public void Run(string value) { } // 默认名相同 → "Run" 自然成组

public void Foo(int x) { }

[LuaAlias("Foo")] // 允许:与已有方法名 Foo 重复 → 并入 "Foo"
public void Bar(string s) { }

[LuaAlias("print")]
public void LogA(int x) { }

[LuaAlias("print")] // 允许:别名彼此重复 → "print" 成组
public void LogB(string s) { }

[LuaAlias("run_i32")] // 仅该最终名下多一个候选 → 通常为 direct
public void Run(long value) { } // 默认名仍进 "Run" 组
}
local d = CSharp.AC.Demo()

d:Run(10) -- "Run" 组(int/string/long)→ dispatch
d:Foo("hi") -- "Foo" 组含 Foo(int) 与 Bar(string) → dispatch → Bar(string)
d:print(1) -- "print" 组 → dispatch
d:run_i32(10) -- "run_i32" 单候选 → direct → Run(long)
d:Bar("x") -- 默认名 "Bar" 仍存在

5.3 C# Attribute

[LuaAlias("run_i32")]
public void Run(int value) { ... }
  • 定义于 ZLua.Common
  • 同一方法可与 XML 别名并存;优先级:Attribute > XML(同最终名时以 Attribute 为准合并进集合,不因「重复」失败)。

5.4 XML 配置

<Type fullName="Demo">
<Method name="Run" signature="(System.Int32)" alias="run_i32"/>
</Type>

5.5 静态 / 实例

  • 实例最终名 → byvalInstanceMap / byobjInstanceMap(与 closure 域一致)
  • 静态最终名 → staticMap

分组 不得跨静/实例或跨 ByVal/ByObj。

5.6 与 field / property 同名

若最终方法名与 field / 无参 property 同名,__indexmethodTable 优先(见 metatable/02-INDEX.md)。这与「方法—方法」重名进 overload 组是不同层规则。


6. 运行时 API

显式绑定固定重载时,优先使用 Bind 期 [LuaAlias](单候选 finalName → direct closure),或在脚本内缓存 obj.method / 类型表上的别名 closure。签名字符串仅用于调试对照或 __zlua_create_signature / zlua.signature(...)(§4.1),作为运行时查找键。

6.1 zlua.register_method

须与 ZLuaLib.cpp / zlualib.lua 一致:两参数形式。

zlua.register_method(aliasName, methodOrClosure) → void
local Demo = CSharp.AC.Demo
local calc = Demo()

-- 从类型表 / 实例取得 direct closure(如 [LuaAlias] 键)
local run = calc.run_i32
zlua.register_method("run_custom_i32", run)
calc:run_custom_i32(20)

local add = Demo.add_i32
zlua.register_method("add_custom_i32", add)
assert(Demo.add_custom_i32(3, 5) == 8)
参数说明
aliasName非空字符串;作为 新的 最终 Lua 名写入 method 表
methodOrClosuredirect method closure(单一候选;MetaBinding::IsDirectMethodClosure 等)

写入目标(由 closure 内嵌 TypeBinding 推断):

closure 域写入
静态方法binding->staticMap + 静态 method 索引表
实例 ByValbinding->byvalInstanceMap
实例 ByObjbinding->byobjInstanceMap

与已有键的关系(简化重载管理):

为避免运行时改写已有 overload 组,register_method 不允许 aliasName 在目标 method 表(对应静/实例 map / methodTable)中 已经存在——无论该键当前是:

  • 单个 direct 方法;还是
  • dispatch 重载组;还是
  • 其它已占用的 method 槽。
情况行为
aliasName 不存在写入 direct closure(该名下仅此候选)
aliasName 已存在(direct 或 dispatch 等)luaL_error,不覆盖、不并入
传入 dispatch closureluaL_error(只接受可解析为单一候选的 direct closure)

与 §5 [LuaAlias] 的差异:别名在 Bind 期 允许撞名并组成 overload;register_method运行时 只做「空位挂名」,参与重载合并。

错误:

条件行为
参数个数 ≠ 2luaL_error
无法识别为合法 direct method closureluaL_error
aliasName 已在元表 method 侧占用luaL_error

Native:__zlua_register_method(Il2Cpp 已实现)。

签名说明: 仅接受两参数 (aliasName, closure);目标表由 closure 绑定域决定,无需传入类型表或实例。

6.2 zlua.types

预置 mscorlib 类型名字符串,见 05-LIB.md §4.2。


7. 调用约定摘要

场景写法
默认分派demo:Run(10)
显式重载([LuaAlias] 或本地缓存 closure)run_i32(demo, 10)
[LuaAlias]demo:run_i32(20)
register_methoddemo:run_custom_i32(20)
静态Demo.Add(3, 5)
签名字符串键demo[sig](demo, 10) 禁止

实例方法 closure 用 点号 并显式传 self;注册别名后可用 冒号


8. Mono / Il2Cpp 一致性

要求
按最终名分组 + dispatch §3 / §5一致
别名允许与默认名 / 其它别名重复一致
签名格式 §4.1一致
dispatch §3.3、§3.6一致
选中重载相同实参 → 相同 C# 重载
register_method 两参数;已占用名拒绝一致
错误文案一致或等价

9. 完整示例

public class Demo
{
public void Run(int value) { }

[LuaAlias("run_str")] // "run_str" 单候选 → direct;默认名仍进 "Run" 组
public void Run(string value) { }

public void Foo(int x) { }

[LuaAlias("Foo")] // 与默认名 Foo 重复 → "Foo" 组含 Foo(int)+Bar(string)
public void Bar(string s) { }

public static int Add(int a, int b) => a + b;

[LuaAlias("add_i32")]
public static int Add(int x) => x;
}
local demo = CSharp.AC.Demo()

demo:Run(10) -- "Run" 多候选 → dispatch → Run(int)
demo:Run("ab") -- dispatch → Run(string)
demo:run_str("x") -- 单候选别名 → direct

demo:Foo("hi") -- "Foo" 含 Foo(int) 与 Bar(string) → dispatch → Bar(string)

local run_i32 = demo.run_i32 -- [LuaAlias] 单候选 direct closure
zlua.register_method("run_cached", run_i32) -- 须为尚未占用的新名

local add = CSharp.AC.Demo.add_i32
zlua.register_method("add_one", add) -- OK:新名
-- zlua.register_method("Add", add) -- error:默认名 / 重载组已存在
assert.equal(CSharp.AC.Demo.add_one(7), 7)

10. 实现落点(参考)

模块职责
ValueMarshaling / Mono 等价ConversionKindGetConversionKindTryPop
FindMatchingMethodapplicable + better member
MetaBindingdispatch、direct closure、register_method
ZLuaLib.cpp__zlua_create_signature__zlua_register_method
Weaver / Codegen[LuaAlias] 写入元数据