从元类中确定类

9
在Ruby中,获取类Foo的特异类非常简单。
eigenclass = class << Foo; self; end
#=> #<Class:Foo>
eigenclass = Foo.singleton_class #2.1.0
#=> #<Class:Foo>

我对反向操作很感兴趣:从特定的类对象中获取其所有者类对象。
klass = eigenclass.owner
#=> Foo

我不确定这是否可能,因为特定的元类是Class的匿名子类,所以Foo在其继承层次结构中不存在。检查特定元类的方法列表也没有令人鼓舞的结果。eigenclass.name返回nil。唯一让我觉得有可能的事情是:

Class.new # normal anon class
#=> #<Class:0x007fbdc499a050>
Foo.singleton_class
#=> #<Class:Foo>

显然,特征类的 to_s 方法知道关于拥有者的一些信息,即使这些信息在实例化特征类时被硬编码。因此我所知道的唯一方法是一些巧妙的Object.const_getting 的hacky实现。

Object.const_get eigenclass.to_s[/^#\<Class\:(?<owner>.+)\>$/, :owner]
#=> Foo

2
更简洁地说:鉴于"foo".singleton_class的值,我们如何回到"foo" - David Grayson
3个回答

6
使用 ObjectSpace.each_object 方法传递给它单例类,以查找所有与给定单例类匹配的类:
Klass = Class.new
ObjectSpace.each_object(Klass.singleton_class).to_a  #=> [Klass]

然而,由于一个类的单例类继承自其父类的单例类,因此如果你尝试查找具有子类的类,你将会得到多个结果:

A = Class.new
B = Class.new(A)
B.singleton_class.ancestors.include?(A.singleton_class)  #=> true

candidates = ObjectSpace.each_object(A.singleton_class)
candidates.to_a  #=> [A, B]

很幸运的是,类/模块可以按继承树中的位置进行排序(与ancestors返回的顺序相同)。由于我们知道所有结果都必须是同一个继承树的一部分,因此我们可以使用max来获取正确的类:

candidates.sort.last  #=> A
ObjectSpace.each_object(B.singleton_class).max  #=> B

如果继承树中存在分支,则似乎无法正常工作:class A; end; class B < A; end; class C < A; endObjectSpace.each_object(A.singleton_class).to_a #=> [C, B, A]ObjectSpace.each_object(A.singleton_class).sort #=> ArgumentError: comparison of Class with Class failed - Chris Keele
@ChrisKeele 嗯,有趣。不幸的是,我认为没有真正优雅的方法来解决这个问题。当然,它失败了,因为它不知道是先放 B 还是先放 C。鉴于此,我认为你自己的答案可能是最可靠的,而且不会过度设计。 - Andrew Marshall
根据Module#<=>文档,如果两个模块没有关系或比较不可能,则返回nil。我想相同深度的祖先属于后一类,如果<=>不返回-101,则Enumerable#sort会引发一个错误。 - Chris Keele
1
安德鲁,有些人喜欢使用“max”而不是“sort.last”。 :-) - Cary Swoveland
@ChrisKeele:啊,所以如果这些项目不形成继承链,它就会失败 :) 很高兴听到这个消息,否则它将必须是一个超级hacky的比较函数 :P - Niklas B.
显示剩余3条评论

3
在不涉及 Ruby 实现的情况下,完善 @BroiSatse 的答案,
class A; end
class B < A; end
class C < A; end
eigenclass = A.singleton_class
ObjectSpace.each_object(eigenclass).find do |klass|
  klass.singleton_class == eigenclass
end
#=> A

这在处理子类树中的分支时也是可靠的,这也是为什么@Andrew Marshall的优美答案不起作用的唯一原因。

1
使用 ObjectSpace
e = class << 'foo'; self; end

ObjectSpace.each_object(e).first    #=> 'foo'

从eigenclass内获取对象:

class << 'foo'
  puts ObjectSpace.each_object(self).first
end

#=> 'foo'  

3
挺聪明的,但不是100%可靠:class A; end; class B < A; end; ObjectSpace.each_object(A.singleton_class).first #=> B - Chris Keele
我相信这是因为大多数实现避免在需要之前实例化特殊类,所以如果A在其定义中没有调用自己的特殊类,则在子类化B时首次调用。 - Chris Keele
你使用的是哪个 Ruby 版本?我仍然无法运行以上代码。 - BroiSatse
我目前在使用MRI 2.1.0-p0。 - Chris Keele

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接