Ruby send vs __send__ - PullRequest
       4

Ruby send vs __send__

145 голосов
/ 11 января 2011

Я понимаю концепцию some_instance.send, но я пытаюсь понять, почему вы можете назвать это обоими способами.Рубиновые коаны подразумевают, что есть не только множество разных способов сделать то же самое.Вот два примера использования:

class Foo
  def bar?
    true
  end
end

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

У кого-нибудь есть идеи по этому поводу?

Ответы [ 5 ]

229 голосов
/ 11 января 2011

Некоторые классы (например, класс сокетов стандартной библиотеки) определяют свой собственный метод send, который не имеет ничего общего с Object#send. Поэтому, если вы хотите работать с объектами любого класса, вам нужно использовать __send__, чтобы быть в безопасности.

Теперь остается вопрос, почему существует send, а не просто __send__. Если бы было только __send__, то имя send могло бы использоваться другими классами без какой-либо путаницы. Причина этого заключается в том, что send существовал первым, и только позже стало понятно, что имя send может также использоваться в других контекстах, поэтому был добавлен __send__ (это то же самое, что произошло с id object_id кстати).

31 голосов
/ 11 января 2011

Если вам действительно нужно 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, когда это имя подходит, например, передача сообщений, классы сокетов и т. д.

9 голосов
/ 28 февраля 2011

__send__ существует, поэтому его нельзя перезаписать случайно.

Что касается того, почему существует send: я не могу говорить ни за кого, но object.send(:method_name, *parameters) выглядит лучше, чем object.__send__(:method_name, *parameters), поэтому я использую send, если мне не нужно , чтобы использовать __send__.

6 голосов
/ 12 июня 2013

Помимо того, что другие уже сказали вам, и того, что сводится к тому, что 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#include и Module#extend становятся открытыми, поэтому приведенный выше пример больше не будет работать.

1 голос
/ 18 апреля 2019

Основное различие между send, __send__ и public_send заключается в следующем.

  1. send и __send__ технически аналогичны используемым для вызова метода Object, но основное отличие заключается в том, что вы можетепереопределить метод отправки без какого-либо предупреждения, и при переопределении __send__ появляется предупреждающее сообщение

предупреждение: переопределение __send__ может вызвать серьезные проблемы

Это связано с тем, что во избежание конфликтов, особенно в гемах или библиотеках, когда контекст, в котором он будет использоваться, неизвестен, всегда используйте __send__ вместо send.

Разница между 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.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...