Ruby envoyer vs __send__

151

Je comprends le concept some_instance.sendmais j'essaie de comprendre pourquoi vous pouvez appeler cela dans les deux sens. Les Ruby Koans impliquent qu'il y a une raison au-delà de fournir de nombreuses façons différentes de faire la même chose. Voici les deux exemples d'utilisation:

class Foo
  def bar?
    true
  end
end

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

Quelqu'un a une idée à ce sujet?

Jaydel
la source

Réponses:

242

Certaines classes (par exemple la classe socket de la bibliothèque standard) définissent leur propre sendméthode qui n'a rien à voir avec Object#send. Donc, si vous voulez travailler avec des objets de n'importe quelle classe, vous devez utiliser __send__pour être du bon côté.

Maintenant, cela laisse la question, pourquoi il y en a sendet pas seulement __send__. S'il n'y en avait que __send__le nom sendpourrait être utilisé par d'autres classes sans aucune confusion. La raison en est qu'il sendexistait en premier et seulement plus tard, il a été réalisé que le nom sendpouvait également être utilisé utilement dans d'autres contextes, donc a __send__été ajouté (c'est la même chose qui s'est produite avec idet object_idd'ailleurs).

sepp2k
la source
8
De plus, BasicObject (introduit dans Ruby 1.9) a seulement __send__, pas send.
Andrew Marshall
Bonne réponse. Cela pourrait être encore mieux s'il est mentionné public_send, ce qui est souvent préférable à de sendtoute façon.
Marc-André Lafortune
31

Si vous avez vraiment besoin sendde vous comporter comme d'habitude, vous devriez l'utiliser __send__, car il ne sera pas (il ne devrait pas) être remplacé. L'utilisation __send__est particulièrement utile dans la métaprogrammation, lorsque vous ne savez pas quelles méthodes la classe manipulée définit. Cela aurait pu être remplacé send.

Regarder:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

Si vous remplacez __send__, Ruby émettra un avertissement:

avertissement: la redéfinition de «__send__» peut causer de sérieux problèmes

Certains cas où il serait utile de remplacer sendseraient ceux où ce nom est approprié, comme le passage de messages, les classes de socket, etc.

Thiago Silveira
la source
9

__send__ existe donc il ne peut pas être écrasé par accident.

Quant à savoir pourquoi sendexiste: je ne peux parler pour personne d'autre, mais object.send(:method_name, *parameters)ça a l'air plus beau que object.__send__(:method_name, *parameters), donc j'utilise sendsauf si j'ai besoin d'utiliser __send__.

Andrew Grimm
la source
6

En dehors de ce que d'autres vous ont déjà dit, et ce qui revient à dire cela sendet que ce __send__sont deux alias de la même méthode, vous pourriez être intéressé par la troisième possibilité, quelque peu différente, qui est public_send. Exemple:

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

Mise à jour: Depuis Ruby 2.1 Module#includeet les Module#extendméthodes deviennent publiques, l'exemple ci-dessus ne fonctionnera plus.

Boris Stitnicky
la source
0

La principale différence entre send __send__et public_send est la suivante.

  1. send et __send__sont techniquement les mêmes que ceux utilisés pour appeler la méthode Object, mais la principale différence est que vous pouvez remplacer la méthode send sans aucun avertissement et lorsque vous remplacez, __send__il y a un message d'avertissement

avertissement: la redéfinition __send__peut entraîner de graves problèmes

En effet, pour éviter les conflits, en particulier dans les gemmes ou les bibliothèques lorsque le contexte dans lequel il sera utilisé est inconnu, utilisez toujours __send__au lieu d'envoyer.

  1. La différence entre send (ou __send__) et public_send est que send / __send__peut appeler les méthodes privées d'un objet, et public_send ne le peut pas.
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)

À la fin, essayez d'utiliser public_send pour éviter un appel direct à une méthode privée au lieu d'utiliser __send__ ou send.

Kishor Vyavahare
la source