xLua学习笔记——C#与Lua相互调用(贰)

目录

目录

三、C# 调用 Lua

(一) 前言

对于 Lua 脚本,C# 只需调用一个主入口 Main.lua 即可,其他 lua 脚本均可由 Main.lua 调用,比如我们将代码写到 Test.lua 中,这样Main.lua就可以调用:

print("Main Lua脚本启动")
require("Test")

对于 C# 脚本,我们只需写下类似这样的代码,后续就可以调用 Lua 相关的东西了:

void Start()
{
    LuaMgr.GetInstance().Init();
    LuaMgr.GetInstance().DoLuaFile("Main");
 
    // C#调用Lua相关代码...
}

后面篇章我会直接用 Lua 代码、C# 代码 来说明这些核心的代码,而上面这些重复的调用步骤就不提及了

(二)全局变量获取

通过之前 LuaMgr 中的 Global 属性,可以获取到 Lua 的全局变量,而 Global 属性是一张表,在 Xlua 中,Lua 的 table 对应 C# 的 LuaTable 类,里面常用的有 Get 和 Set 两种方法,分别用于获取和修改表的属性,接下来举个例子:

1、Lua 代码如下:

num = 1.2
name = "jack"
sex = true

2、C# 获取全局变量代码如下:

float num = LuaMgr.GetInstance().Global.Get<float>("num");
string name = LuaMgr.GetInstance().Global.Get<string>("name");
bool sex = LuaMgr.GetInstance().Global.Get<bool>("sex");

3、C# 修改全局变量代码如下:

float n = LuaMgr.GetInstance().Global.Get("num"); // 1.2
 
n = 10; //这样并不会影响Lua中的值
LuaMgr.GetInstance().Global.Set("num", 20);
 
n = LuaMgr.GetInstance().Global.Get("num"); // 20

获取变量使用 Get 方法即可,而修改变量则不能直接修改变量,因为变量 n 是值拷贝,要调用 Set 方法进行修改

(三)全局函数获取

1、Lua 代码如下:共 4 种测试函数

-- 无参无返回
testFun = function()
	print("无参无返回");
end
 
-- 有参有返回
testFun2 = function(a)
	print("有参有返回")
	return a
end
 
-- 多返回值
testFun3 = function()
	print("多返回值")
	return 1,false,"jack"
end
 
-- 变长参数
testFun4 = function(...)
	print("变长参数")
	local arg = {...}
	for k,v in pairs(arg) do
		print(k,v)
	end
end

C# 获取 Lua 函数的方式有 2 种,一种是委托(自定义、C# 自带的、Unity 自带的均可),另一种是 Xlua 提供的 LuaFunction类,这个类对应 lua 中的 function,使用方式也类似。就像 LuaTable 类对应 Lua 中的 table 一样。

两种方式各有利弊,但主要还是委托的方式。委托接收函数后,直接用委托的括号调用即可,LuaFunction 接收后,调用 Call 方法即可调用

2、C# 获取无参无返回值函数:

// Unity自带委托
UnityAction ua = LuaMgr.GetInstance().Global.Get<UnityAction>("testFun"); 
ua();
// LuaFunction
LuaFunction lf = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun"); 
lf.Call();

3、C# 获取有参有返回值函数:

这里我们用自定义委托,但是注意,除了无参无返回值的委托 XLua 可以识别外,其他自定义委托均需要加上 [CSharpCallLua] 这个特性,并且需要重新在 Unity 编辑器中 Generate Code 才可以被识别:

// 有参有返回值委托
[CSharpCallLua]
public delegate int CustomCall2(int a);

LuaFunction 的返回值是一个 object [],我们只需要获取第一个返回值,所以 [0] 即可。
怎么样,这个 LuaFunction 是不是和 lua 中的 function 几乎一模一样,只要按 lua 语法来就会用了

// 自定义委托
CustomCall2 call2 = LuaMgr.GetInstance().Global.Get<CustomCall2>("testFun2");
Debug.Log(call2(10));
// LuaFunction
LuaFunction lf2 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun2");
Debug.Log(lf2.Call(30)[0]);

4、C# 获取多返回值函数:

多返回值的委托定义如下:返回值对应 lua 的 function 的第一个返回值,其余返回值用 out 替代

[CSharpCallLua]
public delegate int CustomCall3(out bool b, out string c);
// 自定义委托
int a; bool b; string c;
CustomCall3 call3 = LuaMgr.GetInstance().Global.Get<CustomCall3>("testFun3");
a = call3(out b, out c);
Debug.Log($"{a}_{b}_{c}");
 
