SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。
由于目前要把大量的代码移植到 lua 中(真是够虐心的),面向对象肯定少不了,项目的代码都是这么设计的,于是就测试 Slua.Class 接口来扩展 C# 的类,发现有点问题,给作者提交了一个 Issue 和 一个 Pull Request,作者也很快确认并 Merge 了。
问题是这样:当使用 Slua.Class 继承出来的类,实例化出来的所有实例都指向了最后一个实例,导致访问属性都是一样的。比如使用 main.txt 中得一段代码修改测试:
- -- test inherite class
- local mv = My2(, , )
- local mv_2 = My2(, , ) -- I add for test.
- mv:Normalize()
- mv_2:Normalize() -- I add for test.
- print("mv norm:", mv.x, mv.y, mv.z) -- I modified for test.
- print("mv_2 norm:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.
- mv:Set(, , ) -- I modified for test.
- mv_2:Set(, , ) -- I add for test.
- print("mv:", mv.x, mv.y, mv.z) -- I add for test.
- print("mv_2:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.
结果将输出如下:
- mv norm: 0.62469504755442 0.78086880944303 0.93704257133164
- mv_2 norm: 0.62469504755442 0.78086880944303 0.93704257133164
- mv: 40 50 60
- mv_2: 40 50 60
在以上结果中,My2 的实例 my, my_2 构造的值是不同的,但输出相同的结果。看看 Slua.Class 的代码,在 Helper.cs 中:
- local getmetatable=getmetatable
- local function Class(base,static,instance)
- local mt = getmetatable(base)
- local class=static or {}
- setmetatable(class,
- {
- __call=function(...)
- local r = mt.__call(...)
- local ret = instance or {}
- ret.__base=r
- local ret = setmetatable(ret,{
- __index=function(t,k)
- return r[k]
- end,
- __newindex=function(t,k,v)
- r[k]=v
- end,
- })
- return ret
- end,
- }
- )
- return class
- end
- return Class
以上代码中,ret 是类的模板,用来为各个实例化对象提供方法和属性,不应该被构造时返回(而且上面每次构造都返回了相同的一个 ret),但是 ret 应该是大家 shaderd,构造返回的对象应该是一个新构造的对象,且 __index 为 ret,这样既能获取派生类的各种方法属性,又不会不小心修改 ret。
同时,我做了如下的一些小修改:
- 可以直接使用派生类调用积累的静态成员方法,如基类 Base.ShowStatic(),那么派生类可以直接使用:Derived.ShowStatic();
- 增加了一个名为 ctor 的可选构造函数(这个借鉴了云风给出的 lua-oop 方案);
- 保持通过访问父类方法使用 __base,但注意不用使用这个来访问父类成员变量,因为当你第一次在派生类访问父类变量,会被复制到派生类,所以可能会访问到错误的数据,只有派生类的才是有效的。
修改完的代码如下:
- local getmetatable = getmetatable
- local function Class(base,static,instance)
- local mt = getmetatable(base)
- local class = static or {}
- setmetatable(class,
- {
- __index = base,
- __call = function(...)
- local r = mt.__call(...)
- local ret = instance or {}
- local ins_ret = setmetatable(
- {
- __base = r,
- },
- {
- __index = function(t, k)
- local ret_field
- ret_field = ret[k]
- if nil == ret_field then
- ret_field = r[k]
- end
- t[k] = ret_field
- return ret_field
- end,
- })
- if ret.ctor then
- ret.ctor(ins_ret, ...)
- end
- return ins_ret
- end,
- }
- )
- return class
- end
- return Class
使用跟以前一样,但可以增加一个构造函数:
- MyVector3 = Slua.Class(Vector3,
- {
- },
- {
- -- This is optional.
- ctor = function(self)
- print("Do something...")
- end,
- })
但是我觉得还是有点小问题,以上书写新的扩展类代码的时候不是太方便,不能分开单独写每个成员变量和函数,也可以墙纸分开,但命名上不太好看,于是我自己又做了如下修改:
- local getmetatable = getmetatable
- local function Class(base)
- local mt = getmetatable(base)
- local class = {}
- class.ctor = false
- setmetatable(class,
- {
- __index = base,
- __call = function(...)
- local r = mt.__call(...)
- local ins_ret = {__base = r,}
- setmetatable(ins_ret,
- {
- __index = function(t, k)
- local ret_field
- ret_field = rawget(class, k)
- if nil == ret_field then
- ret_field = r[k]
- if 'function' == type(ret_field) then
- class[k] = ret_field
- else
- ins_ret[k] = ret_field
- end
- end
- return ret_field
- end,
- })
- if class.ctor then
- class.ctor(ins_ret, ...)
- end
- return ins_ret
- end,
- }
- )
- return class
- end
- return Class
这样的话,我就可以更方便的定义类,符合以前的书写习惯,同时,优化一下,当访问派生类不存在的的父类成员时,之拷贝函数,不拷贝成员变量,以免浪费空间。这样我可以这样书写:
- MyVector3 = Slua.Class(Vector3)
- -- Constructor, optional.
- function MyVector3:ctor()
- print("Do something!")
- end
- -- Instance method.
- function MyVector3:Normaize()
- --Do your own normalize.
- end
- -- Static method.
- function MyVector3.PrintMyName
- print("MyVector3")
- end
但作者说如果不是 bug,只是为了方便,最后这个不能修改,因为要考虑兼容性,已经有人这么用了,确实是这样,所以我就把这个提交到自己的另一个分支里,在自己的项目使用新方法。
SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。的更多相关文章
- Java中继承thread类与实现Runnable接口的区别
Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 在实际应用中, ...
- Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别
1. Java中线程的创建有两种方式: (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. ...
- [转] Java中继承thread类与实现Runnable接口的区别
Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过实现Runnable接口,实例化Thread类 在实际应用中, ...
- 多线程——Java中继承Thread类与实现Runnable接口的区别
线程我只写过继承Thread类的,后来知道java多线程有三种方式,今天首先比较一下常用的继承Thread类和实现Runnable接口的区别. 按着Ctrl键进入Thread之后,发现Thread类也 ...
- java中继承thread类的其他类的start()方法与run()方法
java中继承thread或者实现runnable接口的类必须重写run()方法. 如果其执行了start()方法,其实就是启动了线程的run()方法. 注意:如果是实现runnable接口的类是没有 ...
- Java中继承,类的高级概念的知识点
1. 继承含义 在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类,这种方式称为继承(inheritance). 2. 继承的优点 A.代码的可重用性 B.子类可以扩 ...
- 使用myeclipse开发java,解决java中继承JFrame类出现The type JFrame is not accessible due to restriction的问题
在java中创建窗体,导入了java中的JFrame类,之后会出现错误: Access restriction: The type QName is not accessible due to res ...
- struts 中继承ActionSupport类
理论上Struts 2.0的Action无须实现任何接口或继承任何类型,但是,我们为了方便实现Action,大多数情况下都会继承 com.opensymphony.xwork2.ActionSuppo ...
- JDBC中重要的类/接口-Connection、DriverManager、ResultSet、Statement及常用方法
DriverManager(管理一组 JDBC 驱动程序的基本服务) 它的方法: getConnection(String url, String user, String password) 试图建 ...
随机推荐
- 查看编译后的calss文件编译jdk版本
使用UtralEdit或者sublime text打开编译后的.class文件, 其中cafe babe为magic number(魔数),标识这个文件是java的class文件. 0033转换成10 ...
- Druid 简单介绍
官方网址:http://code.alibabatech.com/wiki/display/Druid/Home 1.什么是Druid Druid首先是一个数据库连接池.Druid是目前最好的数据库连 ...
- 04_过滤器Filter_02_Filter解决中文乱码问题
[过滤器解决中文乱码问题实例] [工程截图] [web.xml] <?xml version="1.0" encoding="UTF-8"?> &l ...
- ScrollView 尽量避免嵌套RelativeLayout,非常惨痛的教训
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=&q ...
- 用Session实现验证码
新建一个 ashx 一般处理程序 如: YZM.ashx继承接口 IRequiresSessionState //在一般处理程序里面继承 HttpContext context 为请求上下文,包含此次 ...
- 高性能网络I/O框架-netmap源码分析
from:http://blog.chinaunix.net/uid-23629988-id-3594118.html 博主这篇文章写的很好 感觉很有借签意义 值得阅读 高性能网络I/O框架-netm ...
- echo、print、print_r、printf、sprintf、var_dump的区别比较
一.echoecho() 实际上不是一个函数,是php语句,因此您无需对其使用括号.不过,如果您希望向 echo() 传递一个以上的参数,那么使用括号会发生解析错误.而且echo是返回void的,并不 ...
- node.js操作mongoDB数据库
链接数据库: var mongo=require("mongodb"); var host="localhost"; var port=mongo.Connec ...
- (转载)delphi实例TDBGrid用右键菜单复制行粘贴行
delphi实例TDBGrid用右键菜单复制行粘贴行 这个从本质上来说就是DBGrid后台数据库的插入 右键复制当前行的相关数据到临时变量点粘贴时,覆盖数据或插入数据! db为数据库: 字段名id,n ...
- hadoop2——新MapReduces——yarm详解
YARN总体上仍然是Master/Slave结构,在整个资源管理框架中,ResourceManager为Master,NodeManager为Slave,ResourceManager负责对各个Nod ...