อะไรคือความแตกต่างระหว่างการใช้ IDisposable vs a destructor ใน C #?


101

เมื่อไหร่ที่ฉันจะใช้ IDispose ในคลาสซึ่งตรงข้ามกับ destructor ฉันอ่านบทความนี้แต่ฉันยังไม่เข้าใจประเด็นนี้

สมมติฐานของฉันคือถ้าฉันใช้ IDispose กับวัตถุฉันสามารถ 'ทำลาย' มันได้อย่างชัดเจนแทนที่จะรอให้คนเก็บขยะทำ ถูกต้องหรือไม่

นั่นหมายความว่าฉันควรเรียก Dispose on an object อย่างชัดเจนเสมอหรือไม่? ตัวอย่างทั่วไปของสิ่งนี้มีอะไรบ้าง?


5
อันที่จริงคุณควรเรียก Dispose ในทุกวัตถุที่ใช้แล้วทิ้ง คุณสามารถทำได้อย่างง่ายดายโดยใช้usingโครงสร้าง
Luc Touraille

อืมมันสมเหตุสมผลแล้ว ฉันสงสัยมาตลอดว่าเหตุใดจึงใช้คำสั่ง 'ใช้' สำหรับสตรีมไฟล์ ฉันรู้ว่ามันเกี่ยวข้องกับขอบเขตของออบเจ็กต์ แต่ฉันไม่ได้ใส่ไว้ในบริบทกับอินเทอร์เฟซ IDisposable
Jordan Parmer

5
จุดสำคัญอย่างหนึ่งที่ต้องจำไว้คือผู้สรุปผลงานไม่ควรเข้าถึงสมาชิกที่มีการจัดการใด ๆ ของชั้นเรียนเนื่องจากสมาชิกเหล่านั้นอาจไม่ใช่ข้อมูลอ้างอิงที่ถูกต้องอีกต่อไป
Dan Bryant

คำตอบ:


126

Finalizer (aka destructor) เป็นส่วนหนึ่งของการรวบรวมขยะ (GC) ซึ่งไม่แน่นอนเมื่อ (หรือแม้ว่า) สิ่งนี้จะเกิดขึ้นเนื่องจาก GC ส่วนใหญ่เกิดขึ้นจากแรงกดดันของหน่วยความจำ (เช่นต้องการพื้นที่มากขึ้น) โดยทั่วไป Finalizers จะใช้สำหรับการล้างทรัพยากรที่ไม่มีการจัดการเท่านั้นเนื่องจากทรัพยากรที่มีการจัดการจะมีการรวบรวม / กำจัดของตนเอง

ดังนั้นจึงIDisposableใช้ในการล้างวัตถุโดยกำหนดเช่นตอนนี้ ไม่รวบรวมหน่วยความจำของวัตถุ (ซึ่งยังคงเป็นของ GC) - แต่ใช้เพื่อปิดไฟล์การเชื่อมต่อฐานข้อมูลเป็นต้น

มีหัวข้อก่อนหน้านี้มากมาย:

สุดท้ายโปรดทราบว่าไม่ใช่เรื่องแปลกที่IDisposableออบเจ็กต์จะมี Finalizer ด้วยเช่นกัน ในกรณีนี้Dispose()มักจะเรียกGC.SuppressFinalize(this)ซึ่งหมายความว่า GC ไม่ได้เรียกใช้โปรแกรมสุดท้าย - เพียงแค่โยนหน่วยความจำออกไป (ถูกกว่ามาก) โปรแกรมสุดท้ายยังคงทำงานหากคุณลืมไปDispose()ที่วัตถุ


ขอบคุณ! นั่นเป็นเหตุผลที่ดี ฉันขอบคุณมากสำหรับการตอบสนองที่ดี
Jordan Parmer

27
สิ่งพิเศษที่จะพูด อย่าเพิ่ม Finalizer ในชั้นเรียนของคุณเว้นแต่คุณจะต้องการจริงๆ หากคุณเพิ่ม Finalizer (ตัวทำลาย) GC จะต้องเรียกมันว่า (แม้แต่ตัวสุดท้ายที่ว่างเปล่า) และเรียกมันว่าอ็อบเจกต์จะอยู่รอดจากการเก็บขยะ gen 1 เสมอ สิ่งนี้จะขัดขวางและทำให้ GC ช้าลง นั่นคือสิ่งที่ Marc บอกว่าให้เรียก SuppressFinalize ในรหัสด้านบน
Kevin Jones

