关于语法:Ruby send vs __send__

Ruby send vs __send__

我理解some_instance.send的概念,但是我试图弄清楚为什么您可以同时使用这两种方法。 Ruby Koans暗示,除了提供许多不同的方法来执行相同操作之外,还有其他原因。 这是两个用法示例:

1
2
3
4
5
6
7
8
9
class Foo
  def bar?
    true
  end
end

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

有人对此有任何想法吗?


某些类(例如,标准库的套接字类)定义了自己的send方法,该方法与Object#send无关。因此,如果要使用任何类的对象,则需要使用__send__以便安全。

现在剩下的问题是,为什么会有send而不是__send__。如果只有__send__,其他类可以使用名称send,而不会造成任何混淆。原因是send首先存在,后来才意识到,名称send也可能在其他上下文中有用,因此添加了__send__(这与idobject_id)。


如果您真的需要send来表现正常的行为,则应该使用__send__,因为不会(不应)覆盖它。当您不知道被操纵的类定义了哪些方法时,使用__send__在元编程中特别有用。它可能已覆盖send

看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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将发出警告:

warning: redefining `__send__' may
cause serious problems

在某些情况下,覆盖send有用的地方就是该名称合适的地方,例如消息传递,套接字类等。


__send__存在,因此不会被意外覆盖。

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


除了其他人已经告诉您的内容,以及可以归结为send__send__是同一方法的两个别名之外,您可能还对第三个非常不同的可能性感兴趣,即public_send。例:

1
2
3
4
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之间的主要区别如下。

  • send和__send__在技术上与调用Object的方法相同,但是主要区别在于您可以覆盖send方法而不会发出任何警告,并且当您覆盖__send__时会出现警告消息
  • warning: redefining __send__ may cause serious problems

    这是因为要避免冲突,尤其是在gem或库中,当要使用该上下文的上下文未知时,请始终使用__send__而不是send。

  • send(或__send__)和public_send之间的区别在于send / __send__可以调用对象的私有方法,而public_send则不能。
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    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。