开发者

Given a Ruby metaclass, how do I get the instance to which it is attached?

This is the inverse of the question "Given an instance of a Ruby object, how do I get its metaclass?"

You can see a representation of the object to which a metaclass or singleton class is attached in the default to_s output:

s = "hello"
s_meta = class << s; self; end
s_meta.to_s # => "#<Class:#<String:0x1500开发者_JAVA百科4dd>>"

class C; end
c_meta = class << C; self; end
c_meta.to_s # => "#<Class:C>"

Is it possible to implement a method Class.attached that returns this object (or nil if the receiver is a regular class)?

s_meta.attached # => s
c_meta.attached # => C
C.attached # => nil


There's an ugly (yet working) hack, using ObjectSpace. Like, something that you should never use except for playing and perhaps debugging. You just want its first (and only) instance, so:

ObjectSpace.each_object(self).first

To determine whether it's a singleton class, you can use the weird property that ancestors will not include its receiver if it's a singleton class (or eigenclass, or magical class):

ObjectSpace.each_object(self).first unless ancestors.include? self

If you care about edgecases, there are three objects whose classes are also their singleton classes.

[true, false, nil].each do |o|
   o.class.send(:define_method, :attached) { o }
 end


I don't know about MRI.

In JRuby, the following returns what you want:

require 'java'
class A
  def self.meta
    class << self; self; end
  end
end

A.meta.to_java.attached


You can define metaclass to store the attached object.

class Class
  attr_accessor :attached
end

class Object
  def metaclass
    meta = class << self; self; end
    meta.attached = self
    meta
  end
end

class A; end

a = A.new
a_meta = a.metaclass
p a                     #=> #<A:0xb74ed768>
p a_meta                #=> #<Class:#<A:0xb74ed768>>

obj = a_meta.attached
p obj                   #=> #<A:0xb74ed768>

puts obj == a           #=> true
p A.attached            #=> nil


You can get it from inspect (in MRI implementation):

class Class
  def attached
    # first, match the object reference from inspect
    o_ref = inspect.match /0x([0-9a-f]+)>>$/

    # if not found, it's not a metaclass
    return nil unless o_ref

    # calculate the object id from the object reference    
    o_id = (o_ref[1].to_i(16) >> 1) - 0x80000000

    # get the object from its id
    ObjectSpace._id2ref o_id
  end
end

# testing...
class A; end

a = A.new 
a_meta = class << a; self; end

p a                        #=> #<A:0xb7507b00>
p a_meta                   #=> #<Class:#<A:0xb7507b00>>
p a_meta.attached          #=> #<A:0xb7507b00>
p a == a_meta.attached     #=> true
p A.attached               #=> nil

For the relationship between object id and inspect, see this answer.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