Ruby send vs __send__

151

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

class Foo
  def bar?
    true
  end
end

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

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

jaydel
источник

Ответы:

242

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

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

sepp2k
источник
8
Кроме того, BasicObject (введенный в Ruby 1.9) имеет только __send__, нет send.
Эндрю Маршалл
Хороший ответ. Может быть, даже лучше, если это упомянуто public_send, что часто предпочтительнее в sendлюбом случае.
Марк-Андре Лафортун
31

Если вам действительно нужно 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

__send__ существует, поэтому он не может быть переписан случайно.

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

Эндрю Гримм
источник
6

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

Борис Стиницкий
источник
0

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

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

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

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

Кишор Вявахаре
источник