为什么不能用符号来调用受保护的方法来触发?

时间:2015-12-06 22:25:01

标签: ruby

鉴于以下课程:

class Foo
  def a
    dup.tap { |foo| foo.bar }
  end

  def b
    dup.tap(&:bar)
  end

  protected

  def bar
    puts 'bar'
  end
end

似乎Foo#aFoo#b都应该是等价的,但它们不是:

> Foo.new.a
bar
 => #<Foo:0x007fe64a951ab8>

> Foo.new.b
NoMethodError: protected method `bar' called for #<Foo:0x007fe64a940a88>

这有什么理由吗?这是一个错误吗?

在Ruby 2.2.3p173上测试

1 个答案:

答案 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我们得到的实例,就像我们在类类的方法中一样,我们无法调用受保护的方法。

这是满口的,但希望它足够有意义。如果您对如何改进它有任何建议,请告诉我们!