一种C#泛型方法在lua中表示的设计
在进行lua方法注册的时候, 大多数解决方案直接否定了泛型方法, 因为在lua侧难以表达出泛型, 以及lua的函数重载问题,
函数重载问题可以通过一些特殊方法解决, 而泛型问题是主要问题, 以Unity + Slua的情况来说
比如下面的类:
- public class Foo
- {
- public static void GetTypeName(System.Type type)
- {
- Debug.Log(type.Name);
- }
- public static void GetTypeName<T>()
- {
- Debug.Log(typeof(T).Name);
- }
- }
一般只会生成 GetTypeName(System.Type type) 的注册方法.
那么泛型的方法在Lua那边该怎样注册才能让这个调用能够实现呢? 一般来说我们调用泛型方法必须在写代码的时候就确定, 像这样:
- Foo.GetTypeName<int>(); // 输出 Int32
而lua并不能这样约束, 它的调用必须还是非泛型的才可以, 这是第一个问题, 而第二个问题是lua那边怎样写? 我们希望它的写法能跟C#保持
一致, 或者相似吧, 让人看起来容易明白, 可是lua中中括号是大于小于号, 不能这样写, 想想有没有什么办法
因为在lua中是没有类型的, 类型必须来自C#, 所以只能将泛型作为非泛型方法才能使用, 如果让函数进行一次退化和封装, 像下面这样
- -- 先将C# 的typeof注册成全局函数, 注册System.Int32命名为int
- local Foo = {}
- Foo.GetTypeName = function(type)
- return function()
- print(type.Name)
- end
- end
- Foo.GetTypeName(typeof(int))(); -- lua
- Foo.GetTypeName<int>(); // C# -- 之前写错了 -_-!
- 这样写的话, 除了尖括号, 基本就能两边一致了对吧, 运行结果也是一样的
- /*至于怎样注册typeof(int)*/
- // 在LuaState的Init中注册个全局函数
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]- internal static int getType(IntPtr L)
- {
- System.Type type = null;
- LuaObject.checkType(L, , out type);
- LuaObject.pushObject(L, type);
- return ;
- }
- // 在LuaState的Init中自己注册咯
LuaDLL.lua_pushcfunction(L, getType);
LuaDLL.lua_setglobal(L, "typeof");
- // CustomExport.OnAddCustomClass 中添加类型别名
- add(typeof(System.Int32), "int"); // int
只是这里lua的函数没有进行C#那边的调用啊, 下一步就来看看有没有什么办法来实现调用.
如果通过自动注册的话, Foo应该是一个已经注册的类型.
- [SLua.CustomLuaClass]
- public class Foo
并且有元表, 元表里面有非泛型的GetTypeName方法了. 现在先不要去动元表,
直接注册这个到Table里面, 因为如果Table里面有值的话, 就不会去查询元表了
- import "Foo";
- Foo.GetTypeName(typeof(int)); // 输出 Int32
- rawset(Foo, "GetTypeName", function(type)
- return function()
- local mt = getmetatable(Foo)
- local func = rawget(mt,"GetTypeName");
- func(type)
- end
- end)
- Foo.GetTypeName(typeof(int))(); // 输出 Int32 -- 注意返回了function然后再次调用
这个方法比较流氓, 因为直接默认了有非泛型函数, 并且覆盖了元表的非泛型方法, 不可取的.
要继续的话, 首先来看看一个泛型方法怎样通过非泛型(Type)方法进行调用的:
- var methods = typeof(Foo).GetMethods(BindingFlags.Public | BindingFlags.Static | BindingFlags.InvokeMethod);
- foreach(var method in methods)
- {
- if(method.IsGenericMethod)
- {
- var paramters = method.GetParameters();
- if(paramters == null || paramters.Length == )
- {
- var genericMethod = method.MakeGenericMethod(new Type[] { typeof(int) });
- if(genericMethod != null)
- {
- genericMethod.Invoke(null, null); // 输出 Int32
break;
}- }
- }
- }
当然是反射啦, 这样就能让泛型方法退化为非泛型了, 虽然是一个缓慢的反射, 不过时间基本只花费在Invoke上, 问题还不大.
剩下的问题是重载了, 有非泛型和泛型的两个同名函数, 为了测试我先删除掉非泛型,
- [SLua.CustomLuaClass]
- public class Foo
- {
- //public static void GetTypeName(System.Type type)
- //{
- // Debug.Log(type.Name);
- //}
- public static void GetTypeName<T>()
- {
- Debug.Log(typeof(T).Name);
- }
- }
生成的lua注册代码也要修改一下 (自动生成的注册类文件名应该是Lua_Foo.cs 吧)
- System.Type a1;
- checkType(l,,out a1);
- Foo.GetTypeName(a1); // 泛型函数被注释了
- pushValue(l,true);
改成
- System.Type a1;
- checkType(l,,out a1);
- var methods = typeof(Foo).GetMethods(System.Reflection. BindingFlags.Public
- | System.Reflection.BindingFlags.Static
- | System.Reflection.BindingFlags.InvokeMethod);
- foreach(var method in methods)
- {
- if(method.IsGenericMethod)
- {
- var paramters = method.GetParameters();
- if(paramters == null || paramters.Length == )
- {
- var genericMethod = method.MakeGenericMethod(new Type[] { typeof(int) });
- if(genericMethod != null)
- {
- genericMethod.Invoke(null, null);
- break;
- }
- }
- }
- }
- pushValue(l,true);
试试运行一下看看, 输出 Int32 看来没有问题, 问题是在Lua那边还是需要手动封装了一遍, 看前文:
- -- 问题是, 不进行一次rawset无法得到泛型写法
- Foo.GetTypeName(typeof(int))(); // 输出 Int32 -- Table方法
到这里, 基本就可以得出结论了,
一. 在lua中可以通过封装(闭包)的方式接近C#的泛型的写法, 差别只是一个中括号和小括号
- Foo.GetTypeName(typeof(int))(); -- lua 我们可以把 lua全局变量int 赋值为 typeof(int), 这样可以跟C#更加相似:
int = typeof(int);- Foo.GetTypeName(int)();
- Foo.GetTypeName<int>(); // C# -- 之前写错了 -_-!
然而过程异常复杂, 比如上述代码中的rawset过程需要在C#的注册代码中进行实现, 而在调用的地方需要通过反射, 并且在lua侧需要解决函数重载的问题,
上面的例子直接做了覆盖. 就无法正常访问非泛型方法函数了, 是个错误方向.
PS: 今天又看了一遍这篇文章, 这里有点歧义, 其实是在lua这边不需要实现重载, 重载方法的调用是由C#那边的注册代码封装的.
而我这里手动覆盖了lua这边的Foo的GetTypeName方法(该方法原来由metatable提供, 现在我在Foo里面直接添加了GetTypeName).
二. 既然泛型方法可以退化为非泛型, 那么可以直接检测有没有同名的且同参数的非泛型函数, 如果没有就把泛型方法的非泛型版添加到注册函数中即可.
Slua是通过反射程序集来查找相应类型然后通过反射里面的对象来实现注册代码生成的, 这里我想到一个丧心病狂的方法, 就是通过ILGenerator和Emit的方式,
把泛型方法转换成非泛型方法, 添加到IL代码里面, 这样在编辑器下的反射就可以自动生成一个非泛型的对应函数了哈哈哈哈哈哈......想想工作量真够大的.
先封装一下非泛型调用泛型方法的逻辑:
- // 反射调用泛型函数方法
- public static void CallGenericFunction(System.Type type, string genericFuncName, object instance, Type[] genericTypes, object[] paramaters, bool isStatic)
- {
- var flags = BindingFlags.Public | BindingFlags.NonPublic | (isStatic ? BindingFlags.Static : BindingFlags.Instance) | BindingFlags.InvokeMethod;
- var methods = typeof(Foo).GetMethods(flags);
- foreach(var method in methods)
- {
- if(method.IsGenericMethod && string.Equals(method.Name, genericFuncName, StringComparison.Ordinal))
- {
- var arguments = method.GetGenericArguments(); // 检查泛型类的数量是否对的上
- if(arguments != null && arguments.Length == genericTypes.Length)
- {
- // 检查传入参数类型是否对的上, 如果考虑到可变参数, default value参数, 可空结构体参数等, 会很复杂
- if(MethodParametersTypeEquals(method, paramaters))
- {
- var genericMethod = method.MakeGenericMethod(genericTypes);
- if(genericMethod != null)
- {
- genericMethod.Invoke(instance, paramaters);
- break;
- }
- }
- }
- }
- }
- }
- // 简单的对比一下, 实际使用要考虑到可变参数( params object[] ), default value参数( bool isStatic = false ), 可空结构体参数( int? a = null )等
- public static bool MethodParametersTypeEquals(MethodInfo method, object[] parameters)
- {
- var mehotdParamters = method.GetParameters();
- int len_l = mehotdParamters != null ? mehotdParamters.Length : ;
- int len_r = parameters != null ? parameters.Length : ;
- return len_l == len_r;
- }
测试一下, 为了测试非静态函数, 添加了非静态一个方法
- [SLua.CustomLuaClass]
- public class Foo
- {
- public static void GetTypeName(System.Type type)
- {
- Debug.Log(type.Name);
- }
- public static void GetTypeName<T>()
- {
- Debug.Log(typeof(T).Name);
- }
- public void TypeName<T>()
- {
- Debug.Log(typeof(T).Name);
- }
- }
- CallGenericFunction(typeof(Foo), "GetTypeName", null, new Type[] { typeof(float) }, null, true); // 输出 Single
- var foo = new Foo();
- CallGenericFunction(typeof(Foo), "TypeName", foo, new Type[] { typeof(int) }, null, false); // 输出 Int32
调用是正确的.
写了这个调用封装之后, 我发现有两种路线可以进行下去, 第一个就是前面提到的Emit(只在编辑器下使用, 可以放心), 另一个方法是修改Slua的生成代码的逻辑,
先说修改Slua的生成代码那方面, 上面的Foo生成的GetTypeName的注册函数如下 :
- [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
- [UnityEngine.Scripting.Preserve]
- static public int GetTypeName_s(IntPtr l)
- {
- try
- {
- System.Type a1;
- checkType(l, , out a1);
- Foo.GetTypeName(a1);
- pushValue(l, true);
- return ;
- }
- catch(Exception e)
- {
- return error(l, e);
- }
- }// 删了一些头头尾尾
如果没有非泛型方法GetTypeName(System.Type type), 它是不会有这个注册函数的, 我们先直接改这里:
- //Foo.GetTypeName(a1);
- Test.CallGenericFunction(typeof(Foo), "GetTypeName", null, new Type[] { a1 }, null, true); // 直接调用换成了封装的调用, 函数放在一个Test类里面
lua调用进入这里, 注意这里是非泛型方法的入口, 我改了C#那边把调用转到了泛型方法
- import "Foo";
- Foo.GetTypeName(typeof(int)); // 输出 Int32
正确的, 那么应该修改的生成代码逻辑在 LuaCodeGen.cs 里面, 具体就不测试了, 逻辑就改为在反射获取泛型方法之后, 将泛型方法跟非泛型做对比, 然后采取合并之类的逻辑进行代码生成. 这个就看自己的控制逻辑了,
因为退化后的泛型跟非泛型同名同然而逻辑不同的情况是存在的, 如果逻辑不同的话, 就麻烦了...
- public static void GetTypeName(System.Type type); // 逻辑1
- public static void GetTypeName<T>();// 退化->GetTypeName(System.Type type) 逻辑2
在只有GetTypeName<T>()方法时, 那就像上图的生成代码替换掉Foo.GetTypeName(a1) 为 Test.CallGenericFunction(typeof(Foo), ...) 即可.
两个方法都有时, 任选其一即可. 当然存在函数重载的实现也在这里进行即可, 原来的函数重载就是在这实现的.
第二种: 把泛型方法转换成非泛型然后添加到原有类型中, ILGenerator 太复杂, 写个例子就行了, 再见!!!
PS: 这个使用方法不能对已经存在的类型进行修改, 是创建了一个类型, 当然我们可以把有泛型的类型生成一个新的类型来用非泛型代替泛型, 然后利用lua实现多重继承或者强行注册的方式实现最终的整合.
然而, 创建新类型去调用原有类型的泛型方法, 不是静态的需要传递实例作为参数, 然后在自动生成代码处自动封装, 这些逻辑会复杂上天了.
- public class Test{
- public static System.Type TestILGenerator()
- {
- const string funcName = "SayHello";
- //构建程序集
- var asmName = new AssemblyName("Test");
- var asmBuilder = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);
- //构建模块
- var mdlBldr = asmBuilder.DefineDynamicModule("Main", "Main.dll");
- //构建类
- var typeBldr = mdlBldr.DefineType("Hello", TypeAttributes.Public);
- //构建方法
- var methodBldr = typeBldr.DefineMethod(
- funcName,
- MethodAttributes.Public | MethodAttributes.Static,
- null,//return type
- null//parameter type
- );
- //IL构建底层细节
- var il = methodBldr.GetILGenerator();//获取il生成器
- il.Emit(OpCodes.Ldstr, "Hello, World");
- il.Emit(OpCodes.Call, typeof(UnityEngine.Debug).GetMethod("Log", new Type[] { typeof(string) }));
- il.Emit(OpCodes.Ret);
- //完成构建
- var t = typeBldr.CreateType();
- t.GetMethod(funcName).Invoke(null, null); // 输出 Hello, World
- return t;
- }
- }
我在这里测试了一下, 创建类型Hello以及设置静态函数SayHello都是成功了的, 这样就欺骗了编译器认为这个类型存在了, 用LuaCodeGen也能强行生成注册类函数:
- [MenuItem("SLua/Custom/Make2")] // 强行搞一个代码创建
- static public void Custom2()
- {
- List<Type> exports = new List<Type>();
- string path = GenPath + "Custom/";
- ExportGenericDelegate fun = (Type t, string ns) =>
- {
- if(Generate(t, ns, path))
- exports.Add(t);
- };
- var myType = Test.TestILGenerator();
- fun(myType, null);
- }
生成出的代码就懵逼了哈哈, 在生成的时候骗了编译器, 生成之后是骗不了人的, 下面的生成的Lua_Hello.cs 文件.
- [UnityEngine.Scripting.Preserve]
- public class Lua_Hello : LuaObject {
- [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
- [UnityEngine.Scripting.Preserve]
- static public int constructor(IntPtr l) {
- try {
- Hello o;
- o=new Hello(); // 注意这里编译错误, 因为没有实体的类型, 编译器无法找到Hello类
- pushValue(l,true);
- pushValue(l,o);
- return ;
- }
- catch(Exception e) {
- return error(l,e);
- }
- }
- [SLua.MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
- [UnityEngine.Scripting.Preserve]
- static public int SayHello_s(IntPtr l) {
- try {
- Hello.SayHello();
- pushValue(l,true);
- return ;
- }
- catch(Exception e) {
- return error(l,e);
- }
- }
- [UnityEngine.Scripting.Preserve]
- static public void reg(IntPtr l) {
- getTypeTable(l,"Hello");
- addMember(l,SayHello_s);
- createTypeMetatable(l,constructor, typeof(Hello));
- }
- }
这种方法到这里基本上就是死刑了, 可是还可以抢救一下, 就是在运行时进行类型的生成, 然后Lua注册代码, 也就是Lua_Hello.cs里面这些, 也在运行时生成, 然后完成运行时的注册行为...
恩...所以说来说去, 最方便的还是在C#中写上非泛型方法最方便了, 本文也是突发奇想研究弄一下泛型方法在lua中的自动注册问题.
这里的简单例子中泛型和非泛型没有严格的使用限制, 所以可以在C#中两种都写, 实际情况可能有些情况写不了非泛型的情况也有, 这种情况下通过lua调用CallGenericFunction方法来调用泛型方法反而比自动注册简单多了...
脱裤子放屁的事情就别做了, 直接调反射吧.
补充(2019.04.16):
- MakeGenericMethod 这个方法在AOT编译下能不能使用还不清楚, 因为类型不确定的时候是在运行时生成类型, 比如泛型类在运行时生成就不行,
比如 AA<T> 这个泛型类, 使用 typeof(AA<>).MakeGenericType(typeof(int)); 就不行, 需要后续测试
一种C#泛型方法在lua中表示的设计的更多相关文章
- lua中for循环的四种遍历方式
lua中for的四种遍历方式区别 table.maxn 取最大的整数key #table 从1开始的顺序整数最大值,如1,2,3,6 #table == 3 key,value pairs 取每一 ...
- Lua中使用table实现的其它5种数据结构
Lua中使用table实现的其它5种数据结构 lua中的table不是一种简单的数据结构,它可以作为其他数据结构的基础,如:数组,记录,链表,队列等都可以用它来表示. 1.数组 在lua中,table ...
- lua中遍历table的几种方式比较
当我在工作中使用lua进行开发时,发现在lua中有4种方式遍历一个table,当然,从本质上来说其实都一样,只是形式不同,这四种方式分别是: for key, value in pairs(tbtes ...
- lua中,两种json和table互转方法的效率比较
lua中json和table的互转,是我们在平时开发过程中经常用到的.比如: 在用lua编写的服务器中,如果客户端发送json格式的数据,那么在lua处理业务逻辑的时候,必然需要转换成lua自己的数据 ...
- lua中基类和“继承机制”
基类:基类定义了所有对于派生类来说普通的属性和方法,派生类从基类继承所需的属性和方法,且在派生类中增加新的属性和方法. 继承:继承是C++语言的一种重要机制,它允许在已定义的类的基础上产生新类. lu ...
- lua中的table、stack和registery
ok,前面准备给一个dll写wrapper,写了篇日志,看似写的比较明白了,但是其实有很多米有弄明白的.比如PIL中使用的element,key,tname,field这些,还是比较容易混淆的.今天正 ...
- lua 中的面向对象
lua 是一种脚步语言,语言本身并不具备面向对象的特性. 但是我们依然可以利用语言的特性,模拟出面向对象的特性. 面向对象的特性通常会具备:封装,继承,多态的特性,如何在lua中实现这些特性,最主要的 ...
- Lua中的weak表——weak table
弱表(weak table)是一个很有意思的东西,像C++/Java等语言是没有的.弱表的定义是:A weak table is a table whose elements are weak ref ...
- Lua中的协同程序 coroutine
Lua中的协程和多线程很相似,每一个协程有自己的堆栈,自己的局部变量,可以通过yield-resume实现在协程间的切换.不同之处是:Lua协程是非抢占式的多线程,必须手动在不同的协程间切换,且同一时 ...
随机推荐
- Java MySQL数据类型对照
Java MySQL数据类型对照 类型名称 显示长度 数据库类型 JAVA类型 JDBC类型索引(int) 描述 varchar L+N VARCHAR java.lang.S ...
- django复习-3-请求与响应
一.请求request 前端向后端传递参数有几种方式? 提取URL的特定部分,如/weather/beijing/2018,可以在服务器端的路由中用正则表达式截取: "http://127. ...
- 《JavaScript高级程序设计》读书笔记--ECMAScript中所有函数的参数都是按值传递的
ECMAScript中所有函数的参数都是按值传递的.也就是说把函数外部的值复制给函数内部的参数(内部参数的值的修改不影响实参的值). 基本类型变量的复制: 基本类型变量的复制,仅仅是值复制,num1和 ...
- MongoDB3.2配置文件.md
Core Options systemLog Options systemLog: verbosity: <int> quiet: <boolean> traceAllExce ...
- 6、JUC--同步锁Lock
显示锁 Lock 在Java 5.0之前,协调共享对象的访问时可以使用的机 制只有 synchronized 和 volatile .Java 5.0 后增加了一些 新的机制,但并不是一种替代内置 ...
- ubuntu16.04下的htk安装编译
HTK(HMM Tools Kit)是一个剑桥大学开发的专门用于建立和处理HMM的实验工具包[1],主要应用于语音识别领域,也可以应用于语音合成.字符识别和DNA排序等领域.HTK经过剑桥大学.Ent ...
- Leetcode——121. 买卖股票的最佳时机
题目描述:买卖股票的最佳时机 题目要求求解能获得最大利润的方式? 可以定一个二维数组 d [ len ] [ 2 ] ,其中d[ i ][ 0 ] 表示前i天可以获得的最大利润:d[ i ][ 1 ] ...
- Android分享到微信和朋友圈的工具类
1.只要填写上正确的app_id,且引用上该工具类你就能实现分享到朋友圈和分享到微信. 2.需要到微信平台下载jar包,以及注册一个appid import android.content.Conte ...
- odoo之自动生成编号问题
单独的seq.xml文件 <?xml version="1.0" encoding="utf-8"?><openerp> <dat ...
- 详细解读大数据分析引擎Pig&PigLatin语句
Pig 一.Pig的介绍: Pig由Yahoo开发,主要应用于数据分析,Twitter公司大量使用Pig处理海量数据,Pig之所以是数据分析引擎,是因为Pig相当于一个翻译器,将PigLatin语句翻 ...