给出以下类:
class Foo def a dup.tap { |foo| foo.bar } end def b dup.tap(&:bar) end protected def bar puts 'bar' end end
似乎Foo#a和Foo#b应该是等效的,但是它们不是:
> Foo.new.a bar => #<Foo:0x007fe64a951ab8> > Foo.new.b NoMethodError: protected method `bar' called for #<Foo:0x007fe64a940a88>
有没有理由呢?这是一个bug吗?
测试了Ruby 2.2.3p173
解决方法
让我们从Ruby开始,你可能知道,在Foo类中声明的方法中,我可以在任何Foo实例上调用受保护的方法.
Ruby如何确定我们是否处于Foo类中声明的方法?要理解这一点,我们必须深入研究方法调用的内部.我将使用2.2版本的MRI中的示例,但推测其行为和实现在其他版本中是一样的(我希望看到在JRuby或RubinIoUs上进行测试的结果).
Ruby在rb_call0
中这样做.正如评论所示,self用于确定我们是否可以调用受保护的方法.自己在rb_call
从当前线程的调用帧信息中检索.然后,在rb_method_call_status
,我们检查自己的这个值是否是一个保护方法被定义的同一个类.
块有点混淆.请记住,Ruby方法中的局部变量由该方法中声明的任何块捕获.这意味着在块中,self是调用该方法的同一个自身.我们来看一个例子:
class Foo def give_me_a_block! puts "making a block,self is #{self}" Proc.new do puts "self in block0 is #{self}" end end end proc = Foo.new.give_me_a_block! proc.call
运行这个,我们看到Foo的相同实例在所有级别都是一样的,即使我们从完全不同的对象调用proc.
所以,现在我们明白为什么可以在一个方法的一个块内从同一个类的另一个实例上调用一个protected方法.
现在我们来看看为什么用&:bar创建的proc不能这样做.当我们放置&在方法参数之前签名,我们做两件事情:指示ruby将此参数作为块传递,并指示它调用to_proc.
这意味着调用Symbol#to_proc的方法.该方法在C中实现,但是当我们调用C方法时,当前帧上的指向self的指针将成为该C方法的接收者 – 在这种情况下,它成为符号:bar.所以我们正在看一下我们得到的foo实例,就好像我们在类Symbol中的方法一样,我们不能调用一个protected方法.
这是一口气,但希望它足够有意义.如果您有任何建议,如何改善它,请告诉我们!