1
ดังนั้น Finalize คือการปล่อยทรัพยากรที่ไม่มีการจัดการ แต่ Dispose สามารถใช้เพื่อปล่อยทรัพยากรที่มีการจัดการและไม่มีการจัดการได้?
Dark_Knight

2
@ มืดใช่; เนื่องจาก 6 ระดับที่ลดลงของห่วงโซ่การจัดการอาจเป็นระดับที่ไม่มีการจัดการที่ต้องการการล้างข้อมูลอย่างรวดเร็ว
Marc Gravell

1
@KevinJones Objects พร้อม Finalizer รับประกันว่าจะอยู่รอด gen 0 ไม่ใช่ 1 ใช่ไหม? ฉันอ่านในหนังสือชื่อ. NET Performance
David Klempfner

25

บทบาทของFinalize()วิธีการเพื่อให้แน่ใจว่าวัตถุ .NET สามารถทำความสะอาดที่ไม่มีการจัดการทรัพยากรเมื่อเก็บขยะ อย่างไรก็ตามควรปล่อยอ็อบเจ็กต์เช่นการเชื่อมต่อฐานข้อมูลหรือตัวจัดการไฟล์โดยเร็วที่สุดแทนที่จะใช้การรวบรวมขยะ สำหรับสิ่งนั้นคุณควรใช้IDisposableอินเทอร์เฟซและปล่อยทรัพยากรของคุณในDispose()วิธีการ


9

มีคำอธิบายที่ดีมากเกี่ยวกับMSDN :

ใช้หลักของอินเตอร์เฟซนี้คือการปล่อยทรัพยากรที่ไม่มีการจัดการ ตัวรวบรวมขยะจะปลดหน่วยความจำที่จัดสรรให้กับอ็อบเจ็กต์ที่ถูกจัดการโดยอัตโนมัติเมื่ออ็อบเจ็กต์นั้นไม่ได้ใช้อีกต่อไป อย่างไรก็ตามไม่สามารถคาดเดาได้ว่าการเก็บขยะจะเกิดขึ้นเมื่อใด นอกจากนี้ตัวรวบรวมขยะไม่มีความรู้เกี่ยวกับทรัพยากรที่ไม่มีการจัดการ เช่นที่จับหน้าต่างหรือไฟล์และสตรีมที่เปิดอยู่

ใช้เมธอด Dispose ของอินเตอร์เฟสนี้เพื่อปล่อย รีซอร์สที่ไม่มีการจัดการอย่างชัดเจนร่วมกับตัวรวบรวมขยะ ผู้บริโภคของวัตถุสามารถเรียกวิธีนี้เมื่อวัตถุที่ไม่มีความจำเป็น


1
จุดอ่อนที่สำคัญอย่างหนึ่งของคำอธิบายนั้นคือ MS ยกตัวอย่างทรัพยากรที่ไม่มีการจัดการ แต่จากสิ่งที่ฉันเห็นไม่เคยนิยามคำนี้ เนื่องจากโดยทั่วไปแล้วออบเจ็กต์ที่มีการจัดการจะใช้งานได้ภายในโค้ดที่มีการจัดการเท่านั้นจึงอาจคิดว่าสิ่งต่างๆที่ใช้ในโค้ดที่ไม่มีการจัดการนั้นเป็นทรัพยากรที่ไม่มีการจัดการ แต่นั่นไม่เป็นความจริง โค้ดที่ไม่มีการจัดการจำนวนมากไม่ได้ใช้ทรัพยากรใด ๆ และทรัพยากรที่ไม่มีการจัดการบางประเภทเช่นเหตุการณ์มีอยู่ในจักรวาลของโค้ดที่มีการจัดการเท่านั้น
supercat

