Mulailah dengan kelas-kelas sederhana ini ...
Katakanlah saya memiliki satu set kelas sederhana seperti ini:
class Bus
{
Driver busDriver = new Driver();
}
class Driver
{
Shoe[] shoes = { new Shoe(), new Shoe() };
}
class Shoe
{
Shoelace lace = new Shoelace();
}
class Shoelace
{
bool tied = false;
}
A Bus
memiliki a Driver
, Driver
memiliki dua Shoe
s, masing-masing Shoe
memiliki a Shoelace
. Semuanya sangat konyol.
Tambahkan objek IDisposable ke Tali Sepatu
Kemudian saya memutuskan bahwa beberapa operasi pada Shoelace
dapat menjadi multi-utas, jadi saya menambahkan EventWaitHandle
untuk utas untuk berkomunikasi. Jadi Shoelace
sekarang terlihat seperti ini:
class Shoelace
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
// ... other stuff ..
}
Menerapkan IDisposable di Shoelace
Tapi sekarang Microsoft FxCop akan mengeluh: "Implementasikan IDisposable di 'Shoelace' karena itu membuat anggota IDisposable type berikut: 'EventWaitHandle'."
Oke, saya menerapkan IDisposable
pada Shoelace
dan rapi kelas kecil saya menjadi kekacauan ini mengerikan:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
private bool disposed = false;
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~Shoelace()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
}
// No unmanaged resources to release otherwise they'd go here.
}
disposed = true;
}
}
Atau (seperti yang ditunjukkan oleh komentator) karena Shoelace
itu sendiri tidak memiliki sumber daya yang tidak terkelola, saya mungkin menggunakan implementasi pembuangan yang lebih sederhana tanpa memerlukan Dispose(bool)
dan Destructor:
class Shoelace : IDisposable
{
private AutoResetEvent waitHandle = new AutoResetEvent(false);
bool tied = false;
public void Dispose()
{
if (waitHandle != null)
{
waitHandle.Close();
waitHandle = null;
}
GC.SuppressFinalize(this);
}
}
Saksikan dengan ngeri saat ID sekali pakai menyebar
Benar itu sudah diperbaiki. Tapi sekarang FxCop akan komplain yang Shoe
membuat Shoelace
, jadi Shoe
pasti IDisposable
juga.
Dan Driver
menciptakan harus Shoe
begitu . Dan menciptakan begitu harus dan seterusnya.Driver
IDisposable
Bus
Driver
Bus
IDisposable
Tiba-tiba perubahan kecil saya menjadi Shoelace
menyebabkan saya banyak pekerjaan dan bos saya bertanya-tanya mengapa saya perlu membayar Bus
untuk membuat perubahan Shoelace
.
Pertanyaan
Bagaimana Anda mencegah penyebaran ini IDisposable
, tetapi masih memastikan bahwa objek yang tidak dikelola dibuang dengan benar?
sumber
Jawaban:
Anda tidak bisa benar-benar "mencegah" IDisposable dari penyebaran. Beberapa kelas perlu dibuang, seperti
AutoResetEvent
, dan cara yang paling efisien adalah melakukannya dalamDispose()
metode untuk menghindari overhead finalizer. Tetapi metode ini harus dipanggil entah bagaimana, jadi persis seperti dalam contoh Anda, kelas yang merangkum atau berisi IDisposable harus membuang ini, jadi mereka harus dibuang juga, dll. Satu-satunya cara untuk menghindarinya adalah dengan:using
pola)Dalam beberapa kasus IDisposable dapat diabaikan karena mendukung kasus opsional. Misalnya, WaitHandle mengimplementasikan IDisposable untuk mendukung Mutex bernama. Jika sebuah nama tidak digunakan, metode Buang tidak melakukan apa pun. MemoryStream adalah contoh lain, ia tidak menggunakan sumber daya sistem dan implementasi Buangnya juga tidak melakukan apa-apa. Pemikiran yang cermat tentang apakah sumber daya yang tidak dikelola sedang digunakan atau tidak dapat menjadi instruksional. Jadi dapat memeriksa sumber yang tersedia untuk pustaka .net atau menggunakan dekompiler.
sumber
Dalam hal kebenaran, Anda tidak dapat mencegah penyebaran IDisposable melalui hubungan objek jika objek induk membuat dan pada dasarnya memiliki objek turunan yang sekarang harus dibuang. FxCop benar dalam situasi ini dan orang tua harus IDisposable.
Apa yang dapat Anda lakukan adalah menghindari menambahkan IDisposable ke kelas daun dalam hierarki objek Anda. Ini tidak selalu merupakan tugas yang mudah tetapi ini merupakan latihan yang menarik. Dari perspektif logis, tidak ada alasan bahwa ShoeLace perlu sekali pakai. Alih-alih menambahkan WaitHandle di sini, apakah mungkin juga menambahkan asosiasi antara ShoeLace dan WaitHandle pada titik itu digunakan. Cara termudah adalah melalui instance Dictionary.
Jika Anda dapat memindahkan WaitHandle ke asosiasi longgar melalui peta pada titik WaitHandle benar-benar digunakan maka Anda dapat memutuskan rantai ini.
sumber
Untuk mencegah
IDisposable
penyebaran, Anda harus mencoba merangkum penggunaan objek sekali pakai di dalam satu metode. Coba desain secaraShoelace
berbeda:class Shoelace { bool tied = false; public void Tie() { using (var waitHandle = new AutoResetEvent(false)) { // you can even pass the disposable to other methods OtherMethod(waitHandle); // or hold it in a field (but FxCop will complain that your class is not disposable), // as long as you take control of its lifecycle _waitHandle = waitHandle; OtherMethodThatUsesTheWaitHandleFromTheField(); } } }
Cakupan pegangan tunggu terbatas pada
Tie
metode, dan kelas tidak perlu memiliki bidang sekali pakai, sehingga tidak perlu dibuang sendiri.Karena pegangan tunggu adalah detail implementasi di dalam
Shoelace
, itu tidak boleh mengubah dengan cara apa pun antarmuka publiknya, seperti menambahkan antarmuka baru dalam deklarasinya. Apa yang akan terjadi kemudian ketika Anda tidak membutuhkan bidang sekali pakai lagi, apakah Anda akan menghapusIDisposable
deklarasi? Jika Anda memikirkan tentangShoelace
abstraksi , Anda akan segera menyadari bahwa abstraksi seharusnya tidak tercemar oleh ketergantungan infrastruktur, sepertiIDisposable
.IDisposable
harus disediakan untuk kelas yang abstraksinya merangkum sumber daya yang memerlukan pembersihan deterministik; yaitu, untuk kelas di mana disposabilitas merupakan bagian dari abstraksi .sumber
AutoResetEvent
digunakan untuk berkomunikasi antara utas berbeda yang beroperasi dalam kelas yang sama, jadi itu harus menjadi variabel anggota. Anda tidak dapat membatasi cakupannya ke metode. (mis. bayangkan bahwa satu utas hanya duduk menunggu beberapa pekerjaan dengan memblokirwaitHandle.WaitOne()
. Utas utama kemudian memanggilshoelace.Tie()
metode, yang hanya melakukan awaitHandle.Set()
dan segera kembali).Ini pada dasarnya apa yang terjadi ketika Anda mencampur Komposisi atau Agregasi dengan kelas sekali pakai. Seperti yang disebutkan, jalan keluar pertama adalah dengan memfaktor ulang waitHandle dari tali sepatu.
Karena itu, Anda dapat menghapus pola Disposable secara signifikan ketika Anda tidak memiliki resource yang tidak terkelola. (Saya masih mencari referensi resmi untuk ini.)
Tapi Anda bisa menghilangkan destructor dan GC.SuppressFinalize (this); dan mungkin sedikit membersihkan virtual void Dispose (bool disposing).
sumber
Menariknya jika
Driver
diartikan seperti di atas:class Driver { Shoe[] shoes = { new Shoe(), new Shoe() }; }
Kemudian saat
Shoe
dibuatIDisposable
, FxCop (v1.36) tidak komplain yangDriver
seharusnya juga bisaIDisposable
.Namun jika diartikan seperti ini:
class Driver { Shoe leftShoe = new Shoe(); Shoe rightShoe = new Shoe(); }
maka ia akan mengeluh.
Saya curiga bahwa ini hanya batasan FxCop, bukan solusi, karena pada versi pertama,
Shoe
instance masih dibuat olehDriver
dan masih perlu dibuang.sumber
Shoe
konstruktor makashoes
akan dibiarkan dalam keadaan yang sulit?Saya tidak berpikir ada cara teknis untuk mencegah ID sekali pakai menyebar jika Anda menjaga desain Anda begitu erat. Orang kemudian harus bertanya-tanya apakah desainnya benar.
Dalam contoh Anda, menurut saya masuk akal untuk memiliki sepatu yang memiliki tali sepatu, dan mungkin, pengemudi harus memiliki sepatunya sendiri. Namun, bus seharusnya tidak memiliki pengemudi. Biasanya, pengemudi bus tidak mengikuti bus ke tempat pembuangan sampah :) Dalam kasus pengemudi dan sepatu, pengemudi jarang membuat sepatu sendiri, artinya mereka tidak benar-benar "memilikinya".
Desain alternatif bisa berupa:
class Bus { IDriver busDriver = null; public void SetDriver(IDriver d) { busDriver = d; } } class Driver : IDriver { IShoePair shoes = null; public void PutShoesOn(IShoePair p) { shoes = p; } } class ShoePairWithDisposableLaces : IShoePair, IDisposable { Shoelace lace = new Shoelace(); } class Shoelace : IDisposable { ... }
Sayangnya, desain baru ini lebih rumit, karena memerlukan kelas tambahan untuk membuat dan membuang contoh konkret sepatu dan driver, tetapi kerumitan ini melekat pada masalah yang sedang dipecahkan. Hal baiknya adalah bus tidak perlu lagi dibuang hanya untuk tujuan membuang tali sepatu.
sumber
Bagaimana kalau menggunakan Inversion of Control?
class Bus { private Driver busDriver; public Bus(Driver busDriver) { this.busDriver = busDriver; } } class Driver { private Shoe[] shoes; public Driver(Shoe[] shoes) { this.shoes = shoes; } } class Shoe { private Shoelace lace; public Shoe(Shoelace lace) { this.lace = lace; } } class Shoelace { bool tied; private AutoResetEvent waitHandle; public Shoelace(bool tied, AutoResetEvent waitHandle) { this.tied = tied; this.waitHandle = waitHandle; } } class Program { static void Main(string[] args) { using (var leftShoeWaitHandle = new AutoResetEvent(false)) using (var rightShoeWaitHandle = new AutoResetEvent(false)) { var bus = new Bus(new Driver(new[] {new Shoe(new Shoelace(false, leftShoeWaitHandle)),new Shoe(new Shoelace(false, rightShoeWaitHandle))})); } } }
sumber