Apakah menyalahgunakan IDisposable dan "using" sebagai sarana untuk mendapatkan "perilaku cakupan" untuk keamanan pengecualian?

112

Sesuatu yang sering saya gunakan kembali di C ++ membiarkan kelas Amenangani kondisi masuk dan keluar negara untuk kelas lainB , melalui Akonstruktor 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 Frobblenegara saat FiddleTheFrobblekembali. 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 FrobbleJanitorterlihat 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 FrobbleJanitordigunakan 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 usingdan penyalahgunaan IDisposable?

Johann Gerell
sumber
23
1 untuk nama kelas dan metode saja :-). Nah, untuk bersikap adil, dan pertanyaan sebenarnya.
Joey
2
@Johannes: Ya, saya bisa mengerti mengapa - kebanyakan orang hanya memainkan biola mereka, tapi saya harus memprotesnya. ;-) Dan, ngomong-ngomong, +1 untuk kemiripan nama Anda.
Johann Gerell
Mungkin Anda dapat menggunakan cakupan kunci (ini) {..} untuk mencapai hasil yang sama dalam contoh ini?
oɔɯǝɹ
1
@ oɔɯǝɹ: Anda menggunakan lock () untuk mencegah akses simultan ke sesuatu dari utas yang berbeda. Tidak ada hubungannya dengan skenario yang saya gambarkan.
Johann Gerell
1
IScope tanpa Finalizer di versi berikutnya dari C # :)
Jon Raynor

Jawaban:

33

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!

Andras Zoltan
sumber
2
Saya percaya ini juga merupakan penggunaan artistik dari "menggunakan". Saya pikir posting Samual Jack yang tertaut ke komentar dari Eric Gunnerson memvalidasi implementasi "menggunakan" ini. Saya dapat melihat poin luar biasa yang dibuat dari Andras dan Eric dalam hal ini.
IAbstract
1
Saya berbicara dengan Eric Gunnerson tentang hal ini beberapa waktu yang lalu dan menurut dia, maksud dari tim desain C # yang menggunakan akan menunjukkan cakupan. Bahkan, dia mendalilkan bahwa jika mereka dirancang menggunakan sebelum pernyataan kunci, bahkan mungkin tidak ada pernyataan kunci. Itu akan menjadi blok penggunaan dengan panggilan Monitor atau sesuatu. PEMBARUAN: Baru menyadari, jawaban berikutnya adalah oleh Eric Lippert yang juga anggota tim bahasa. ;) Mungkin tim C # sendiri tidak sepenuhnya setuju tentang ini? Konteks pembahasan saya w / Gunnerson adalah kelas TimedLock: bit.ly/lKugIO
Haacked
2
@Haacked - Lucu bukan; komentar Anda benar-benar membuktikan maksud saya; bahwa Anda telah berbicara dengan satu orang di tim dan dia mendukungnya; kemudian menunjukkan Eric Lippert di bawah ini, dari tim yang sama tidak setuju. Dari sudut pandang saya; C # menyediakan kata kunci yang mengeksploitasi antarmuka dan juga menghasilkan pola kode yang terlihat bagus yang berfungsi di banyak skenario. Jika kita tidak seharusnya menyalahgunakannya, maka C # harus mencari cara lain untuk menegakkannya. Either way, para desainer mungkin perlu mendapatkan bebek mereka berturut-turut di sini! Sementara itu: using(Html.BeginForm())Tuan? tidak keberatan jika saya melakukannya Pak! :)
Andras Zoltan
71

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

bool needsLock = false;
try
{
    // must be carefully written so that needsLock is set
    // if and only if the unlock happened:

    this.Frobble.AtomicUnlock(ref needsLock);
    blah blah blah
}
finally
{
    if (needsLock) this.Frobble.Lock();
}

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.

Eric Lippert
sumber
18
Meskipun saya akan menjawab pertanyaan secara berbeda, saya pikir itu adalah posting yang diperdebatkan dengan baik. Namun, saya mempermasalahkan klaim Anda yang lebih usingmerupakan 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, usingblok 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.
kvb
13
Kebocoran sumber daya adalah masalah kebenaran, ya. Namun kegagalan untuk menggunakan "menggunakan" tidak membocorkan sumber daya; sumber daya akan dibersihkan pada akhirnya saat finalizer dijalankan. Pembersihan mungkin tidak seagresif dan tepat waktu seperti yang Anda inginkan, tetapi itu akan terjadi.
Eric Lippert
7
Posting yang luar biasa, ini dua sen saya :) Saya curiga banyak "penyalahgunaan" usingkarena 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 .....
LBushkin
11
Meskipun secara umum saya setuju dengan posisi Anda, ada beberapa kasus di mana manfaat dari mengubah tujuan usingterlalu 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.
LBushkin
6
Lucunya, saya selalu berpikir untuk menggunakan sebagai sarana pendukung RAII. Tampaknya cukup intuitif bagi saya untuk menganggap gembok sebagai jenis sumber daya.
Brian
27

