Apa cara paling elegan untuk menulis metode "Coba" di C # 7?


21

Saya menulis jenis implementasi Antrian yang memiliki TryDequeuemetode yang menggunakan pola yang mirip dengan berbagai TryParsemetode .NET , di mana saya mengembalikan nilai boolean jika tindakan berhasil, dan menggunakan outparameter untuk mengembalikan nilai dequeued aktual.

public bool TryDequeue(out Message message) => _innerQueue.TryDequeue(out message);

Sekarang, saya suka menghindari outparams kapan pun saya bisa. C # 7 memberi kita delcarations variabel untuk membuatnya bekerja lebih mudah, tapi saya masih menganggap params lebih dari kejahatan yang diperlukan daripada alat yang berguna.

Perilaku yang saya inginkan dari metode ini adalah sebagai berikut:

  • Jika ada item yang harus didekor, kembalikan.
  • Jika tidak ada item yang harus di-dequeue (antrian kosong), berikan pemanggil informasi yang cukup untuk bertindak dengan tepat.
  • Jangan hanya mengembalikan item nol jika tidak ada item yang tersisa.
  • Jangan melemparkan pengecualian jika mencoba keluar dari antrian kosong.

Saat ini, pemanggil metode ini hampir selalu menggunakan pola seperti berikut (menggunakan sintaks variabel C # 7):

if (myMessageQueue.TryDequeue(out Message dequeued))
    MyMessagingClass.SendMessage(dequeued)
else
    Console.WriteLine("No messages!"); // do other stuff

Yang bukan yang terburuk, semua diceritakan. Tetapi saya merasa bahwa mungkin ada cara yang lebih baik untuk melakukan ini (saya benar-benar mau mengakui bahwa mungkin tidak ada). Aku benci bagaimana penelepon harus memecah alirannya dengan syarat ketika semua yang diinginkannya adalah mendapatkan nilai jika ada.

Apa beberapa pola lain yang ada untuk mencapai perilaku "coba" yang sama?

Untuk konteks, metode ini berpotensi disebut dalam proyek VB, jadi poin bonus untuk sesuatu yang berfungsi baik di keduanya. Namun, fakta ini seharusnya memiliki bobot yang sangat kecil.


9
Tentukan Option<T>struct dan kembalikan. bool Try(..., out data)Fungsi - fungsi itu adalah kekejian.
CodesInChaos

2
Saya berpikir serupa ... Mungkin <T>, Opsi <T> jika ada yang salah dengan parameter OUT.
Jon Raynor

1
@FrustratedWithFormsDesigner dengan baik, saya belum banyak "mencoba" hal-hal lain (permainan kata-kata selamat datang), sebanyak memikirkan mereka. Saya punya ide untuk mengembalikan ValueTuple tetapi yang terbaik menurut saya tidak menawarkan banyak peningkatan.
Eric Sondergard

@CodesInChaos Kami berada di halaman yang sama, itu sebabnya saya di sini! Dan saya suka ide ini. Jika Anda punya waktu, maukah Anda memberikan lebih banyak detail dalam jawaban sehingga saya bisa menerimanya?
Eric Sondergard

Jawaban:


24

Gunakan tipe Opsi, yang berarti objek tipe yang memiliki dua versi, biasanya disebut "Beberapa" (ketika ada nilai) atau "Tidak Ada" (ketika tidak ada nilai) ... Atau sesekali mereka disebut Adil dan Tidak Ada. Kemudian ada fungsi dalam tipe ini yang memungkinkan Anda mengakses nilai jika ada, menguji keberadaan, dan yang paling penting metode yang memungkinkan Anda menerapkan fungsi yang mengembalikan Opsi lebih lanjut ke nilai jika ada (biasanya dalam C # ini harus disebut FlatMap meskipun dalam bahasa lain sering disebut Bind, bukan ... Namanya sangat penting dalam C # karena memiliki metode nama dan tipe ini mari kita gunakan objek Opsi Anda dalam pernyataan LINQ).

Fitur tambahan dapat mencakup metode seperti IfPresent dan IfNotPresent untuk meminta tindakan dalam kondisi yang relevan, dan OrElse (yang menggantikan nilai default ketika tidak ada nilai tetapi tidak ada op sebaliknya), dan sebagainya.

Contoh Anda kemudian mungkin terlihat seperti:

myMessageQueue.TryDeque()
    .IfPresent( dequeued => MyMessagingClass.SendMessage(dequeued))
    .IfNotPresent (() =>  Console.WriteLine("No messages!")

Ini adalah pola Opsi (atau Mungkin) monad, dan ini sangat berguna. Ada implementasi yang ada (mis. Https://github.com/nlkl/Optional/blob/master/README.md ) tetapi tidak sulit untuk melakukannya juga.

(Anda mungkin ingin memperpanjang pola ini sehingga Anda mengembalikan deskripsi penyebab kesalahan alih-alih apa-apa ketika metode gagal ... Ini sepenuhnya dapat dicapai dan sering disebut monis Either; seperti namanya Anda dapat menggunakan FlatMap yang sama pola untuk membuatnya mudah untuk bekerja dengan dalam hal itu, juga)


Ini jelas merupakan pilihan yang baik, terima kasih atas saran dan pemikiran Anda. Saya benar-benar ingin mengeksplorasi lebih banyak ide di sini.
Eric Sondergard

2
Yang menyenangkan tentang Option/ Maybeadalah bahwa itu adalah monad (jika diterapkan sebagai satu, tentu saja), yang berarti dapat dirantai dengan aman, dibungkus, dibuka, dan diproses tanpa perlu menangani kasus yang berbeda secara langsung, dan rantai ini dan pemrosesan dapat dilakukan dengan Ekspresi Kueri LINQ.
Jörg W Mittag

3
By the way, flatMap/ binddisebut SelectManydi NET.
Jörg W Mittag

5
Saya ingin tahu apakah itu akan menjadi ide yang lebih baik untuk memilih nama yang berbeda, karena dengan melakukan ini Anda melanggar Try...konvensi penamaan di .NET (dan berpotensi membingungkan pengelola).
Bob

@ Bob Itu poin yang bagus. Saya setuju.
Eric Sondergard

12

Jawaban yang diberikan bagus dan saya akan pergi dengan salah satunya. Anggap jawaban ini hanya mengisi beberapa sudut dengan beberapa ide alternatif:

  • Buatlah subclass dari yang Messagedipanggil SomeMessagedan NoMessage. Dequeuesekarang dapat kembali NoMessagejika tidak ada pesan, dan SomeMessagejika ada pesan. Jika callee peduli untuk mendeteksi di mana mereka berada, mereka dapat dengan mudah melakukannya dengan mengetik inspeksi. Jika mereka tidak, well, NoMessagelakukan saja apa-apa setiap kali ada metode yang dipanggil, dan hei, mereka mendapatkan apa yang mereka minta.

  • Sama seperti di atas, tetapi Messagesecara implisit dikonversi menjadi bool(atau mengimplementasikan operator true / operator false). Sekarang Anda dapat mengatakan if (message)dan memilikinya benar jika pesannya baik dan salah jika itu buruk. (Ini hampir pasti ide yang buruk, tetapi saya memasukkannya untuk kelengkapan!)

  • C # 7 memiliki tupel. Kembalikan (bool, Message)tuple.

  • Buat Messagetipe struct dan kembali Message?.


Hai, terima kasih atas jawaban Anda. Dengan pertanyaan ini saya berusaha mengekspos diri saya pada ide-ide yang berbeda dengan mencoba mencari solusi untuk masalah spesifik saya. Tepuk tangan!
Eric Sondergard

Maksud saya, jika "ide buruk" Anda digunakan oleh Unity, mengapa kita tidak bisa menggunakannya?
Arturo Torres Sánchez

@ ArturoTorresSánchez: Pertanyaan Anda adalah "orang lain menggunakan praktik pemrograman yang sangat buruk, jadi mengapa saya tidak bisa?" Pertanyaannya tidak jelas. Tidak ada yang menghentikan Anda dari menggunakan praktik pemrograman buruk yang sama seperti orang lain. Anda terus maju. Itu tidak akan membuatnya menjadi praktik yang baik di C #.
Eric Lippert

Itu adalah lidah di pipi. Saya kira saya perlu menggunakan "/ s" untuk menandainya.
Arturo Torres Sánchez

@ ArturoTorresSánchez: Ini adalah situs tanya jawab; Saya menganggap pertanyaan sebagai pertanyaan yang mencari jawaban .
Eric Lippert

10

Di C # 7 Anda dapat menggunakan pencocokan pola untuk mencapai hal yang sama dengan cara yang lebih elegan:

if (myMessageQueue.TryDequeue() is Message dequeued) 
{
     MyMessagingClass.SendMessage(dequeued)
} 
else 
{
    Console.WriteLine("No messages!"); // do other stuff
}

Dalam kasus khusus ini, saya mungkin akan menggunakan acara daripada polling antrian berulang kali.


3
Bukankah itu perlu TryDequeuemengembalikan beberapa antarmuka marker dengan Messageimplementasi dan beberapa implementasi "tidak ada"? Tentu, ini lebih elegan di situs panggilan, tetapi Anda terjebak menerapkan 3 implementasi dalam kode aktual, yang itu sendiri mungkin tidak mungkin jika Messagemerupakan tipe yang sudah ada.
Telastyn

1
@ Telastyn: Ini juga berfungsi jika hanya mengembalikan nol . Anda juga bisa menggunakan jenis Opsi.
JacquesB

@ JacquesB: tetapi bagaimana jika null adalah item antrian yang valid?
JBSnorro

5

Tidak ada yang salah dengan metode Coba ... (memiliki parameter keluar) untuk skenario di mana kegagalan (tidak ada nilai) sama umum dengan keberhasilan.

Tetapi, jika Anda bersikeras melakukan sesuatu secara berbeda, Anda mungkin ingin mengembalikan pesan kosong dan hanya mengirimkannya (bukan masalah Anda lagi) atau menunda pernyataan if sampai Anda benar-benar harus tahu apakah Anda memiliki pesan dengan konten atau tidak.

Perhatikan bahwa bagaimanapun juga seseorang harus melakukan tes. Saya berpendapat tes harus dilakukan kapan dan di mana pertanyaannya saat ini. Ini pada saat dequeing.


Anda membuat poin yang bagus. Anda masih harus menguji, apa pun yang Anda lakukan. Jujur, saya masih mungkin akan keluar dengan parameter pada saat ini (sebenarnya saya saat ini mengekspos beberapa implementasi "TryDequeue" sekarang hanya untuk mengutak-atik masing-masing). Saya benar-benar hanya ingin membahas opsi lain yang mungkin ada di luar sana.
Eric Sondergard
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.