Ruby invia vs __send__


151

Capisco il concetto di some_instance.sendma sto cercando di capire perché puoi chiamarlo in entrambi i modi. I Ruby Koans implicano che c'è qualche motivo oltre a fornire molti modi diversi per fare la stessa cosa. Ecco i due esempi di utilizzo:

class Foo
  def bar?
    true
  end
end

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

Qualcuno ha qualche idea su questo?

Risposte:


242

Alcune classi (ad esempio la classe socket della libreria standard) definiscono il proprio sendmetodo che non ha nulla a che fare con Object#send. Quindi, se vuoi lavorare con oggetti di qualsiasi classe, devi usare __send__per stare al sicuro.

Ora questo lascia la domanda, perché c'è sende non solo __send__. Se ci fosse solo __send__il nome sendpotrebbe essere usato da altre classi senza alcuna confusione. La ragione di ciò è che sendesisteva prima e solo dopo si è capito che il nome sendpoteva anche essere utilmente usato in altri contesti, quindi è __send__stato aggiunto (è la stessa cosa che è successo con ide object_idtra l'altro).


8
Inoltre, BasicObject (introdotto in Ruby 1.9) ha solo __send__, no send.
Andrew Marshall,

Buona risposta. Potrebbe essere anche meglio se menzionato public_send, che spesso è preferibile sendcomunque.
Marc-André Lafortune,

31

Se hai davvero bisogno senddi comportarti come farebbe normalmente, dovresti usarlo __send__, perché non (non dovrebbe) essere annullato. L'uso __send__è particolarmente utile nella metaprogrammazione, quando non si conoscono i metodi definiti dalla classe manipolata. Potrebbe aver scavalcato send.

Orologio:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

Se esegui l'override __send__, Ruby emetterà un avviso:

attenzione: la ridefinizione di __send__ 'può causare seri problemi

Alcuni casi in cui sarebbe utile eseguire l'override sendsarebbero quelli in cui il nome è appropriato, come il passaggio di messaggi, le classi socket, ecc.


9

__send__ esiste quindi non può essere sovrascritto per errore.

Per quanto riguarda il motivo per cui sendesiste: Non posso parlare per chiunque altro, ma object.send(:method_name, *parameters)sembra più bello di quanto object.__send__(:method_name, *parameters), per cui uso senda meno che non ho bisogno di usare __send__.


6

A parte ciò che altri ti hanno già detto e ciò che si riduce a dirlo sende __send__sono due alias dello stesso metodo, potresti essere interessato alla terza possibilità, un po 'diversa, che è public_send. Esempio:

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

Aggiornamento: da Ruby 2.1 Module#includee i Module#extendmetodi diventano pubblici, quindi l'esempio sopra non funzionerebbe più.


0

La differenza principale tra send __send__, e public_send è la seguente.

  1. send e __send__sono tecnicamente identici a quelli usati per chiamare il metodo Object, ma la differenza principale è che puoi ignorare il metodo send senza alcun avviso e quando esegui l'override, __send__viene visualizzato un messaggio di avviso

attenzione: la ridefinizione __send__può causare seri problemi

Questo perché per evitare conflitti, specialmente nelle gemme o nelle librerie quando il contesto in cui verrà utilizzato è sconosciuto, utilizzare sempre __send__invece di inviare.

  1. La differenza tra send (o __send__) e public_send è che send / __send__può chiamare i metodi privati ​​di un oggetto e public_send non può.
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)

Alla fine prova a usare public_send per evitare la chiamata diretta al metodo privato invece di usare __send__ o send.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.