Saya adalah pengembang C ++ yang berpengalaman, saya tahu bahasanya dengan sangat terperinci dan telah menggunakan beberapa fitur spesifiknya secara intensif. Juga, saya tahu prinsip-prinsip OOD dan pola desain. Saya sekarang belajar C # tetapi saya tidak bisa menghentikan perasaan tidak bisa menyingkirkan pola pikir C ++. Saya mengikat diri saya dengan keras pada kekuatan C ++ sehingga saya tidak bisa hidup tanpa beberapa fitur. Dan saya tidak dapat menemukan solusi atau penggantian yang baik untuk mereka di C #.
Praktik baik apa , pola desain , idiom yang berbeda dalam C # dari perspektif C ++ yang dapat Anda sarankan? Bagaimana cara mendapatkan desain C ++ yang sempurna tanpa terlihat bodoh di C #?
Secara khusus, saya tidak dapat menemukan cara C # -ish yang baik untuk menangani (contoh terbaru):
- Mengontrol masa pakai sumber daya yang memerlukan pembersihan deterministik (seperti file). Ini mudah
using
, tetapi bagaimana menggunakannya dengan benar ketika kepemilikan sumber daya sedang ditransfer [... antara utas]? Dalam C ++ saya hanya akan menggunakan pointer bersama dan membiarkannya mengurus 'pengumpulan sampah' pada waktu yang tepat. - Perjuangan konstan dengan fungsi-fungsi utama untuk generik tertentu (Saya suka hal-hal seperti spesialisasi template parsial di C ++). Haruskah saya mengabaikan segala upaya untuk melakukan pemrograman generik dalam C #? Mungkin obat generik terbatas pada tujuan dan bukankah C # -ish untuk menggunakannya kecuali untuk domain masalah tertentu?
- Fungsionalitas seperti makro. Walaupun umumnya merupakan ide yang buruk, untuk beberapa domain masalah tidak ada solusi lain (misalnya evaluasi kondisional dari pernyataan, seperti dengan log yang seharusnya hanya untuk rilis Debug). Tidak memilikinya berarti saya harus menambahkan lebih banyak
if (condition) {...}
boilerplate dan itu masih tidak sama dalam hal memicu efek samping.
C#
untuk menghasilkan kode lain. Anda dapat membacaCSV
atauXML
atau apa yang Anda file sebagai input dan menghasilkanC#
atauSQL
file. Ini bisa lebih kuat daripada menggunakan makro fungsional.Jawaban:
Dalam pengalaman saya, tidak ada buku untuk melakukan ini. Forum membantu dengan idiom dan praktik terbaik, tetapi pada akhirnya Anda harus meluangkan waktu. Berlatih dengan memecahkan sejumlah masalah berbeda dalam C #. Lihatlah apa yang sulit / canggung dan cobalah untuk membuatnya lebih sulit dan tidak canggung lain kali. Bagi saya, proses ini memakan waktu sekitar satu bulan sebelum saya "mengerti".
Hal terbesar yang menjadi fokus adalah kurangnya manajemen memori. Dalam C ++ ini memerintah setiap keputusan desain tunggal yang Anda buat, tetapi dalam C # itu kontra produktif. Dalam C #, Anda sering ingin mengabaikan kematian atau keadaan transisi lain dari objek Anda. Asosiasi semacam itu menambahkan sambungan yang tidak perlu.
Sebagian besar, fokus pada penggunaan alat baru yang paling efektif, tidak menyalahkan diri sendiri karena keterikatan pada yang lama.
Dengan menyadari bahwa berbagai idiom mendorong ke arah pendekatan desain yang berbeda. Anda tidak bisa hanya mem-porting sesuatu 1: 1 antara (sebagian besar) bahasa dan mengharapkan sesuatu yang indah dan idiomatis di ujung yang lain.
Tetapi dalam menanggapi skenario tertentu:
Sumber daya yang tidak dikelola bersama - Ini adalah ide yang buruk di C ++, dan merupakan ide yang buruk di C #. Jika Anda memiliki file kemudian buka, gunakan, dan tutup. Membagikannya di antara utas hanya meminta masalah, dan jika hanya satu utas yang benar-benar menggunakannya maka buat utas tersebut untuk membuka / memiliki / menutupnya. Jika Anda benar - benar memiliki file yang berumur panjang karena suatu alasan, buat kelas untuk menyatakan hal itu dan biarkan aplikasi mengaturnya sesuai kebutuhan (tutup sebelum keluar, ikat dekat dengan semacam acara Penutupan Aplikasi, dll.)
Spesialisasi Templat Parsial - Anda kurang beruntung di sini, meskipun semakin saya menggunakan C # semakin saya menemukan penggunaan template yang saya lakukan di C ++ untuk jatuh ke YAGNI. Mengapa membuat sesuatu yang generik ketika T selalu int? Skenario lain paling baik diselesaikan di C # dengan aplikasi antarmuka yang liberal. Anda tidak perlu obat generik ketika antarmuka atau tipe delegasi dapat dengan kuat mengetik apa yang ingin Anda variasikan.
Persyaratan preprosesor - C # telah
#if
, gila. Lebih baik lagi, ia memiliki atribut bersyarat yang hanya akan membuang fungsi itu dari kode jika simbol kompiler tidak didefinisikan.sumber
Sejauh yang saya bisa lihat, satu-satunya hal yang memungkinkan untuk Manajemen Seumur Hidup deterministik adalah
IDisposable
, yang rusak (seperti dalam: tidak ada bahasa atau jenis dukungan sistem) ketika, seperti yang Anda perhatikan, Anda perlu Mentransfer Kepemilikan sumber daya semacam itu.Berada di kapal yang sama dengan Anda - pengembang C ++ melakukan C # atm. - kurangnya dukungan untuk memodelkan Transfer Kepemilikan (file, db-koneksi, ...) dalam tipe / tanda tangan C # sangat mengganggu bagi saya, tetapi saya harus mengakui bahwa itu bekerja cukup baik untuk "hanya" mengandalkan pada konvensi dan API API untuk mendapatkan ini dengan benar.
IDisposable
untuk melakukan pembersihan deterministik jika perlu.IDisposable
, pastikan API yang terlibat jelas tentang hal ini dan jangan gunakanusing
dalam kasus ini, karenausing
tidak dapat "dibatalkan".Ya, itu tidak seanggun yang Anda bisa ungkapkan dalam sistem tipe dengan C ++ modern (pindahkan semantik, dll.), Tapi ini menyelesaikan pekerjaan, dan (luar biasa bagi saya) komunitas C # secara keseluruhan sepertinya tidak peduli terlalu banyak, AFAICT.
sumber
Anda menyebutkan dua fitur C ++ spesifik. Saya akan membahas RAII:
Ini biasanya tidak berlaku, karena C # adalah sampah yang dikumpulkan. Konstruksi C # terdekat mungkin adalah
IDisposable
antarmuka, digunakan sebagai berikut:Anda seharusnya tidak berharap untuk
IDisposable
sering menerapkan (dan melakukannya sedikit maju), karena tidak perlu untuk sumber daya yang dikelola. Namun, Anda harus menggunakanusing
konstruk (atau menyebutDispose
diri Anda, jikausing
tidak mungkin) untuk implementasi apa punIDisposable
. Bahkan jika Anda mengacaukan ini, kelas C # yang dirancang dengan baik akan mencoba untuk menangani ini untuk Anda (menggunakan finalizers). Namun, dalam bahasa yang dikumpulkan sampah, pola RAII tidak sepenuhnya berfungsi (karena pengumpulan tidak menentukan), sehingga pembersihan sumber daya Anda akan tertunda (terkadang serempak).sumber
Disposing()
file dan mungkin harus digunakanusing
.Ini adalah pertanyaan yang sangat bagus, karena saya telah melihat sejumlah pengembang C ++ menulis kode C # yang mengerikan.
Sebaiknya jangan menganggap C # sebagai "C ++ dengan sintaks yang lebih bagus". Mereka adalah bahasa berbeda yang memerlukan pendekatan berbeda dalam pemikiran Anda. C ++ memaksa Anda untuk terus berpikir tentang apa yang akan dilakukan CPU dan memori. C # tidak seperti itu. C # khusus dirancang agar Anda tidak memikirkan CPU dan memori dan sebaliknya memikirkan domain bisnis tempat Anda menulis .
Salah satu contoh dari ini yang telah saya lihat adalah bahwa banyak pengembang C ++ akan suka menggunakan untuk loop karena mereka lebih cepat daripada foreach. Ini biasanya merupakan ide yang buruk di C # karena membatasi jenis koleksi yang mungkin diulangi (dan karena itu kegunaan ulang dan fleksibilitas kode).
Saya pikir cara terbaik untuk menyesuaikan dari C ++ ke C # adalah dengan mencoba dan mendekati pengkodean dari perspektif yang berbeda. Pada awalnya ini akan sulit, karena selama bertahun-tahun Anda akan sepenuhnya terbiasa menggunakan utas "apa yang CPU dan memori lakukan" di otak Anda untuk memfilter kode yang Anda tulis. Namun dalam C #, Anda seharusnya memikirkan hubungan antara objek dalam domain bisnis. "Apa yang ingin saya lakukan" sebagai kebalikan dari "apa yang dilakukan komputer".
Jika Anda ingin melakukan sesuatu untuk semua yang ada di daftar objek, alih-alih menulis loop for pada daftar, buat metode yang mengambil
IEnumerable<MyObject>
dan menggunakanforeach
loop.Contoh spesifik Anda:
Anda seharusnya tidak pernah melakukan ini di C # (terutama di antara utas). Jika Anda perlu melakukan sesuatu pada suatu file, lakukan di satu tempat pada satu waktu. Tidak apa-apa (dan memang praktik yang baik) untuk menulis kelas pembungkus yang mengelola sumber daya yang tidak dikelola yang melewati antara kelas Anda, tetapi jangan mencoba dan meneruskan File antara utas dan memiliki kelas terpisah menulis / membaca / menutup / membukanya. Jangan berbagi kepemilikan sumber daya yang tidak dikelola. Gunakan
Dispose
pola untuk menangani pembersihan.Generik dalam C # dirancang untuk menjadi generik. Spesialisasi generik harus ditangani oleh kelas turunan. Mengapa harus
List<T>
berperilaku berbeda jika itu adalahList<int>
atauList<string>
? Semua operasi padaList<T>
generik sehingga berlaku untuk apa punList<T>
. Jika Anda ingin mengubah perilaku.Add
metode pada aList<string>
, maka buat kelas turunanMySpecializedStringCollection : List<string>
atau kelas komposisiMySpecializedStringCollection : IList<string>
yang menggunakan generik secara internal tetapi melakukan hal-hal dengan cara yang berbeda. Ini membantu Anda menghindari melanggar Prinsip Pergantian Liskov dan secara tidak sopan meniduri orang lain yang menggunakan kelas Anda.Seperti yang orang lain katakan, Anda dapat menggunakan perintah preprocessor untuk melakukan ini. Atribut bahkan lebih baik. Secara umum, atribut adalah cara terbaik untuk menangani hal-hal yang bukan fungsi "inti" untuk sebuah kelas.
Singkatnya, ketika menulis C #, perlu diingat bahwa Anda harus berpikir sepenuhnya tentang domain bisnis dan bukan tentang CPU dan memori. Anda dapat mengoptimalkan nanti jika perlu , tetapi kode Anda harus mencerminkan hubungan antara prinsip-prinsip bisnis yang Anda coba petakan.
sumber
IDisposable
( bukan sumber daya mentah) dari satu kelas ke kelas lain: Lihat saja StreamWriter API di mana ini masuk akal bagiDispose()
aliran yang dilewati. Dan di situlah letak lubang dalam bahasa C # - Anda tidak dapat mengekspresikan ini dalam sistem tipe.Hal yang paling berbeda bagi saya adalah kecenderungan untuk segala sesuatu yang baru . Jika Anda menginginkan objek, lupa mencoba meneruskannya ke rutin dan membagikan data yang mendasarinya, tampaknya pola C # hanya untuk membuat diri Anda objek baru. Ini, secara umum, tampaknya jauh lebih dari cara C #. Pada awalnya pola ini tampak sangat tidak efisien, tetapi saya kira membuat sebagian besar objek di C # adalah operasi cepat.
Namun, ada juga kelas-kelas statis yang benar-benar mengganggu saya karena sering kali Anda mencoba membuatnya hanya untuk mengetahui Anda harus menggunakannya saja. Saya merasa tidak mudah untuk mengetahui mana yang harus digunakan.
Saya cukup senang dalam program C # untuk mengabaikannya ketika saya tidak lagi membutuhkannya, tapi ingat apa yang dikandung objek itu - umumnya Anda hanya perlu berpikir jika memiliki beberapa data 'tidak biasa', objek yang hanya terdiri dari memori akan pergi saja sendiri, orang lain harus dibuang atau Anda harus melihat cara menghancurkannya - secara manual atau dengan menggunakan blok jika tidak.
Anda akan dengan cepat mengambilnya, C # hampir tidak berbeda dari VB sehingga Anda dapat memperlakukannya sebagai alat RAD yang sama (jika itu membantu untuk memikirkan C # seperti VB.NET kemudian melakukannya, sintaksnya hanya sedikit berbeda, dan masa lalu saya menggunakan VB membuat saya menjadi pola pikir yang tepat untuk pengembangan RAD). Satu-satunya hal yang sulit adalah menentukan berapa banyak perpustakaan .net besar yang harus Anda gunakan. Google pasti teman Anda di sini!
sumber