// LuaFuntion
LuaFunction lf3 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun3");
object[] objs = lf3.Call(1000);
for (int i = 0; i < objs.Length; i++)
    Debug.Log(objs[i]);

5、C# 获取变长参数函数:

[CSharpCallLua]
public delegate void CustomCall4(params int[] args); // 变长参数类型根据实际情况而定
// 自定义委托
CustomCall4 call4 = LuaMgr.GetInstance().Global.Get<CustomCall4>("testFun4");
call4(1, 2, 3, 4, 5);
// LuaFuntion
LuaFunction lf4 = LuaMgr.GetInstance().Global.Get<LuaFunction>("testFun4");
lf4.Call(6, 7, 8, 9, 10);

可以发现,使用 LuaFunction 会比使用委托更加方便,但是因为 object 拆箱装箱会有一些性能开销,因此平时不太推荐使用 LuaFunction,而使用委托会更加好一些。

(四) Table 映射 List 和 Dictionary

我们知道,Lua 中的表可以表示多种多样的类型,比如列表、字典、类等等,下面就先来说一下映射 List 和 Dictionary

1、Lua 代码如下:

-- List
list = {1,2,3,4,5,6}
 
-- Dictionary
dict = {
	["1"] = 1,
	["2"] = 2,
	["3"] = 3,
	["4"] = 4
}

2、C# 获取 List:

和之前的变量一样,无法直接在 C# 中直接修改 Lua 的值。当然你还可以使用 LuaTable 类来装载,这样就可以用 set 方法修改了变量了

// List接收
List<int> list = LuaMgr.GetInstance().Global.Get<List<int>>("list");
for (int i = 0; i < list.Count; i++)
    Debug.Log(list[i]);
 
//list[0] = 100; // 值拷贝,这样修改不会改变lua的值
 
// LuaTable接收
LuaTable tableList = LuaMgr.GetInstance().Global.Get<LuaTable>("list");
for (int i = 1; i <= tableList.Length; i++) // Lua的table索引默认从1开始
    Debug.Log(tableList.Get<int,int>(i));
 
tableList.Set<int, int>(1, 100); // 修改索引1的值为100

3、C# 获取 Dictionary:

字典也是可以用 LuaTable 装的,凡是 Lua 中为 Table 的都可以装,后面就不演示了,这里直接用 C# 的字典来装

Dictionary<string, int> dict = LuaMgr.GetInstance().Global.Get<Dictionary<string, int>>("dict");
foreach (string item in dict.Keys)
{
    Debug.Log(item + "_" + dict[item]);
}

(五) Table 映射 C# 类

1、Lua 代码如下:

-- Class
MyClass = {
	age = 2,
	sex = true,
	name = "jack",
	myFunc = function()
		print("Func!")
	end,
	mychild = {
		id = "123"
	}
}

2、C# 代码如下:

注意类声明修饰符需要是 public,成员变量可多可少,只不过对不上的话会被忽略

public class MyClass
{
    public int age;
    public bool sex;
    public string name;
    public UnityAction myFunc;
    public MyChildClass mychild;
}
 
public class MyChildClass
{
    public string id;
}

MyClass obj = LuaMgr.GetInstance().Global.Get<MyClass>("MyClass");
Debug.Log(obj.age);
Debug.Log(obj.sex);
Debug.Log(obj.name);
obj.myFunc();
Debug.Log(obj.mychild.id);

(六) Table 映射 C# 接口

1、Lua 代码如下:

上面类的代码几乎一样,只不过要去掉之前的子类成员

MyClass2 = {
	age = 2,
	sex = true,
	name = "jack",
	myFunc = function()
		print("Func!")
	end
}

2、C# 代码如下:

接口需要加上 [CSharpCallLua] 特性,并且接口内只能声明属性,不能声明成员变量

[CSharpCallLua]
public interface IInterface
{
    int age { get; set; }
    bool sex { get; set; }
    string name { get; set; }
    UnityAction myFunc { get; set; }
}

获取并没有什么差别,唯一要注意的就是接口是引用拷贝,这和其他的不同,也就是说你可以直接在 C# 中修改 lua 的变量

IInterface obj = LuaMgr.GetInstance().Global.Get<IInterface>("MyClass2");
Debug.Log(obj.age);
Debug.Log(obj.sex);
Debug.Log(obj.name);
obj.myFunc();
 
obj.age = 100; // 注意:接口是引用拷贝,即修改接口会影响lua的变量