Jika Anda hanya ingin kode yang bersih dan terbatas, Anda juga dapat menggunakan lambda, á la

myFribble.SafeExecute(() =>
    {
        myFribble.DangerDanger();
        myFribble.LiveOnTheEdge();
    });

di mana .SafeExecute(Action fribbleAction)metode ini membungkus blok try- catch- finally.

herzmeister
sumber
Dalam contoh itu, Anda melewatkan status entri. Selanjutnya, pastikan Anda menyelesaikan percobaan / akhirnya, tetapi Anda tidak memanggil apa pun pada akhirnya, jadi jika sesuatu di lambda melempar Anda tidak tahu di negara bagian apa Anda berada.
Johann Gerell
1
Ah! Oke, saya rasa maksud Anda itu SafeExecuteadalah Fribblemetode yang memanggil Unlock()dan Lock()dalam percobaan / akhirnya dibungkus. Saya minta maaf. Saya segera melihat SafeExecutesebagai metode ekstensi generik dan oleh karena itu menyebutkan kurangnya status masuk dan keluar. Salahku.
Johann Gerell
1
Berhati-hatilah dengan apporach ini. Mungkin ada beberapa perpanjangan seumur hidup halus yang tidak disengaja dan berbahaya dalam kasus penduduk setempat yang ditangkap!
jason
4
Yuk. Mengapa menyembunyikan coba / tangkap / akhirnya? Membuat kode Anda tersembunyi untuk dibaca orang lain.
Frank Schwieterman
2
@Yukky Frank: Bukan ide saya untuk "menyembunyikan" apa pun, itu adalah permintaan penanya. :-P ... Yang mengatakan, permintaan akhirnya adalah masalah "Jangan Ulangi Diri Anda". Anda mungkin memiliki banyak metode yang memerlukan "bingkai" boilerplate yang sama untuk memperoleh / melepaskan sesuatu dengan rapi dan Anda tidak ingin memaksakannya pada pemanggil (pikirkan enkapsulasi). Juga seseorang dapat menyebutkan metode seperti .SafeExecute (...) lebih jelas untuk mengkomunikasikan dengan cukup baik apa yang mereka lakukan.
herzmeister
25

Eric Gunnerson, yang berada di tim desain bahasa C #, memberikan jawaban ini untuk pertanyaan yang hampir sama:

Doug bertanya:

re: Pernyataan kunci dengan batas waktu ...

Saya telah melakukan trik ini sebelumnya untuk menangani pola umum dalam berbagai metode. Biasanya akuisisi kunci , tetapi ada beberapa yang lain . Masalahnya adalah selalu terasa seperti retasan karena objek tidak benar-benar dapat dibuang sebanyak " panggilan-kembali-di-ujung-dari-cakupan-dapat ".

Doug,

Ketika kami memutuskan [sic] pernyataan penggunaan, kami memutuskan untuk menamainya "menggunakan" daripada sesuatu yang lebih spesifik untuk membuang objek sehingga dapat digunakan untuk skenario ini.

Samuel Jack
sumber
4
Kutipan itu seharusnya mengatakan apa yang dia maksud ketika dia mengatakan "persis skenario ini", karena saya ragu dia merujuk pada pertanyaan masa depan ini.
Frank Schwieterman
4
@ Frank Schwieterman: Saya menyelesaikan kutipan. Rupanya, orang-orang dari # tim C melakukan berpikir usingfitur itu tidak terbatas pada pembuangan sumber daya.
paercebal
11

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:

void UseMeUnlocked(Action callback) {
  Unlock();
  try {
    callback();
  }
  finally {
    Lock();
  }
}

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:

  • Tidak melakukan apa-apa, pengecualian tidak dapat dipulihkan. Kasus normal. Memanggil Buka Kunci tidak masalah.
  • Tangkap dan tangani pengecualian tersebut
  • Pulihkan status dalam kodenya dan biarkan pengecualian melewati rantai panggilan.

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.

Hans Passant
sumber
Kasus paksa diberikan oleh "herzmeister der welten" di atas, saya kira - bahkan jika OP tampaknya tidak menyukai contoh tersebut.
Benjamin Podszun
Ya, saya menafsirkannya SafeExecutesebagai metode penyuluhan umum. Saya melihat sekarang bahwa kemungkinan itu dimaksudkan sebagai Fribblemetode yang memanggil Unlock()dan Lock()dalam percobaan / akhirnya dibungkus. Salahku.
Johann Gerell
Mengabaikan pengecualian tidak berarti itu tidak dapat dipulihkan. Artinya akan ditangani di atas dalam tumpukan. Misalnya, pengecualian dapat digunakan untuk keluar dari utas dengan baik. Dalam kasus ini, Anda harus membebaskan sumber daya Anda dengan benar, termasuk kunci terkunci.
paercebal
7

Contoh dunia nyata adalah BeginForm dari ASP.net MVC. Pada dasarnya Anda bisa menulis:

Html.BeginForm(...);
Html.TextBox(...);
Html.EndForm();

atau