1
หากอ็อบเจ็กต์อายุสั้นสมัครรับข้อมูลเหตุการณ์จากอ็อบเจ็กต์ที่มีอายุการใช้งานยาวนาน (เช่นขอให้แจ้งการเปลี่ยนแปลงใด ๆ ที่เกิดขึ้นภายในอายุการใช้งานของอ็อบเจ็กต์ที่มีอายุสั้น) เหตุการณ์ดังกล่าวควรได้รับการพิจารณาว่าเป็นทรัพยากรที่ไม่มีการจัดการเนื่องจากความล้มเหลวในการ การยกเลิกการสมัครกิจกรรมจะทำให้อายุการใช้งานของวัตถุที่มีอายุสั้นขยายไปถึงวัตถุที่มีอายุการใช้งานยาวนาน หากออบเจ็กต์อายุสั้นจำนวนหลายพันหรือหลายล้านรายการที่สมัครเป็นสมาชิกเหตุการณ์ แต่ถูกละทิ้งโดยไม่มีการยกเลิกการสมัครนั่นอาจทำให้หน่วยความจำหรือ CPU รั่วไหล (เนื่องจากเวลาที่ต้องใช้ในการประมวลผลการสมัครสมาชิกแต่ละครั้งจะเพิ่มขึ้น)
supercat

1
สถานการณ์อื่นที่เกี่ยวข้องกับทรัพยากรที่ไม่มีการจัดการภายในโค้ดที่มีการจัดการคือการจัดสรรอ็อบเจ็กต์จากพูล โดยเฉพาะอย่างยิ่งหากจำเป็นต้องรันโค้ดใน. NET Micro Framework (ซึ่งตัวรวบรวมขยะมีประสิทธิภาพน้อยกว่าในเครื่องเดสก์ท็อปมาก) อาจเป็นประโยชน์สำหรับรหัสที่มีเช่นโครงสร้างอาร์เรย์ซึ่งแต่ละส่วนอาจถูกทำเครื่องหมายว่า "ใช้แล้ว" หรือ "ฟรี" คำขอการจัดสรรควรค้นหาโครงสร้างซึ่งปัจจุบันมีการทำเครื่องหมายว่า "ว่าง" ทำเครื่องหมายว่า "ใช้แล้ว" และส่งดัชนีกลับไป คำขอเผยแพร่ควรทำเครื่องหมายโครงสร้างว่า "ฟรี" หากคำขอการจัดสรรส่งกลับเช่น 23 แสดงว่า ...
supercat

1
... ถ้ารหัสไม่เคยแจ้งให้เจ้าของอาร์เรย์ทราบว่าไม่ต้องการรายการ # 23 อีกต่อไปช่องอาร์เรย์นั้นจะไม่สามารถใช้งานได้โดยรหัสอื่น ๆ การจัดสรรสล็อตอาร์เรย์ด้วยตนเองดังกล่าวไม่ได้ถูกใช้บ่อยนักในโค้ดเดสก์ท็อปเนื่องจาก GC มีประสิทธิภาพค่อนข้างสูง แต่ในโค้ดที่ทำงานบน Micro Framework นั้นสามารถสร้างความแตกต่างได้มาก
supercat

8

สิ่งเดียวที่ควรอยู่ในตัวทำลาย C # คือบรรทัดนี้:

Dispose(False);

แค่นั้นแหละ. ไม่มีสิ่งอื่นใดที่ควรอยู่ในวิธีการนั้น


3
นี่คือรูปแบบการออกแบบที่ Microsoft เสนอในเอกสาร. NET แต่อย่าใช้เมื่อวัตถุของคุณไม่สามารถระบุได้ msdn.microsoft.com/en-us/library/fs2xkftw%28v=vs.110%29.aspx
Zbyl

1
ฉันคิดไม่ออกว่าจะเสนอชั้นเรียนด้วยโปรแกรมปิดท้ายที่ไม่มีเมธอด Dispose ด้วยเหตุผลใด
Jonathan Allen

4

คำถามของคุณเกี่ยวกับว่าคุณควรโทรหาหรือไม่มักDisposeจะเป็นการถกเถียงกันอย่างดุเดือด ดูบล็อกนี้สำหรับมุมมองที่น่าสนใจจากบุคคลที่เคารพนับถือในชุมชน. NET

โดยส่วนตัวแล้วฉันคิดว่าจุดยืนของเจฟฟรีย์ริกเตอร์ที่เรียกว่าDisposeไม่บังคับนั้นอ่อนแออย่างไม่น่าเชื่อ เขายกตัวอย่างสองตัวอย่างเพื่อแสดงความคิดเห็นของเขา

