สิ่งสำคัญคือต้องแยกการกำจัดออกจากการเก็บขยะ สิ่งเหล่านี้เป็นสิ่งที่แยกจากกันโดยสิ้นเชิงโดยมีจุดหนึ่งที่เหมือนกันซึ่งฉันจะมาถึงในอีกไม่กี่นาที
Dispose
, การเก็บขยะและการสรุป
เมื่อคุณเขียนusing
คำสั่งมันเป็นเพียงน้ำตาลในเชิงไวยากรณ์สำหรับการลอง / บล็อกในที่สุดซึ่งDispose
จะถูกเรียกแม้ว่าโค้ดในเนื้อหาของusing
คำสั่งจะมีข้อยกเว้นก็ตาม มันไม่ได้หมายความว่าวัตถุที่ถูกเก็บขยะในตอนท้ายของบล็อก
การกำจัดเป็นเรื่องเกี่ยวกับทรัพยากรที่ไม่มีการจัดการ ( ทรัพยากรที่ไม่ใช่หน่วยความจำ) สิ่งเหล่านี้อาจเป็นที่จับ UI การเชื่อมต่อเครือข่ายที่จับไฟล์เป็นต้นสิ่งเหล่านี้เป็นทรัพยากรที่มี จำกัด ดังนั้นโดยทั่วไปคุณจึงต้องการเผยแพร่โดยเร็วที่สุด คุณควรใช้IDisposable
เมื่อใดก็ตามที่ประเภทของคุณ "เป็นเจ้าของ" ทรัพยากรที่ไม่มีการจัดการไม่ว่าโดยตรง (โดยปกติจะผ่านทางIntPtr
) หรือทางอ้อม (เช่นผ่านทางStream
a SqlConnection
ฯลฯ )
การเก็บขยะนั้นเป็นเรื่องเกี่ยวกับความทรงจำเท่านั้น - ด้วยการบิดเพียงเล็กน้อย คนเก็บขยะสามารถค้นหาวัตถุที่ไม่สามารถอ้างอิงได้อีกต่อไปและปลดปล่อยพวกมัน มันไม่ได้มองหาขยะตลอดเวลา - เฉพาะเมื่อตรวจพบว่าจำเป็นต้องใช้ (เช่นหากฮีป "รุ่น" หนึ่งหน่วยความจำหมด)
บิดคือการสรุป ตัวเก็บขยะจะเก็บรายการของวัตถุที่ไม่สามารถเข้าถึงได้อีกต่อไป แต่มีตัวสุดท้าย (เขียนเป็น~Foo()
ภาษา C # ค่อนข้างสับสน - พวกมันไม่เหมือนกับตัวทำลาย C ++) มันเรียกใช้ Finalizers บนวัตถุเหล่านี้ในกรณีที่พวกเขาจำเป็นต้องทำการล้างข้อมูลเพิ่มเติมก่อนที่หน่วยความจำจะถูกปลดปล่อย
Finalizers มักใช้ในการล้างทรัพยากรในกรณีที่ผู้ใช้ประเภทนั้นลืมทิ้งอย่างเป็นระเบียบ ดังนั้นถ้าคุณเปิดFileStream
แต่ลืมโทรDispose
หรือClose
, finalizer จะในที่สุดก็ปล่อยจับแฟ้มต้นแบบสำหรับคุณ ในโปรแกรมที่เขียนมาอย่างดีผู้เข้ารอบสุดท้ายแทบจะไม่เริ่มทำงานในความคิดของฉัน
การตั้งค่าตัวแปรเป็น null
จุดเล็ก ๆ จุดหนึ่งในการตั้งค่าตัวแปรเป็นnull
- แทบจะไม่จำเป็นสำหรับการเก็บขยะ บางครั้งคุณอาจต้องการทำหากเป็นตัวแปรสมาชิกแม้ว่าจากประสบการณ์ของฉันมันหายากสำหรับ "บางส่วน" ของวัตถุที่ไม่จำเป็นอีกต่อไป เมื่อเป็นตัวแปรภายใน JIT มักจะฉลาดพอ (ในโหมดรีลีส) ที่จะรู้ว่าเมื่อใดที่คุณจะไม่ใช้การอ้างอิงอีก ตัวอย่างเช่น:
StringBuilder sb = new StringBuilder();
sb.Append("Foo");
string x = sb.ToString();
// The string and StringBuilder are already eligible
// for garbage collection here!
int y = 10;
DoSomething(y);
// These aren't helping at all!
x = null;
sb = null;
// Assume that x and sb aren't used here
ครั้งหนึ่งที่อาจคุ้มค่ากับการตั้งค่าตัวแปรท้องถิ่นnull
คือเมื่อคุณอยู่ในวงและบางสาขาของลูปจำเป็นต้องใช้ตัวแปร แต่คุณรู้ว่าคุณมาถึงจุดที่คุณไม่ได้ทำ ตัวอย่างเช่น:
SomeObject foo = new SomeObject();
for (int i=0; i < 100000; i++)
{
if (i == 5)
{
foo.DoSomething();
// We're not going to need it again, but the JIT
// wouldn't spot that
foo = null;
}
else
{
// Some other code
}
}
การนำ IDisposable / finalizers
ดังนั้นประเภทของคุณเองควรใช้ Finalizers หรือไม่? เกือบจะไม่แน่นอน หากคุณถือทรัพยากรที่ไม่มีการจัดการทางอ้อมเท่านั้น(เช่นคุณมีFileStream
ตัวแปรในฐานะสมาชิก) การเพิ่ม Finalizer ของคุณเองจะไม่ช่วยอะไร: สตรีมเกือบจะมีสิทธิ์ได้รับการรวบรวมขยะเมื่อวัตถุของคุณอยู่ดังนั้นคุณจึงสามารถพึ่งพาFileStream
มีโปรแกรมปิดท้าย (ถ้าจำเป็น - อาจหมายถึงอย่างอื่น ฯลฯ ) หากคุณต้องการถือทรัพยากรที่ไม่มีการจัดการ "เกือบ" โดยตรงSafeHandle
คือเพื่อนของคุณ - ต้องใช้เวลาสักหน่อยในการดำเนินการ แต่หมายความว่าคุณแทบจะ ไม่ต้องเขียน Finalizer อีกเลย โดยปกติคุณควรจะต้องมีโปรแกรมสุดท้ายก็ต่อเมื่อคุณมีส่วนจัดการโดยตรงกับทรัพยากร (an IntPtr
) และคุณควรมองหาSafeHandle
โดยเร็วที่สุดเท่าที่คุณจะทำได้. (มีลิงก์สองลิงก์ - อ่านทั้งสองอย่างโดยดี)
Joe Duffy มีแนวทางที่ยาวมากเกี่ยวกับ Finalizers และ IDisposable (เขียนร่วมกับสมาร์ทโฟล์คมากมาย) ซึ่งควรค่าแก่การอ่าน เป็นสิ่งที่ควรทราบว่าหากคุณปิดชั้นเรียนมันจะทำให้ชีวิตง่ายขึ้นมาก: รูปแบบของการแทนที่Dispose
เพื่อเรียกDispose(bool)
วิธีเสมือนใหม่ฯลฯ จะเกี่ยวข้องก็ต่อเมื่อชั้นเรียนของคุณได้รับการออกแบบมาเพื่อการสืบทอด
นี่เป็นเรื่องเล็กน้อย แต่โปรดขอความกระจ่างในจุดที่คุณต้องการ :)