(七)Table 映射 LuaTable 类

我们之前在映射 List 中讲过了这个类了,不过现在需要再稍微详细讲一下这个类,Lua 代码和上面接口讲解的一模一样,不用改。

1、C# 代码如下:

使用 Get 获取变量,使用 Set 修改变量,当然最后不用了记得释放:

// 获取Table
LuaTable table = LuaMgr.GetInstance().Global.Get<LuaTable>("myClass2");
Debug.Log(table.Get<int>("age"));
Debug.Log(table.Get<bool>("sex"));
Debug.Log(table.Get<string>("name"));
table.Get<LuaFunction>("myFunc").Call();
 
// 修改变量
table.Set("age", 100);
 
// 最后要释放
table.Dispose();

四、Lua 调用 C#

(一)前言

在第三节 C# 调用 Lua 的前言中,我已经说过如何通过 C# 调用 Main.lua,Main.lua 调用其他 lua 脚本来做到 C# 调用 Lua

在 Lua 调用 C# 中,我们主要逻辑都写在 lua 脚本中,但是运行时,我们并不是从 Lua 脚本作为入口去启动,因为 Lua 本身并不会识别 C# 中的数据,所以我们必须从 Unity 中启动,通过 C# 调用 Main.lua 的方式,这样 Xlua 会帮我们处理,让 Lua 可以识别并使用我们 C# 中的数据

(二)Lua 调用 C# 类

Lua 想要调用 C#,Xlua 已经帮我们处理好了,只通过 CS 这个特殊的表(根入口),就可以访问 C# 中的任何东西了,固定套路:CS.命名空间.类

1、实例化对象

Lua 中并没有 new 这个方法,但是 xlua 中,我们可以通过 () 的方式来实例化一个 C# 类,这里的 () 相当于类似无参构造,括号中传入参数就类似有参构造

Lua 代码如下:

local obj = CS.UnityEngine.GameObject()
 
GameObject = CS.UnityEngine.GameObject -- 可以取别名,方便书写
local obj2 = GameObject("Obj2")

运行,可以发现实例化了 2 个空物体,其中一个是带有 Obj2 名字的

2、调用静态方法

静态方法通过.来调用

Lua 代码如下:

local obj = GameObject.Find("Obj2")

3、调用成员方法

成员方法必须使用:调用,否则很容易出错,因为 C# 的成员方法一般都会用到自己的成员变量,因此 Lua 调用 C# 的成员方法时,必须使用冒号调用!这样默认就会把调用者(类对象)作为第一个参数传入

Lua 代码如下:

obj.transform:Translate(Vector3.right)

4、调用自定义类

如果我们的自定义类没有放在任何命名空间下,那么直接 CS.类名就好了,比如我在 C# 创建了一个 Test 类,那么实例化这个类就可以这样:

Lua 代码如下:

local test = CS.Test()

5、调用带泛型的方法

Lua 中并不支持泛型,因此,我们不能调用带泛型的方法,我们只能换种方案,通过 Type 的方式调用。xlua 为我们提供了 typeof 这个方法,获取变量的类型。比如我有一个 LuaCallCSharp 的脚本,想通过 AddComponent 添加到物体上

Lua 代码如下:

obj:AddComponent(typeof(CS.LuaCallCSharp))

(三)Lua 调用 C# 枚举

1、枚举的使用

固定套路就是:CS.命名空间.枚举.枚举名,比如我想使用 unity 的一个枚举和一个方法来创建立方体

Lua 代码如下:

PrimitiveType = CS.UnityEngine.PrimitiveType -- 枚举
GameObject = CS.UnityEngine.GameObject
 
local cube = GameObject.CreatePrimitive(PrimitiveType.Cube)

自定义枚举套路一样

2、枚举的转换

关键方法:__CastFrom(值),可以将对应值转换成枚举类型,下面举个小栗子:

C# 代码如下:

public enum MyEnum
{
    Idle,
    Move,
    Jump
}

Lua 代码如下:

-- 数值转枚举
local a = CS.MyEnum.__CastFrom(1) -- Move
-- 字符串转枚举
local b = CS.MyEnum.__CastFrom("Jump") -- Jump

