鉴于以下课程:
class Foo
def a
dup.tap { |foo| foo.bar }
end
def b
dup.tap(&:bar)
end
protected
def bar
puts 'bar'
end
end
Run Code Online (Sandbox Code Playgroud)
好像都Foo#a和Foo#b应该是等价的,但它们不是:
> Foo.new.a
bar
=> #<Foo:0x007fe64a951ab8>
> Foo.new.b
NoMethodError: protected method `bar' called for #<Foo:0x007fe64a940a88>
Run Code Online (Sandbox Code Playgroud)
是否有一个原因?这是一个错误吗?
在Ruby 2.2.3p173上测试过
a首先让我们注意到,在 Ruby 中,您可能知道,在类上声明的方法中,我可以在的任何实例Foo上调用受保护的方法。 Foo
Ruby 如何确定我们是否处于类中声明的方法中Foo?为了理解这一点,我们必须深入研究方法调用的内部结构。我将使用 MRI 2.2 版本中的示例,但其他版本中的行为和实现可能是相同的(不过,我很乐意看到在 JRuby 或 Rubinious 上测试的结果)。
Ruby 在rb_call0. 正如评论所暗示的,self用于确定我们是否可以调用受保护的方法。 从当前线程的调用帧信息self中检索。rb_call然后,在 中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
Run Code Online (Sandbox Code Playgroud)
运行这个,我们看到相同的实例Foo在所有级别都是相同的,即使我们从完全不同的对象调用 proc。
所以,现在我们明白为什么可以从方法的块内调用同一类的另一个实例上的受保护方法。
现在让我们看看为什么用创建的过程&:bar不能做到这一点。当我们在方法参数之前放置一个&符号时,我们做了两件事:指示 ruby 将这个参数作为块传递,并指示它调用to_proc它。
这意味着调用该方法Symbol#to_proc。此方法是在 C 中实现的,但是当我们调用 C 方法时,当前帧上的指针self将成为该 C 方法的接收者 - 在本例中,它成为符号:bar。foo因此,我们正在查看我们获得的实例,就好像我们在类 Symbol 的方法中一样,并且我们无法调用受保护的方法。
这是拗口的,但希望它有足够的意义。如果您对我如何改进它有任何建议,请告诉我!