ในตัวอย่างแรกเขากล่าวว่าการเรียกDisposeใช้ตัวควบคุม Windows Forms นั้นน่าเบื่อและไม่จำเป็นในสถานการณ์หลัก อย่างไรก็ตามเขาไม่ได้พูดถึงว่าDisposeคอนเทนเนอร์ควบคุมถูกเรียกโดยอัตโนมัติในสถานการณ์หลักเหล่านั้น

ในตัวอย่างที่สองเขาระบุว่านักพัฒนาซอฟต์แวร์อาจสันนิษฐานอย่างไม่ถูกต้องว่าIAsyncResult.WaitHandleควรกำจัดอินสแตนซ์จากเชิงรุกโดยไม่ทราบว่าคุณสมบัติเริ่มต้นการจัดการการรออย่างเกียจคร้านส่งผลให้เกิดการลงโทษด้านประสิทธิภาพโดยไม่จำเป็น แต่ปัญหาของตัวอย่างนี้คือIAsyncResultตัวเองไม่ปฏิบัติตามแนวทางที่เผยแพร่ของ Microsoft ในการจัดการกับIDisposableวัตถุ นั่นคือถ้าระดับเก็บการอ้างอิงไปยังประเภทแล้วชั้นเองควรใช้IDisposable IDisposableหากIAsyncResultปฏิบัติตามกฎนั้นDisposeวิธีการของตัวเองก็สามารถตัดสินใจได้ว่าสมาชิกที่เป็นส่วนประกอบใดจำเป็นต้องกำจัด

ดังนั้นถ้าไม่มีใครมีข้อโต้แย้งที่น่าสนใจกว่านี้ฉันจะอยู่ในค่าย "มักเรียกทิ้ง" ด้วยความเข้าใจว่าจะมีบางกรณีที่ส่วนใหญ่เกิดจากตัวเลือกการออกแบบที่ไม่ดี


3

มันค่อนข้างเรียบง่ายจริงๆ ฉันรู้ว่าได้รับคำตอบแล้ว แต่ฉันจะพยายามอีกครั้ง แต่จะพยายามทำให้ง่ายที่สุด

โดยทั่วไปไม่ควรใช้ตัวทำลาย เป็นเพียงการเรียกใช้. net เท่านั้นที่ต้องการให้ทำงาน มันจะทำงานหลังจากรอบการเก็บรวบรวมขยะเท่านั้น อาจไม่ถูกเรียกใช้จริงในช่วงวงจรชีวิตของแอปพลิเคชันของคุณ ด้วยเหตุนี้คุณจึงไม่ควรใส่รหัสใด ๆ ในตัวทำลายที่ "ต้อง" เรียกใช้ นอกจากนี้คุณยังไม่สามารถพึ่งพาอ็อบเจ็กต์ใด ๆ ที่มีอยู่ในคลาสที่จะมีอยู่เมื่อมันทำงานได้ (อาจถูกล้างไปแล้วเนื่องจากลำดับที่ผู้ทำลายวิ่งเข้ามาไม่ได้รับการรับประกัน)

ควรใช้ IDisposible เมื่อใดก็ตามที่คุณมีออบเจ็กต์ที่สร้างทรัพยากรที่ต้องการการล้างข้อมูล (เช่นไฟล์และตัวจัดการกราฟิก) ในความเป็นจริงหลายคนโต้แย้งว่าสิ่งใดก็ตามที่คุณใส่ไว้ในตัวทำลายล้างควรถูกทำให้เป็นไอดีทิ้งได้เนื่องจากเหตุผลที่ระบุไว้ข้างต้น

ชั้นเรียนส่วนใหญ่จะเรียกการกำจัดเมื่อมีการเรียกใช้ Finalizer แต่นี่เป็นเพียงตัวป้องกันที่ปลอดภัยและไม่ควรพึ่งพา คุณควรทิ้งสิ่งที่ใช้ IDisposable อย่างชัดเจนเมื่อคุณทำเสร็จแล้ว หากคุณใช้ IDisposable คุณควรเรียก Dispose ใน Finalizer ดูตัวอย่างhttp://msdn.microsoft.com/en-us/library/system.idisposable.aspx


ไม่คนเก็บขยะไม่เคยเรียก Dispose () มันเพียง แต่เรียก finalizer
Marc Gravell

แก้ไขที่. ชั้นเรียนควรเรียกการกำจัดใน Finalizer แต่ไม่จำเป็นต้องทำ
DaEagle

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.