Sesuatu yang sering saya gunakan kembali di C ++ membiarkan kelas A
menangani kondisi masuk dan keluar negara untuk kelas lainB
, melalui A
konstruktor dan destruktor, untuk memastikan bahwa jika sesuatu dalam lingkup itu melemparkan pengecualian, maka B akan memiliki keadaan yang diketahui ketika ruang lingkup telah keluar. Sejauh akronimnya, ini bukanlah RAII murni, tetapi ini merupakan pola yang mapan.
Di C #, saya sering ingin melakukannya
class FrobbleManager
{
...
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
this.Frobble.Lock();
}
}
Yang perlu dilakukan seperti ini
private void FiddleTheFrobble()
{
this.Frobble.Unlock();
try
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
finally
{
this.Frobble.Lock();
}
}
jika saya ingin menjamin Frobble
negara saat FiddleTheFrobble
kembali. Kode akan lebih bagus dengan
private void FiddleTheFrobble()
{
using (var janitor = new FrobbleJanitor(this.Frobble))
{
Foo(); // Can throw
this.Frobble.Fiddle(); // Can throw
Bar(); // Can throw
}
}
di mana FrobbleJanitor
terlihat kasar seperti
class FrobbleJanitor : IDisposable
{
private Frobble frobble;
public FrobbleJanitor(Frobble frobble)
{
this.frobble = frobble;
this.frobble.Unlock();
}
public void Dispose()
{
this.frobble.Lock();
}
}
Dan itulah cara saya ingin melakukannya. Sekarang kenyataan menyusul, karena apa yang ingin saya gunakan mengharuskan yang FrobbleJanitor
digunakan dengan using
. Saya dapat menganggap ini sebagai masalah peninjauan kode, tetapi ada sesuatu yang mengganggu saya.
Pertanyaan: Apakah hal di atas akan dianggap sebagai penggunaan using
dan penyalahgunaan IDisposable
?
sumber
Jawaban:
Saya rasa tidak, tentu saja. IDisposable teknis yang dimaksudkan untuk digunakan untuk hal-hal yang memiliki sumber daya non-dikelola, tapi kemudian direktif menggunakan adalah cara rapi menerapkan pola umum
try .. finally { dispose }
.Seorang purist akan membantah 'ya - itu kasar', dan dalam arti purist itu; tetapi kebanyakan dari kita tidak membuat kode dari perspektif purist, tetapi dari perspektif semi-artistik. Menggunakan konstruksi 'menggunakan' dengan cara ini memang cukup artistik menurut saya.
Anda mungkin harus menempelkan antarmuka lain di atas ID sekali pakai untuk mendorongnya sedikit lebih jauh, menjelaskan kepada pengembang lain mengapa antarmuka itu menyiratkan IDisposable.
Ada banyak alternatif lain untuk melakukan ini tetapi, pada akhirnya, saya tidak dapat memikirkan apa pun yang akan serapi ini, jadi lakukanlah!
sumber
using(Html.BeginForm())
Tuan? tidak keberatan jika saya melakukannya Pak! :)Saya menganggap ini sebagai penyalahgunaan pernyataan penggunaan. Saya sadar bahwa saya termasuk minoritas dalam posisi ini.
Saya menganggap ini sebagai pelecehan karena tiga alasan.
Pertama, karena saya berharap "menggunakan" digunakan untuk menggunakan sumber daya dan membuangnya setelah Anda selesai menggunakannya . Mengubah status program tidak menggunakan sumber daya dan mengubahnya kembali tidak membuang apa pun. Oleh karena itu, "menggunakan" untuk mengubah dan memulihkan status adalah penyalahgunaan; kode tersebut menyesatkan bagi pembaca biasa.
Kedua, karena saya berharap "menggunakan" digunakan karena kesopanan, bukan karena kebutuhan . Alasan Anda menggunakan "menggunakan" untuk membuang file setelah selesai bukan karena itu perlu , tetapi karena itu sopan - orang lain mungkin menunggu untuk menggunakan file itu, jadi mengatakan "selesai sekarang "adalah hal yang benar secara moral untuk dilakukan. Saya berharap bahwa saya harus dapat melakukan refaktorisasi "penggunaan" sehingga sumber daya yang digunakan disimpan lebih lama, dan dibuang nanti, dan satu-satunya dampak dari melakukan hal itu adalah sedikit mengganggu proses lainnya . Blok "menggunakan" yang memiliki dampak semantik pada status program disalahgunakan karena menyembunyikan mutasi status program yang penting dan diperlukan dalam sebuah konstruksi yang terlihat seperti itu ada untuk kenyamanan dan kesopanan, bukan kebutuhan.
Dan ketiga, tindakan program Anda ditentukan oleh statusnya; kebutuhan untuk memanipulasi keadaan secara hati-hati adalah alasan mengapa kita melakukan percakapan ini sejak awal. Mari pertimbangkan bagaimana kami dapat menganalisis program asli Anda.
Apakah Anda membawa ini ke tinjauan kode di kantor saya, pertanyaan pertama yang akan saya tanyakan adalah "apakah benar mengunci frobble jika ada pengecualian?" Jelas terlihat dari program Anda bahwa hal ini secara agresif mengunci kembali frobble apa pun yang terjadi. Apakah itu benar? Pengecualian telah dilemparkan. Status program tidak diketahui. Kami tidak tahu apakah Foo, Fiddle atau Bar melempar, mengapa mereka melempar, atau mutasi apa yang mereka lakukan ke keadaan lain yang tidak dibersihkan. Dapatkah Anda meyakinkan saya bahwa dalam situasi yang mengerikan itu selalu hal yang benar untuk dilakukan dengan mengunci kembali?
Mungkin ya, mungkin juga tidak. Maksud saya adalah, dengan kode sebagaimana aslinya ditulis, peninjau kode tahu untuk mengajukan pertanyaan . Dengan kode yang menggunakan "using", saya tidak tahu harus bertanya; Saya berasumsi bahwa blok "menggunakan" mengalokasikan sumber daya, menggunakannya sebentar, dan dengan sopan membuangnya ketika selesai, bukan bahwa kurung kurawal tutup dari blok "menggunakan" mengubah status program saya dalam keadaan luar biasa ketika banyak kondisi konsistensi status program telah dilanggar.
Penggunaan blok "using" untuk mendapatkan efek semantik membuat program ini menjadi fragmen:
sangat berarti. Ketika saya melihat satu close brace, saya tidak langsung berpikir "bahwa brace memiliki efek samping yang berdampak luas pada status global program saya". Tetapi ketika Anda menyalahgunakan "menggunakan" seperti ini, tiba-tiba hal itu terjadi.
Hal kedua yang akan saya tanyakan jika saya melihat kode asli Anda adalah "apa yang terjadi jika pengecualian dilemparkan setelah Buka kunci tetapi sebelum percobaan dimasukkan?" Jika Anda menjalankan rakitan yang tidak dioptimalkan, kompilator mungkin telah memasukkan instruksi no-op sebelum mencoba, dan ada kemungkinan pengecualian thread abort terjadi pada no-op. Ini jarang terjadi, tetapi memang terjadi dalam kehidupan nyata, terutama di server web. Dalam hal ini, buka kunci terjadi tetapi kunci tidak pernah terjadi, karena pengecualian dilemparkan sebelum percobaan. Sangat mungkin bahwa kode ini rentan terhadap masalah ini, dan seharusnya ditulis
Sekali lagi, mungkin ya, mungkin tidak, tapi saya tahu untuk mengajukan pertanyaan . Dengan versi "menggunakan", ini rentan terhadap masalah yang sama: pengecualian pembatalan utas dapat dilemparkan setelah Frobble dikunci tetapi sebelum wilayah coba dilindungi yang terkait dengan penggunaan dimasukkan. Tetapi dengan versi "menggunakan", saya berasumsi bahwa ini adalah "jadi apa?" situasi. Sangat disayangkan jika itu terjadi, tetapi saya berasumsi bahwa "menggunakan" hanya untuk bersikap sopan, bukan untuk mengubah status program yang sangat penting. Saya berasumsi bahwa jika beberapa pengecualian pembatalan utas yang buruk terjadi pada waktu yang salah, oh baiklah, pengumpul sampah akan membersihkan sumber daya itu pada akhirnya dengan menjalankan finalizer.
sumber
using
merupakan masalah kesopanan daripada kebenaran. Saya percaya bahwa kebocoran sumber daya adalah masalah kebenaran, meskipun sering kali cukup kecil sehingga bukan bug dengan prioritas tinggi. Jadi,using
blok sudah memiliki dampak semantik pada status program, dan apa yang mereka cegah bukan hanya "ketidaknyamanan" pada proses lain tetapi mungkin lingkungan yang rusak. Itu tidak berarti bahwa jenis dampak semantik yang berbeda yang disiratkan oleh pertanyaan juga sesuai.using
karena ini adalah penenun yang berorientasi pada aspek orang miskin di C #. Apa yang sebenarnya diinginkan pengembang adalah cara untuk menjamin bahwa beberapa kode umum akan berjalan di akhir blok tanpa harus menduplikasi kode itu. C # tidak mendukung AOP hari ini (MarshalByRefObject & PostSharp tidak bertahan). Namun, transformasi penyusun dari pernyataan using memungkinkan untuk mencapai AOP sederhana dengan mengubah tujuan semantik pembuangan sumber daya deterministik. Meskipun bukan praktik yang baik, terkadang menarik untuk dilewatkan .....using
terlalu menarik untuk dilepaskan. Contoh terbaik yang dapat saya pikirkan adalah melacak dan melaporkan waktu kode:using( TimingTrace.Start("MyMethod") ) { /* code */ }
Sekali lagi, ini adalah AOP -Start()
merekam waktu mulai sebelum blok dimulai,Dispose()
mencatat waktu akhir, dan mencatat aktivitas. Itu tidak mengubah status program dan agnostik terhadap pengecualian. Ini juga menarik karena menghindari sedikit pipa ledeng dan sering digunakan sebagai pola mengurangi semantik yang membingungkan.Jika Anda hanya ingin kode yang bersih dan terbatas, Anda juga dapat menggunakan lambda, á la
di mana
.SafeExecute(Action fribbleAction)
metode ini membungkus bloktry
-catch
-finally
.sumber
SafeExecute
adalahFribble
metode yang memanggilUnlock()
danLock()
dalam percobaan / akhirnya dibungkus. Saya minta maaf. Saya segera melihatSafeExecute
sebagai metode ekstensi generik dan oleh karena itu menyebutkan kurangnya status masuk dan keluar. Salahku.Eric Gunnerson, yang berada di tim desain bahasa C #, memberikan jawaban ini untuk pertanyaan yang hampir sama:
sumber
using
fitur itu tidak terbatas pada pembuangan sumber daya.Ini adalah lereng yang licin. IDisposable memiliki kontrak, yang didukung oleh finalizer. Finalizer tidak berguna dalam kasus Anda. Anda tidak dapat memaksa klien untuk menggunakan pernyataan using, hanya dorong dia untuk melakukannya. Anda bisa memaksanya dengan cara seperti ini:
Tapi itu cenderung agak canggung tanpa lamda. Yang mengatakan, saya telah menggunakan IDisposable seperti yang Anda lakukan.
Namun ada detail dalam posting Anda yang membuat ini sangat mirip dengan anti-pola. Anda menyebutkan bahwa metode tersebut dapat memunculkan pengecualian. Ini bukanlah sesuatu yang dapat diabaikan oleh penelepon. Dia dapat melakukan tiga hal tentang itu:
Dua yang terakhir mengharuskan pemanggil untuk secara eksplisit menulis blok percobaan. Sekarang pernyataan use menghalangi. Ini mungkin menyebabkan klien menjadi koma yang membuatnya percaya bahwa kelas Anda menjaga status dan tidak ada pekerjaan tambahan yang perlu dilakukan. Itu hampir tidak pernah akurat.
sumber
SafeExecute
sebagai metode penyuluhan umum. Saya melihat sekarang bahwa kemungkinan itu dimaksudkan sebagaiFribble
metode yang memanggilUnlock()
danLock()
dalam percobaan / akhirnya dibungkus. Salahku.Contoh dunia nyata adalah BeginForm dari ASP.net MVC. Pada dasarnya Anda bisa menulis:
atau
Html.EndForm memanggil Dispose, dan Dispose hanya mengeluarkan
</form>
tag. Hal baiknya tentang ini adalah {} tanda kurung membuat "cakupan" yang terlihat yang membuatnya lebih mudah untuk melihat apa yang ada di dalam formulir dan apa yang tidak.Saya tidak akan terlalu sering menggunakannya, tetapi pada dasarnya IDisposable hanyalah cara untuk mengatakan "Anda HARUS memanggil fungsi ini setelah Anda selesai". MvcForm menggunakannya untuk memastikan formulir ditutup, Aliran menggunakannya untuk memastikan aliran ditutup, Anda dapat menggunakannya untuk memastikan objek tidak terkunci.
Secara pribadi saya hanya akan menggunakannya jika dua aturan berikut ini benar, tetapi aturan tersebut saya atur secara arbiter:
Pada akhirnya, ini semua tentang ekspektasi. Jika sebuah objek mengimplementasikan IDisposable, saya menganggapnya perlu melakukan pembersihan, jadi saya menyebutnya. Saya pikir biasanya lebih baik daripada memiliki fungsi "Shutdown".
Karena itu, saya tidak suka baris ini:
Karena FrobbleJanitor "memiliki" Frobble sekarang, saya bertanya-tanya apakah tidak lebih baik jika memanggil Fiddle di Frobble di Janitor?
sumber
Kami memiliki banyak penggunaan pola ini di basis kode kami, dan saya telah melihatnya di semua tempat sebelumnya - saya yakin itu pasti telah dibahas di sini juga. Secara umum saya tidak melihat apa yang salah dengan melakukan ini, ini memberikan pola yang berguna dan tidak menyebabkan kerugian nyata.
sumber
Menimpali ini: Saya setuju dengan sebagian besar di sini bahwa ini rapuh, tetapi berguna. Saya ingin mengarahkan Anda ke kelas System.Transaction.TransactionScope , yang melakukan sesuatu seperti yang ingin Anda lakukan.
Umumnya saya suka sintaksnya, ini menghilangkan banyak kekacauan dari daging asli. Harap pertimbangkan untuk memberi nama yang baik pada kelas helper - mungkin ... Scope, seperti contoh di atas. Nama harus memberikan yang mengenkapsulasi sepotong kode. * Scope, * Block atau yang serupa harus melakukannya.
sumber
Catatan: Sudut pandang saya mungkin bias dari latar belakang C ++ saya, jadi nilai jawaban saya harus dievaluasi terhadap kemungkinan bias itu ...
Apa Kata Spesifikasi Bahasa C #?
Mengutip Spesifikasi Bahasa C # :
The Code yang menggunakan sumber daya , tentu saja, kode mulai dengan
using
kata kunci dan pergi sampai lingkup melekat padausing
.Jadi saya rasa ini baik-baik saja karena Kunci adalah sumber daya.
Mungkin kata kunci
using
itu dipilih dengan buruk. Mungkin seharusnya dipanggilscoped
.Kemudian, kita dapat menganggap hampir semua hal sebagai sumber daya. Sebuah pegangan file. Koneksi jaringan ... Sebuah utas?
Seutas ???
Menggunakan (atau menyalahgunakan)
using
kata kunci?Akankah mengkilap untuk (ab) menggunakan
using
kata kunci untuk memastikan pekerjaan utas diakhiri sebelum keluar dari ruang lingkup?Herb Sutter tampaknya menganggapnya berkilau , karena ia menawarkan penggunaan yang menarik dari pola IDispose untuk menunggu pekerjaan utas selesai:
http://www.drdobbs.com/go-parallel/article/showArticle.jhtml?articleID=225700095
Ini kodenya, copy-paste dari artikel:
Sementara kode C # untuk objek aktif tidak disediakan, ada tertulis C # menggunakan pola IDispose untuk kode yang versi C ++ terkandung dalam destruktor. Dan dengan melihat versi C ++, kita melihat destruktor yang menunggu utas internal berakhir sebelum keluar, seperti yang ditunjukkan dalam ekstrak artikel lainnya ini:
Jadi, sejauh menyangkut Herb, itu berkilau .
sumber
Saya yakin jawaban atas pertanyaan Anda adalah tidak, ini bukan penyalahgunaan
IDisposable
.Cara saya memahami
IDisposable
antarmuka adalah bahwa Anda tidak seharusnya bekerja dengan suatu objek setelah dibuang (kecuali bahwa Anda diizinkan untuk memanggilDispose
metodenya sesering yang Anda inginkan).Karena Anda membuat baru
FrobbleJanitor
secara eksplisit setiap kali Anda masuk keusing
pernyataan, Anda tidak pernah menggunakanFrobbeJanitor
objek yang sama dua kali. Dan karena tujuannya adalah untuk mengelola objek lain,Dispose
tampaknya sesuai untuk tugas membebaskan sumber daya ini ("dikelola").(Btw., Kode contoh standar yang mendemonstrasikan implementasi yang tepat
Dispose
hampir selalu menyarankan bahwa sumber daya yang dikelola harus dibebaskan, juga, bukan hanya sumber daya yang tidak terkelola seperti pegangan sistem file.)Satu-satunya hal yang secara pribadi saya khawatirkan adalah kurang jelasnya apa yang terjadi
using (var janitor = new FrobbleJanitor())
dibandingkan dengantry..finally
blok yang lebih eksplisit di manaLock
&Unlock
operasi terlihat secara langsung. Tapi pendekatan mana yang diambil mungkin tergantung pada preferensi pribadi.sumber
Itu tidak kasar. Anda menggunakan mereka untuk apa mereka diciptakan. Tetapi Anda mungkin harus mempertimbangkan satu sama lain sesuai dengan kebutuhan Anda. Misalnya jika Anda memilih 'kesenian' maka Anda dapat menggunakan 'menggunakan' tetapi jika bagian kode Anda dieksekusi berkali-kali, maka untuk alasan kinerja Anda dapat menggunakan konstruksi 'coba' .. 'akhirnya'. Karena 'menggunakan' biasanya melibatkan kreasi suatu objek.
sumber
Saya pikir Anda melakukannya dengan benar. Overloading Dispose () akan menjadi masalah yang kelas yang sama kemudian harus bersihkan itu benar-benar harus dilakukan, dan masa pembersihan itu berubah menjadi berbeda dari waktu ketika Anda berharap untuk memegang kunci. Tetapi karena Anda membuat kelas terpisah (FrobbleJanitor) yang bertanggung jawab hanya untuk mengunci dan membuka kunci Frobble, semuanya sudah cukup dipisahkan sehingga Anda tidak akan mencapai masalah itu.
Saya akan mengganti nama FrobbleJanitor, mungkin menjadi sesuatu seperti FrobbleLockSession.
sumber