Ruby send vs _ _ send _ _

我理解 some_instance.send的概念,但是我想弄明白为什么你可以把它称为双向的。Ruby Koans 暗示,除了提供许多不同的方法来做同样的事情之外,还有一些其他的原因。下面是两个用法例子:

class Foo
def bar?
true
end
end


foo = Foo.new
foo.send(:bar?)
foo.__send__(:bar?)

有人知道这是怎么回事吗?

25006 次浏览

有些类(例如标准库的套接字类)定义自己的 send方法,这个方法与 Object#send没有任何关系。因此,如果您想使用任何类的对象,那么为了安全起见,您需要使用 __send__

现在问题来了,为什么有 send而不仅仅是 __send__。如果只有 __send__的名称 send可以被其他类使用没有任何混淆。原因是 send先存在,后来才意识到 send这个名字也可以用在其他场合,所以增加了 __send__(顺便说一下,idobject_id也是这样)。

如果 真的需要 send按照正常的方式运行,那么应该使用 __send__,因为它不会(不应该)被覆盖。当您不知道被操纵的类定义了什么方法时,使用 __send__在元编程中特别有用。它可能覆盖了 send

注意:

class Foo
def bar?
true
end


def send(*args)
false
end
end


foo = Foo.new
foo.send(:bar?)
# => false
foo.__send__(:bar?)
# => true

如果覆盖 __send__,Ruby 将发出一个警告:

警告: 重新定义‘ _ _ send _ _’可能 造成严重的问题

在某些情况下,覆盖 send是有用的,比如消息传递、套接字类等。

__send__的存在使得它不能被意外覆盖。

至于 send为什么存在: 我不能代表任何人,但是 object.send(:method_name, *parameters)看起来比 object.__send__(:method_name, *parameters)好,所以我使用 send,除非我使用 需要来使用 __send__

除了其他人已经告诉你的,以及归结为说 send__send__是同一个方法的两个别名之外,你可能会对第三种不同的可能性感兴趣,那就是 public_send。例如:

A, B, C = Module.new, Module.new, Module.new
B.include A #=> error -- private method
B.send :include, A #=> bypasses the method's privacy
C.public_send :include, A #=> does not bypass privacy

更新: 自从 Ruby 2.1,Module#includeModule#extend方法变成公共的,所以上面的例子不再起作用。

Send、 __send__和 public _ send 之间的主要区别如下。

  1. Send 和 __send__在技术上与用于调用 Object 的方法相同,但主要区别在于您可以在没有任何警告的情况下重写 send 方法,并且当您重写 __send__时,会有一条警告消息

警告: 重新定义 __send__可能会导致严重的问题

这是因为为了避免冲突,特别是在 gems 或库中,当使用它的上下文是未知的时候,总是使用 __send__而不是 send。

  1. Send (或 __send__)和 public _ send 之间的区别在于 send/__send__可以调用对象的私有方法,而 public _ send 不能。
class Foo
def __send__(*args, &block)
"__send__"
end
def send(*args)
"send"
end
def bar
"bar"
end
private
def private_bar
"private_bar"
end
end


Foo.new.bar #=> "bar"
Foo.new.private_bar #=> NoMethodError(private method 'private_bar' called for #Foo)


Foo.new.send(:bar) #=> "send"
Foo.new.__send__(:bar) #=> "__send__"
Foo.new.public_send(:bar) #=> "bar"


Foo.new.send(:private_bar) #=> "send"
Foo.new.__send__(:private_bar) #=> "__send__"
Foo.new.public_send(:private_bar) #=> NoMethodError(private method 'private_bar' called for #Foo)

最后,尝试使用 public _ send 避免直接调用私有方法,而不是使用 _ _ send _ _ 或 send。