将匿名类分配给常量时是否有钩子?

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中有很多钩子方法,但我找不到这个.

Nik*_* B. 6

让我们来看看内部的常量赋值是如何工作的.后面的代码是从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_namespaceINC_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(这是资源的巨大浪费,恕我直言).