rdv*_*ijk 7 ruby hook metaprogramming class constants
我最近一直在练习一些Ruby元编程,并且想知道将匿名类分配给常量.
在Ruby中,可以创建一个匿名类,如下所示:
anonymous_class = Class.new # => #<Class:0x007f9c5afb21d0>
Run Code Online (Sandbox Code Playgroud)
可以创建此类的新实例:
an_instance = anonymous_class.new # => #<#<Class:0x007f9c5afb21d0>:0x007f9c5afb0330>
Run Code Online (Sandbox Code Playgroud)
现在,当将匿名类分配给常量时,该类现在具有正确的名称:
Foo = anonymous_class # => Foo
Run Code Online (Sandbox Code Playgroud)
以前创建的实例现在也是该类的实例:
an_instance # => #<Foo:0x007f9c5afb0330>
Run Code Online (Sandbox Code Playgroud)
我的问题:当匿名类被分配给常量时,是否存在钩子方法?
Ruby中有很多钩子方法,但我找不到这个.
让我们来看看内部的常量赋值是如何工作的.后面的代码是从ruby-1.9.3-p0的源tarball中提取的.首先我们看一下VM指令的定义setconstant
(用于指定常量):
# /insns.def, line 239
DEFINE_INSN
setconstant
(ID id)
(VALUE val, VALUE cbase)
()
{
vm_check_if_namespace(cbase);
rb_const_set(cbase, id, val);
INC_VM_STATE_VERSION();
}
Run Code Online (Sandbox Code Playgroud)
没有机会在这里vm_check_if_namespace
或INC_VM_STATE_VERSION
在这里放钩.所以我们看一下rb_const_set
(variable.c:1886),每次分配一个常量时调用的函数:
# /variable.c, line 1886
void
rb_const_set(VALUE klass, ID id, VALUE val)
{
rb_const_entry_t *ce;
VALUE visibility = CONST_PUBLIC;
# ...
check_before_mod_set(klass, id, val, "constant");
if (!RCLASS_CONST_TBL(klass)) {
RCLASS_CONST_TBL(klass) = st_init_numtable();
}
else {
# [snip], won't be called on first assignment
}
rb_vm_change_state();
ce = ALLOC(rb_const_entry_t);
ce->flag = (rb_const_flag_t)visibility;
ce->value = val;
st_insert(RCLASS_CONST_TBL(klass), (st_data_t)id, (st_data_t)ce);
}
Run Code Online (Sandbox Code Playgroud)
我删除了第一次在模块内部分配常量时甚至没有调用的所有代码.然后我查看了这个调用的所有函数,并没有找到一个我们可以从Ruby代码中放置一个钩子的点.这意味着,除非我遗漏了某些事情,否则没有办法勾选一个不变的任务(至少在MRI中).
更新
澄清一下:匿名类在分配后不会神奇地获得一个新名称(正如Andrew在答案中所说的那样).相反,常量名称以及类的对象ID存储在Ruby的内部常量查找表中.如果在此之后,请求了类的名称,现在可以将其解析为正确的名称(而不仅仅是Class:0xXXXXXXXX...
).
因此,对此分配作出反应的最佳方法是name
在后台工作线程的循环中检查类的类,直到它为非nil
(这是资源的巨大浪费,恕我直言).