using(Html.BeginForm(...)){
    Html.TextBox(...);
}

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:

  • Buang harus menjadi fungsi yang selalu harus dijalankan, jadi tidak boleh ada kondisi apa pun kecuali Pemeriksaan Null
  • Setelah Dispose (), objek tersebut tidak boleh digunakan kembali. Jika saya menginginkan objek yang dapat digunakan kembali, saya lebih suka memberikan metode buka / tutup daripada membuangnya. Jadi saya melempar InvalidOperationException saat mencoba menggunakan objek yang dibuang.

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:

this.Frobble.Fiddle();

Karena FrobbleJanitor "memiliki" Frobble sekarang, saya bertanya-tanya apakah tidak lebih baik jika memanggil Fiddle di Frobble di Janitor?

Michael Stum
sumber
Tentang "Saya tidak suka baris ini" Anda - Saya sebenarnya sempat berpikir untuk melakukannya seperti itu saat merumuskan pertanyaan, tetapi saya tidak ingin mengacaukan semantik pertanyaan saya. Tapi saya setuju dengan Anda. Agak. :)
Johann Gerell
1
Suara positif untuk memberikan contoh dari Microsoft itu sendiri. Perhatikan bahwa ada pengecualian khusus untuk dilempar jika Anda menyebutkan: ObjectDisposedException.
Antitoon
4

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.

Simon Steele
sumber
4

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.

Benjamin Podszun
sumber
1
"Petugas kebersihan" berasal dari artikel C ++ lama oleh Andrei Alexandrescu tentang penjaga teropong, bahkan sebelum ScopeGuard sampai ke Loki.
Johann Gerell
@Benjamin: Saya suka kelas TransactionScope, belum pernah mendengarnya sebelumnya. Mungkin karena saya berada di tanah CF Net, di mana itu tidak termasuk ... :-(
Johann Gerell
4

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 # :

8.13 Pernyataan penggunaan

[...]

Sebuah sumber daya adalah kelas atau struct yang mengimplementasikan System.IDisposable, yang mencakup metode parameterless tunggal bernama Buang. Kode yang menggunakan sumber daya dapat memanggil Buang untuk menunjukkan bahwa sumber daya tidak lagi diperlukan. Jika Buang tidak dipanggil, maka pembuangan otomatis pada akhirnya terjadi sebagai akibat dari pengumpulan sampah.

The Code yang menggunakan sumber daya , tentu saja, kode mulai dengan usingkata kunci dan pergi sampai lingkup melekat pada using.

Jadi saya rasa ini baik-baik saja karena Kunci adalah sumber daya.

Mungkin kata kunci usingitu dipilih dengan buruk. Mungkin seharusnya dipanggil scoped.

Kemudian, kita dapat menganggap hampir semua hal sebagai sumber daya. Sebuah pegangan file. Koneksi jaringan ... Sebuah utas?

Seutas ???

Menggunakan (atau menyalahgunakan) usingkata kunci?

Akankah mengkilap untuk (ab) menggunakan usingkata 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:

// C# example
using( Active a = new Active() ) {    // creates private thread
       
       a.SomeWork();                  // enqueues work
       
       a.MoreWork();                   // enqueues work
       
} // waits for work to complete and joins with private thread

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:

~Active() {
    // etc.
    thd->join();
 }

Jadi, sejauh menyangkut Herb, itu berkilau .

paercebal.dll
sumber
3

Saya yakin jawaban atas pertanyaan Anda adalah tidak, ini bukan penyalahgunaan IDisposable.

Cara saya memahami IDisposableantarmuka adalah bahwa Anda tidak seharusnya bekerja dengan suatu objek setelah dibuang (kecuali bahwa Anda diizinkan untuk memanggil Disposemetodenya sesering yang Anda inginkan).

Karena Anda membuat baru FrobbleJanitor secara eksplisit setiap kali Anda masuk ke usingpernyataan, Anda tidak pernah menggunakan FrobbeJanitorobjek yang sama dua kali. Dan karena tujuannya adalah untuk mengelola objek lain, Disposetampaknya sesuai untuk tugas membebaskan sumber daya ini ("dikelola").

(Btw., Kode contoh standar yang mendemonstrasikan implementasi yang tepat Disposehampir 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 dengan try..finallyblok yang lebih eksplisit di mana Lock&Unlock operasi terlihat secara langsung. Tapi pendekatan mana yang diambil mungkin tergantung pada preferensi pribadi.

stakx - tidak lagi berkontribusi
sumber
1

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.

ata
sumber
1

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.

Frank Schwieterman
sumber
Tentang mengganti nama - Saya menggunakan "Petugas Kebersihan" dari alasan utama saya berurusan dengan Lock / Unlock. Saya pikir itu berima dengan pilihan nama lainnya. ;-) Jika saya menggunakan metode ini sebagai pola di seluruh basis kode saya, saya pasti akan menyertakan sesuatu yang lebih deskriptif secara umum, seperti yang Anda maksudkan dengan "Sesi". Jika ini adalah C ++ hari yang lama, saya mungkin akan menggunakan FrobbleUnlockScope.
Johann Gerell