Ruby send vs __send__

151

Saya mengerti konsep some_instance.sendtapi saya mencoba mencari tahu mengapa Anda bisa menyebut ini dua arah. Ruby Koans menyiratkan bahwa ada beberapa alasan selain memberikan banyak cara berbeda untuk melakukan hal yang sama. Berikut adalah dua contoh penggunaan:

class Foo
  def bar?
    true
  end
end

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

Adakah yang tahu tentang ini?

jaydel
sumber

Jawaban:

242

Beberapa kelas (misalnya kelas soket perpustakaan standar) mendefinisikan sendmetode mereka sendiri yang tidak ada hubungannya dengan Object#send. Jadi, jika Anda ingin bekerja dengan objek dari kelas apa pun, Anda harus menggunakan __send__untuk berada di sisi yang aman.

Sekarang tinggal bertanya, mengapa ada senddan tidak adil __send__. Jika hanya __send__ada nama yang sendbisa digunakan oleh kelas lain tanpa kebingungan. Alasan untuk itu adalah bahwa sendada terlebih dahulu dan baru kemudian disadari bahwa nama itu sendmungkin juga bermanfaat digunakan dalam konteks lain, demikian __send__ditambahkan (itu adalah hal yang sama yang terjadi dengan iddan object_idomong-omong).

sepp2k
sumber
8
Juga, BasicObject (diperkenalkan pada Ruby 1.9) hanya memiliki __send__, tidak send.
Andrew Marshall
Jawaban yang bagus. Mungkin akan lebih baik jika disebutkan public_send, yang seringkali lebih disukai daripada apa pun send.
Marc-André Lafortune
31

Jika Anda benar - benar perlu sendbersikap seperti biasanya, Anda harus menggunakan __send__, karena itu tidak akan (seharusnya tidak) diganti. Menggunakan __send__sangat berguna dalam metaprogramming, ketika Anda tidak tahu metode apa yang didefinisikan oleh kelas yang dimanipulasi. Itu bisa saja ditimpa send.

Menonton:

class Foo
  def bar?
    true
  end

  def send(*args)
    false
  end
end

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

Jika Anda menimpa __send__, Ruby akan memancarkan peringatan:

peringatan: mendefinisikan kembali `__send__ 'dapat menyebabkan masalah serius

Beberapa kasus di mana akan berguna untuk menimpa sendadalah di mana nama itu sesuai, seperti pesan yang lewat, kelas soket, dll.

Thiago Silveira
sumber
9

__send__ ada sehingga tidak dapat ditulis ulang secara tidak sengaja.

Adapun mengapa sendada: Saya tidak bisa berbicara untuk orang lain, tetapi object.send(:method_name, *parameters)terlihat lebih baik daripada object.__send__(:method_name, *parameters), jadi saya gunakan sendkecuali saya perlu menggunakannya __send__.

Andrew Grimm
sumber
6

Terlepas dari apa yang orang lain katakan kepada Anda, dan apa yang akhirnya mengatakan itu senddan __send__dua alias dari metode yang sama, Anda mungkin tertarik pada yang ketiga, kemungkinan yang agak berbeda, yaitu public_send. Contoh:

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

Pembaruan: Sejak Ruby 2.1, Module#includedan Module#extendmetode menjadi publik, jadi contoh di atas tidak akan berfungsi lagi.

Boris Stitnicky
sumber
0

Perbedaan utama antara kirim __send__,, dan public_send adalah sebagai berikut.

  1. mengirim dan __send__secara teknis sama dengan yang digunakan untuk memanggil metode Object, tetapi perbedaan utamanya adalah Anda dapat mengganti metode pengiriman tanpa peringatan apa pun dan ketika Anda menimpa __send__maka ada pesan peringatan

peringatan: mendefinisikan ulang __send__dapat menyebabkan masalah serius

Ini karena untuk menghindari konflik, khususnya di permata atau perpustakaan ketika konteks di mana itu akan digunakan tidak diketahui, selalu gunakan __send__alih-alih mengirim.

  1. Perbedaan antara send (atau __send__) dan public_send adalah bahwa send / __send__dapat memanggil metode pribadi suatu objek, dan public_send tidak bisa.
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)

Pada akhirnya cobalah untuk menggunakan public_send untuk menghindari panggilan langsung ke metode pribadi alih-alih menggunakan __send__ atau mengirim.

Kishor Vyavahare
sumber