结果就是 a 值为 Move(C# 索引默认从 0 开始),b 值为 Jump

(四)Lua 调用 C# 数组、List、Dictionary

1、C# 代码如下:

public class Test
{
    public int[] array = new int[5] { 1, 2, 3, 4, 5 };
    public List<int> list = new List<int>();
    public Dictionary<int, string> dict = new Dictionary<int, string>();
}

2、Lua 访问 C# 数组代码如下:

我们访问的是 C# 的数组,因此按照 C# 的方式访问就行,并且在遍历时无法使用#获取长度,因为 C# 数组在这里是 userdata 类型,而非 table 类型

local obj = CS.Test()
 
-- 访问数组
print(obj.array.Length)
print(obj.array[0]) -- 获取数组元素按C#的来就行
 
for i=0,obj.array.Length-1 do
	print(obj.array[i])
end

3、Lua 创建 C# 数组代码如下:

因为我们无法直接 new 一个数组,因此可以调用 Array 类中的 CreateInstance 方法来创建一个数组

local array = CS.System.Array.CreateInstance(typeof(CS.System.Int32),10)

4、Lua 访问 List 代码如下:

obj.list:Add(0)
obj.list:Add(1)
obj.list:Add(2)
 
for i=0,obj.list.Count-1 do
	print(obj.list[i])
end

5、Lua 创建 List 代码如下:

新版本的方式更加容易理解,新版本的第一句代表创建了一个 List 的类,并不是实例化对象

-- 老版本:`是esc下面那个键,后面的数字1代表有1个参数类型
local list = CS.System.Collections.Generic["List`1[System.String]"]()
-- 新版本:xlua版本大于v2.1.12
local list_String = CS.System.Collections.Generic.List(CS.System.String) -- 这样创建并不是实例化对象,而是一个类
local list = list_String()

6、Lua 访问 Dictionary 代码如下:

obj.dic:Add(1,"123")
for k,v in pairs(obj.dic) do
	print(k,v)
end

7、Lua 创建 Dictionary 代码如下:

老版本和 List 差不太多,不过这里只列出新版本的,因为更推荐使用新版本方式创建:

local Dict_String_Vector3 = CS.System.Collections.Generic.Dictionary(CS.System.String,CS.UnityEngine.Vector3)
local dict = Dic_String_Vector3()

(五)Lua 调用 C# 拓展方法

我们先为 C# 的 Test 类写一个拓展方法,注意 Lua 中要使用拓展方法,需要在C#中加上 [LuaCallCSharp] 特性

1、C# 代码如下:

[LuaCallCSharp]
public static class Tools
{
    public static void Move(this Test obj)
    {
        Debug.Log("Test移动");
    }
}

2、Lua 代码如下:

local obj = CS.Test()
obj:Move()

(六)Lua 调用 C# ref 和 out 函数

1、C# 代码如下:

3 个测试函数

public class Test
{
    public int RefFunc(int a, ref int b, int c)
    {
        b = 2;
        return 1;
    }
 
    public int OutFunc(int a, out int b, int c)
    {
        b = 1;
        return 2;
    }
 
    public int RefOutFunc(int a, out int b, ref int c)
    {
        b = 1;
        c = 2;
        return 3;
    }
}

调用 ref 或者 out 函数时,第一个返回值是 return 的那个值,后面的就是 ref 或 out 的值,从左到右一一对应

2、Lua 调用 ref 函数:

这里 ref 少传参数的话,后面的参数会补为 nil,所以 ref 需要填占位的值

local obj = CS.Test()

local i,j = obj:RefFunc(1,0,1)
print(i,j) -- i为1,j为2

3、Lua 调用 out 函数:

out 可以不传占位置的参数,即少传参数的话,会先跳过 out 修饰的参数,这里我只传了 2 个参数,其中 1 传给了 a,2 传给了 c,b 跳过了

local i,j = obj:OutFunc(1,2)
print(i,j) -- i为2,j为1

4、Lua 调用 ref 和 out 函数:

这里同样,1 传给了 a,2 传给了 c,b 被跳过了

local i,j,k = obj:RefOutFunc(1,2)
print(i,j,k) -- i为3,j为1,k为2

简单总结下:ref 传参需要占位,out 传参可以不用占位

(七)Lua 调用 C# 重载函数

1、C# 代码如下:

4 个测试函数

public class Test
{
    public int Func()
    {
        return 100;
    }
 
    public int Func(int a, int b)
    {
        return a + b;
    }
 
    public int Func(int a)
    {
        return a;
    }
 
    public float Func(float a)
    {
        return a;
    }
}

2、Lua 代码如下:

我们先调用前两个函数,发现是可以调用的,说明 C# 的重载函数是可以被调用的,但是后两个会出现冲突,只有 int 的这个是正常的,float 的结果是 0,因为 lua 无法区分 int 和 float,他只有 number 类型

local obj = CS.Test()
print(obj:Func()) -- 100
print(obj:Func(10,5)) -- 15
print(obj:Func(1)) -- 1
print(obj:Func(1.2)) -- 0

解决方法:

通过 xlua 提供的反射机制,就可以做到区分所有重载函数啦,主要分 3 步:

  • 反射获取 C# 函数
  • 转成 lua 函数
  • 直接调用
-- 1、获取c#函数信息
local m1 = typeof(CS.Test):GetMethod("Func",{typeof(CS.System.Int32)}) -- 得到Int重载函数信息
local m2 = typeof(CS.Test):GetMethod("Func",{typeof(CS.System.Single)}) -- 得到Float重载函数信息
 
-- 2、转成lua函数
local f1 = xlua.tofunction(m1)
local f2 = xlua.tofunction(m2)
 
-- 3、使用
print(f1(obj,10))
print(f2(obj,10.5))

(八)Lua 使用 C# 委托和事件

1、C# 代码如下:

声明了一个事件一个委托,但是事件由于只能在类内部调用,因此要提供一些方法给外部

public class Test
{
    public UnityAction action;
    public event UnityAction eventAction;
    // 事件只能在内部调用,因此提供调用方法
    public void DoEvent()
    {
        if (eventAction != null)
            eventAction();
    }
 
    public void ClearEvent()
    {
        eventAction = null;
    }
}

2、Lua 代码如下:

先准备要添加到委托或事件的函数:

local obj = CS.Test()
-- 要添加的函数
local func = function()
	print("lua函数Func")
end

3、Lua 调用委托代码如下:

第一次添加委托要用 = 赋值,后面用 + 的方式

-- 添加
obj.action = func
obj.action = obj.action + func
-- 调用委托
obj.action()
-- 删除委托
obj.action = obj.action - func
-- 清除
obj.action = nil

4、Lua 调用事件代码如下:

事件的添加和删除比较不一样,需要通过括号中指明操作符和函数,类似使用成员方法

-- 添加
obj:eventAction("+",func)
obj:eventAction("+",func)
-- 调用事件
obj:DoEvent()
-- 删除
obj:eventAction("-",func)
-- 清空
obj:ClearEvent()

(九)Lua 使用 C# 二维数组

1、C# 代码如下:

public class Test
{
    public int[,] array = new int[,] { { 1, 2, 3 }, { 4, 5, 6 } };
}

2、Lua 代码如下:

-- 获取长度
print("行:"..obj.array:GetLength(0))
print("列:"..obj.array:GetLength(1))
-- 获取元素,无法通过[0][0]或[0,0]方法获取元素,需要通过Array类方法
print(obj.array:GetValue(0,0))
print(obj.array:GetValue(1,1))
-- 遍历
for i=0,obj.array:GetLength(0)-1 do
	for j=0,obj.array:GetLength(1)-1 do
		print(obj.array:GetValue(i,j))
	end
end

(十)Lua 使用 c# 的 null 和 nil 比较

1、引入

我们想创建一个空物体,并尝试获取身上的 Rigidbody 脚本,如果没有则添加该脚本,先来看一个错误示范:

Lua 代码如下:

GameObject = CS.UnityEngine.GameObject
Rigidbody = CS.UnityEngine.Rigidbody
 
local obj = GameObject("测试加脚本")
local rig = obj:GetComponent(typeof(Rigidbody))
 
-- lua中nil和null没办法进行==比较
if rig == nil then
	rig = obj:AddComponent(typeof(Rigidbody))
end

此时是无法向 obj 身上添加 Rigidbody 脚本的,因为 rig 并不是 nil 类型,我们知道 rig 此时是 C# 中的 null,而 Lua 是没有 null 这个概念的,rig 的类型在 Lua 中是 usesrdata 类型,而非 nil。而 rig 为 nil 的情况要么是没初始化,要么是主动赋值了 nil。

幸运的是我们有多种方式来判断 C# 的 null 类型,下面就来介绍

2、方式一

使用 Equals 方法,xlua 为我们特殊处理了这个 Equals 方法,让 null 可以和 nil 进行比较。当然目前这样比较还是不够严谨,因为 rig 本身如果是 nil 时就会报错

if rig:Equals(nil) then
	rig = obj:AddComponent(typeof(Rigidbody))
end

3、方式二

自己在 Lua 中写一个 IsNull 方法判断,后面调用该方法即可判断,我更推荐这种方式

Lua 代码如下:

function IsNull(object)
	if object == nil or object:Equals(nil) then
		return true
	end
	return false
end

if IsNull(rig) then
	rig = obj:AddComponent(typeof(Rigidbody))
end

4、方式三

可以在 C# 中写个 IsNull 方法判断,比如我为 Object 方法写了一个拓展方法 IsNull(拓展方法记得加 [LuaCallCSharp] 特性哦!这里省略了)

public static bool IsNull(this UnityEngine.Object obj)
{
    return obj == null;
}

之后再 Lua 中调用就行了,不过这个前提也是 rig 不为 nil

Lua 代码如下:

if rig:IsNull() then
	rig = obj:AddComponent(typeof(Rigidbody))
end

(十一)Lua 使用 C# 协程

使用 xlua 的 util 表里的 cs_generator 函数,可以将 lua 函数转换成适配 c# 协程格式的函数,这样我们就可以在 Lua 中开启协程了:

1、准备工作

先在 Lua 中取好别名,获取 xlua 的 util 工具表,并创建一个 GameObject,上面挂载的LuaCallCSharp脚本其实就是继承MonoBehaviour的类,用于开启协程

Lua 代码如下:

GameObject = CS.UnityEngine.GameObject
WaitForSeconds = CS.UnityEngine.WaitForSeconds
 
-- xlua提供的工具表
util = require("xlua.util")
 
local obj = GameObject("Coroutine")
local mono = obj:AddComponent(typeof(CS.LuaCallCSharp))

2、开启协程

之后我们创建一个协程函数,并调用 StartCoroutine 开启协程:

-- 创建协程函数
func = function()
	local a = 1
	while true do
		coroutine.yield(WaitForSeconds(1))
		print(a)
		a = a + 1
		if a >= 5 then
			mono:StopCoroutine(routine)
		end
	end
end
 
-- 开启协程
-- mono:StartCoroutine(func) 报错,无法将lua函数传入协程中
routine = mono:StartCoroutine(util.cs_generator(func)) -- 需要调用xlua的util中的cs_generator函数

(十二)Lua 使用 C# 泛型函数

1、前言

我们之前说过 Lua 是不能直接调用 C# 的泛型函数的,会有问题。经过测试,lua 直接调用泛型函数时,只支持约束为 class 且有形参的泛型函数,而且需要传入正确类型的参数,否则会变成 null 传入,下面简单举个例子:

C# 代码如下:

public class Test
{
    public class Father {}
    public class Child : Father {}
 
    public void TestFunc<T>(T a,T b) where T : Father
    {
        Debug.Log("有参数有约束的泛型");
    }
}

Lua 中,我们尝试直接调用这个泛型方法:

Lua 代码如下:

local obj = CS.Test()
local father = CS.Test.Father()
local child = CS.Test.Child()
 
obj:TestFunc(child,father)

当然,直接调用的话只有这种泛型是可以使用的,但是如果没有约束,或者没有形参,或者约束的不是 class,那么就无法直接调用了。

如果我们想完全能够调用 C# 的泛型函数,需要一些特殊的方法,下面就来讲解。

2、解决方法

首先是 C# 部分,我们再写一个泛型方法用于测试

C# 代码如下:

public void TestFunc2<T>(T a)
{
    Debug.Log(a);
}

接下来就是 Lua 调用泛型方法了,主要分三步:

  • 调用 xlua 表的 get_generic_method 方法,传入类和泛型方法名,获取一个泛型模板函数
  • 调用模板函数,传入一个泛型的类型,就可以获得指定泛型类型的函数
  • 直接使用该泛型函数即可,但注意第一个参数是要执行泛型方法的对象,后面才是泛型方法的参数

Lua 代码如下:

local obj = CS.Test()
-- 获取泛型模板函数
local TestFunc2 = xlua.get_generic_method(CS.Test,"TestFunc2")
-- 获取明确类型的泛型函数
local TestFunc2_Int = TestFunc2(CS.System.Int32)
-- 调用
TestFunc2_Int(obj,2)

当然使用 C# 泛型方法并不是完美的,还是有限制条件在的,具体发生在打包方式上:

  • 当我们使用 Mono 打包,没有问题,完全可以使用

  • 但使用 IL2CPP 打包,只有泛型参数是引用类型,才完全可以使用

    • 如果是值类型,只有 C# 代码显式调用过了对应值类型的泛型函数,才可以使用,比如 C# 中用过了 Func<int>,Lua 中使用 int 调用泛型函数 Func 时才可以找到对应实现

image-20260130232431592