鉴于以下课程:
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>
这有什么理由吗?这是一个错误吗?
在Ruby 2.2.3p173上测试
答案 0 :(得分:3)
让我们首先注意在Ruby中,正如您可能知道的那样,在类a
上声明的方法Foo
中,我可以在 的任何实例上调用受保护的方法Foo
。
Ruby如何确定我们是否在类Foo
中声明的方法中?要理解这一点,我们将不得不深入研究方法调用的内部。我将使用2.2版本的MRI示例,但可能在其他版本中的行为和实现是相同的(我很乐意看到在JRuby或Rubinious上测试这些结果)。
Ruby在rb_call0
中执行此操作。正如评论所示,self
用于确定我们是否可以调用受保护的方法。在rb_call
中从当前线程的调用帧信息中检索self
。然后,在rb_method_call_status
中,我们检查self
的此值是否与定义受保护方法的类相同。
块有点混淆了这个问题。请记住,Ruby方法中的局部变量由该方法中声明的任何块捕获。这意味着在块中,self
与调用该方法的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。
所以,现在我们理解为什么可以在方法的块中调用同一个类的另一个实例上的受保护方法。
现在让我们看一下使用&:bar
创建的proc无法执行此操作的原因。当我们在方法参数之前放置&
符号时,我们做两件事:指示ruby将此参数作为块传递,并指示它在其上调用to_proc
。
这意味着调用方法Symbol#to_proc
。此方法在C中实现,但是当我们调用C方法时,当前帧上指向self
的指针成为该C方法的接收者 - 在这种情况下,它变为符号:bar
。所以我们正在查看foo
我们得到的实例,就像我们在类类的方法中一样,我们无法调用受保护的方法。
这是满口的,但希望它足够有意义。如果您对如何改进它有任何建议,请告诉我们!