Ruby send vs __send__


151

Я розумію концепцію, some_instance.sendале я намагаюся з'ясувати, чому ви можете називати це обома способами. "Рубі Коанс" означає, що є кілька причин, крім того, що існує безліч різних способів зробити те саме. Ось два приклади використання:

class Foo
  def bar?
    true
  end
end

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

Хтось має про це уявлення?

Відповіді:


242

Деякі класи (наприклад, клас сокета стандартної бібліотеки) визначають власний sendметод, який не має нічого спільного Object#send. Тож якщо ви хочете працювати з об’єктами будь-якого класу, вам потрібно використовуватись __send__з безпечної сторони.

Тепер це залишає питання, чому існує, sendа не просто __send__. Якби тільки __send__ім'я, sendможна було б використати інші класи без будь-якої плутанини. Причиною тому є те, що sendіснувало спочатку, і лише пізніше було зрозуміло, що ім'я sendтакож може бути корисно використане в інших контекстах, тому __send__було додано (це те саме, що трапилося idі object_idдо речі).


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__, Рубі надсилатиме попередження:

попередження: переозначення `__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__технічно такі ж, як і для виклику методу Object, але головна відмінність полягає в тому, що ви можете перекрити метод надсилання без будь-якого попередження, і коли ви перекриєте, __send__то з’являється повідомлення попередження

попередження: повторне визначення __send__може спричинити серйозні проблеми

Це тому, що щоб уникнути конфліктів, особливо в дорогоцінних каменях або бібліотеках, коли контекст, де він буде використовуватися, невідомий, завжди використовуйте __send__замість пересилання.

  1. Різниця між send (або __send__) та public_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__ або надсилати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.