由于目前要把大量的代码移植到 lua 中(真是够虐心的),面向对象肯定少不了,项目的代码都是这么设计的,于是就测试 Slua.Class 接口来扩展 C# 的类,发现有点问题,给作者提交了一个 Issue 和 一个 Pull Request,作者也很快确认并 Merge 了。

  问题是这样:当使用 Slua.Class 继承出来的类,实例化出来的所有实例都指向了最后一个实例,导致访问属性都是一样的。比如使用 main.txt 中得一段代码修改测试:

  1. -- test inherite class
  2. local mv = My2(, , )
  3. local mv_2 = My2(, , ) -- I add for test.
  4. mv:Normalize()
  5. mv_2:Normalize() -- I add for test.
  6. print("mv norm:", mv.x, mv.y, mv.z) -- I modified for test.
  7. print("mv_2 norm:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.
  8. mv:Set(, , ) -- I modified for test.
  9. mv_2:Set(, , ) -- I add for test.
  10. print("mv:", mv.x, mv.y, mv.z) -- I add for test.
  11. print("mv_2:", mv_2.x, mv_2.y, mv_2.z) -- I add for test.

结果将输出如下:

  1. mv norm: 0.62469504755442 0.78086880944303 0.93704257133164
  2. mv_2 norm: 0.62469504755442 0.78086880944303 0.93704257133164
  3. mv: 40 50 60
  4. mv_2: 40 50 60

  在以上结果中,My2 的实例 my, my_2 构造的值是不同的,但输出相同的结果。看看 Slua.Class 的代码,在 Helper.cs 中:

  1. local getmetatable=getmetatable
  2. local function Class(base,static,instance)
  3. local mt = getmetatable(base)
  4. local class=static or {}
  5. setmetatable(class,
  6. {
  7. __call=function(...)
  8. local r = mt.__call(...)
  9. local ret = instance or {}
  10. ret.__base=r
  11. local ret = setmetatable(ret,{
  12. __index=function(t,k)
  13. return r[k]
  14. end,
  15. __newindex=function(t,k,v)
  16. r[k]=v
  17. end,
  18. })
  19. return ret
  20. end,
  21. }
  22. )
  23. return class
  24. end
  25. return Class

  以上代码中,ret 是类的模板,用来为各个实例化对象提供方法和属性,不应该被构造时返回(而且上面每次构造都返回了相同的一个 ret),但是 ret 应该是大家 shaderd,构造返回的对象应该是一个新构造的对象,且 __index 为 ret,这样既能获取派生类的各种方法属性,又不会不小心修改 ret。

  同时,我做了如下的一些小修改:

  1. 可以直接使用派生类调用积累的静态成员方法,如基类 Base.ShowStatic(),那么派生类可以直接使用:Derived.ShowStatic();
  2. 增加了一个名为 ctor 的可选构造函数(这个借鉴了云风给出的 lua-oop 方案);
  3. 保持通过访问父类方法使用 __base,但注意不用使用这个来访问父类成员变量,因为当你第一次在派生类访问父类变量,会被复制到派生类,所以可能会访问到错误的数据,只有派生类的才是有效的。

  修改完的代码如下:

  1. local getmetatable = getmetatable
  2. local function Class(base,static,instance)
  3. local mt = getmetatable(base)
  4. local class = static or {}
  5. setmetatable(class,
  6. {
  7. __index = base,
  8. __call = function(...)
  9. local r = mt.__call(...)
  10. local ret = instance or {}
  11. local ins_ret = setmetatable(
  12. {
  13. __base = r,
  14. },
  15. {
  16. __index = function(t, k)
  17. local ret_field
  18. ret_field = ret[k]
  19. if nil == ret_field then
  20. ret_field = r[k]
  21. end
  22. t[k] = ret_field
  23. return ret_field
  24. end,
  25. })
  26. if ret.ctor then
  27. ret.ctor(ins_ret, ...)
  28. end
  29. return ins_ret
  30. end,
  31. }
  32. )
  33. return class
  34. end
  35. return Class

  使用跟以前一样,但可以增加一个构造函数:

  1. MyVector3 = Slua.Class(Vector3,
  2. {
  3. },
  4. {
  5. -- This is optional.
  6. ctor = function(self)
  7. print("Do something...")
  8. end,
  9. })

  但是我觉得还是有点小问题,以上书写新的扩展类代码的时候不是太方便,不能分开单独写每个成员变量和函数,也可以墙纸分开,但命名上不太好看,于是我自己又做了如下修改:

  1. local getmetatable = getmetatable
  2. local function Class(base)
  3. local mt = getmetatable(base)
  4. local class = {}
  5. class.ctor = false
  6. setmetatable(class,
  7. {
  8. __index = base,
  9. __call = function(...)
  10. local r = mt.__call(...)
  11. local ins_ret = {__base = r,}
  12. setmetatable(ins_ret,
  13. {
  14. __index = function(t, k)
  15. local ret_field
  16. ret_field = rawget(class, k)
  17. if nil == ret_field then
  18. ret_field = r[k]
  19. if 'function' == type(ret_field) then
  20. class[k] = ret_field
  21. else
  22. ins_ret[k] = ret_field
  23. end
  24. end
  25. return ret_field
  26. end,
  27. })
  28.  
  29. if class.ctor then
  30. class.ctor(ins_ret, ...)
  31. end
  32.  
  33. return ins_ret
  34. end,
  35. }
  36. )
  37. return class
  38. end
  39. return Class

这样的话,我就可以更方便的定义类,符合以前的书写习惯,同时,优化一下,当访问派生类不存在的的父类成员时,之拷贝函数,不拷贝成员变量,以免浪费空间。这样我可以这样书写:

  1. MyVector3 = Slua.Class(Vector3)
  2.  
  3. -- Constructor, optional.
  4. function MyVector3:ctor()
  5. print("Do something!")
  6. end
  7.  
  8. -- Instance method.
  9. function MyVector3:Normaize()
  10. --Do your own normalize.
  11. end
  12.  
  13. -- Static method.
  14. function MyVector3.PrintMyName
  15. print("MyVector3")
  16. end

  但作者说如果不是 bug,只是为了方便,最后这个不能修改,因为要考虑兼容性,已经有人这么用了,确实是这样,所以我就把这个提交到自己的另一个分支里,在自己的项目使用新方法。

 
 

SLua 中继承 C# 类接口 Slua.Class 的一个 Bug。的更多相关文章

  1. Java中继承thread类与实现Runnable接口的区别

    Java中线程的创建有两种方式: 1.  通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2.  通过实现Runnable接口,实例化Thread类 在实际应用中, ...

  2. Java基础知识强化之多线程笔记05:Java中继承thread类 与 实现Runnable接口的区别

    1. Java中线程的创建有两种方式:  (1)通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中. (2)通过实现Runnable接口,实例化Thread类. 2. ...

  3. [转] Java中继承thread类与实现Runnable接口的区别

    Java中线程的创建有两种方式: 1.  通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2.  通过实现Runnable接口,实例化Thread类 在实际应用中, ...

  4. 多线程——Java中继承Thread类与实现Runnable接口的区别

    线程我只写过继承Thread类的,后来知道java多线程有三种方式,今天首先比较一下常用的继承Thread类和实现Runnable接口的区别. 按着Ctrl键进入Thread之后,发现Thread类也 ...

  5. java中继承thread类的其他类的start()方法与run()方法

    java中继承thread或者实现runnable接口的类必须重写run()方法. 如果其执行了start()方法,其实就是启动了线程的run()方法. 注意:如果是实现runnable接口的类是没有 ...

  6. Java中继承,类的高级概念的知识点

    1. 继承含义 在面向对象编程中,可以通过扩展一个已有的类,并继承该类的属性和行为,来创建一个新的类,这种方式称为继承(inheritance). 2. 继承的优点 A.代码的可重用性 B.子类可以扩 ...

  7. 使用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 ...

  8. struts 中继承ActionSupport类

    理论上Struts 2.0的Action无须实现任何接口或继承任何类型,但是,我们为了方便实现Action,大多数情况下都会继承 com.opensymphony.xwork2.ActionSuppo ...

  9. JDBC中重要的类/接口-Connection、DriverManager、ResultSet、Statement及常用方法

    DriverManager(管理一组 JDBC 驱动程序的基本服务) 它的方法: getConnection(String url, String user, String password) 试图建 ...

随机推荐

  1. 查看编译后的calss文件编译jdk版本

    使用UtralEdit或者sublime text打开编译后的.class文件, 其中cafe babe为magic number(魔数),标识这个文件是java的class文件. 0033转换成10 ...

  2. Druid 简单介绍

    官方网址:http://code.alibabatech.com/wiki/display/Druid/Home 1.什么是Druid Druid首先是一个数据库连接池.Druid是目前最好的数据库连 ...

  3. 04_过滤器Filter_02_Filter解决中文乱码问题

    [过滤器解决中文乱码问题实例] [工程截图] [web.xml] <?xml version="1.0" encoding="UTF-8"?> &l ...

  4. ScrollView 尽量避免嵌套RelativeLayout,非常惨痛的教训

    <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android=&q ...

  5. 用Session实现验证码

    新建一个 ashx 一般处理程序 如: YZM.ashx继承接口 IRequiresSessionState //在一般处理程序里面继承 HttpContext context 为请求上下文,包含此次 ...

  6. 高性能网络I/O框架-netmap源码分析

    from:http://blog.chinaunix.net/uid-23629988-id-3594118.html 博主这篇文章写的很好 感觉很有借签意义 值得阅读 高性能网络I/O框架-netm ...

  7. echo、print、print_r、printf、sprintf、var_dump的区别比较

    一.echoecho() 实际上不是一个函数,是php语句,因此您无需对其使用括号.不过,如果您希望向 echo() 传递一个以上的参数,那么使用括号会发生解析错误.而且echo是返回void的,并不 ...

  8. node.js操作mongoDB数据库

    链接数据库: var mongo=require("mongodb"); var host="localhost"; var port=mongo.Connec ...

  9. (转载)delphi实例TDBGrid用右键菜单复制行粘贴行

    delphi实例TDBGrid用右键菜单复制行粘贴行 这个从本质上来说就是DBGrid后台数据库的插入 右键复制当前行的相关数据到临时变量点粘贴时,覆盖数据或插入数据! db为数据库: 字段名id,n ...

  10. hadoop2——新MapReduces——yarm详解

    YARN总体上仍然是Master/Slave结构,在整个资源管理框架中,ResourceManager为Master,NodeManager为Slave,ResourceManager负责对各个Nod ...