การใช้อินเตอร์เฟส IDisposable อย่างเหมาะสม


1657

ฉันรู้จากการอ่านเอกสารของ Microsoftว่าการใช้IDisposableอินเทอร์เฟซหลักคือการล้างทรัพยากรที่ไม่มีการจัดการ

สำหรับฉัน "ไม่มีการจัดการ" หมายถึงสิ่งต่าง ๆ เช่นการเชื่อมต่อฐานข้อมูลซ็อกเก็ตที่จับหน้าต่าง ฯลฯ แต่ฉันเห็นรหัสที่ใช้Dispose()วิธีการกับทรัพยากรที่มีการจัดการฟรีซึ่งดูเหมือนว่าซ้ำซ้อนกับฉันเนื่องจากตัวเก็บขยะควรดูแล สำหรับคุณ

ตัวอย่างเช่น:

public class MyCollection : IDisposable
{
    private List<String> _theList = new List<String>();
    private Dictionary<String, Point> _theDict = new Dictionary<String, Point>();

    // Die, clear it up! (free unmanaged resources)
    public void Dispose()
    {
        _theList.clear();
        _theDict.clear();
        _theList = null;
        _theDict = null;
    }

คำถามของฉันคือสิ่งนี้ทำให้หน่วยความจำขยะฟรีใช้โดยMyCollectionเร็วกว่าปกติไหม?

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


34
ฉันชอบคำตอบที่ยอมรับเพราะมันบอกคุณถึงรูปแบบที่ถูกต้องในการใช้ IDisposable แต่เช่นเดียวกับ OP ที่กล่าวไว้ในการแก้ไขของเขามันไม่ตอบคำถามที่เขาตั้งใจ IDisposable ไม่ได้เรียก 'GC' มันแค่ 'ทำเครื่องหมาย' วัตถุที่ทำลายได้ แต่อะไรคือวิธีที่แท้จริงในการเพิ่มหน่วยความจำในตอนนี้แทนที่จะรอให้ GC เริ่มเตะ ฉันคิดว่าคำถามนี้สมควรได้รับการอภิปรายเพิ่มเติม
Punit Vora

40
IDisposableไม่ได้ทำเครื่องหมายอะไรเลย Disposeวิธีการทำในสิ่งที่มันได้ทำทรัพยากรทำความสะอาดโดยใช้อินสแตนซ์ สิ่งนี้ไม่เกี่ยวข้องกับ GC
John Saunders

4
@จอห์น. IDisposableฉันไม่เข้าใจ และนี่คือเหตุผลที่ฉันบอกว่าคำตอบที่ยอมรับไม่ตอบคำถามที่ตั้งใจไว้ของ OP (และแก้ไขการติดตาม) ว่า IDisposable จะช่วยใน <i> การเพิ่มหน่วยความจำ </i> หรือไม่ เนื่องจากIDisposableไม่มีอะไรเกี่ยวข้องกับการเพิ่มหน่วยความจำเพียงแค่ทรัพยากรแล้วอย่างที่คุณพูดคุณไม่จำเป็นต้องตั้งค่าการอ้างอิงที่มีการจัดการเป็นโมฆะซึ่งเป็นสิ่งที่ OP ทำในตัวอย่างของเขา ดังนั้นคำตอบที่ถูกต้องสำหรับคำถามของเขาคือ "ไม่มันไม่ได้ช่วยให้หน่วยความจำว่างเร็วขึ้นจริง ๆ แล้วมันไม่ได้ช่วยให้หน่วยความจำว่าง แต่เป็นทรัพยากรเท่านั้น" แต่อย่างไรก็ตามขอบคุณสำหรับข้อมูลของคุณ
Punit Vora

9
@desigeek: ถ้าเป็นเช่นนั้นคุณไม่ควรพูดว่า "IDisposable ไม่ 'เรียก' GC มันแค่ 'ทำเครื่องหมาย' วัตถุที่ทำลายได้"
John Saunders

5
@desigeek: ไม่มีวิธีรับประกันการเพิ่มหน่วยความจำอย่างแน่นอน คุณสามารถโทร GC.Collect () แต่นั่นเป็นคำขอที่สุภาพไม่ใช่ความต้องการ เธรดที่กำลังทำงานทั้งหมดจะต้องถูกระงับชั่วคราวเพื่อให้การรวบรวมขยะดำเนินการต่อ - อ่านแนวคิดของ. NET safepoints หากคุณต้องการเรียนรู้เพิ่มเติมเช่นmsdn.microsoft.com/en-us/library/678ysw69(v=vs.110) aspx หากเธรดไม่สามารถหยุดชั่วคราวได้เช่นเนื่องจากมีการเรียกใช้โค้ดที่ไม่มีการจัดการ GC.Collect () อาจไม่ทำอะไรเลย
Gannet คอนกรีต

คำตอบ:


2608

จุดกำจัดคือการเพิ่มทรัพยากรที่ไม่มีการจัดการ มันจำเป็นต้องทำในบางจุดมิฉะนั้นพวกเขาจะไม่ทำความสะอาด เก็บขยะไม่ทราบวิธีการเรียกร้องDeleteHandle()ในตัวแปรชนิดIntPtrก็ไม่ทราบว่าจะDeleteHandle()ได้หรือไม่ก็ต้องโทร

หมายเหตุ : ทรัพยากรที่ไม่มีการจัดการคืออะไร? หากคุณพบมันใน Microsoft .NET Framework: มีการจัดการ หากคุณไปรอบ ๆ MSDN ด้วยตัวคุณเองมันก็ไม่มีการจัดการ สิ่งใดก็ตามที่คุณใช้การโทรแบบ P / Invoke เพื่อออกไปข้างนอกโลกที่แสนสบายของทุกสิ่งที่มีให้คุณใน. NET Framework นั้นไม่มีการจัดการ - และตอนนี้คุณต้องรับผิดชอบในการทำความสะอาด

วัตถุที่คุณสร้างขึ้นต้องเปิดเผยวิธีการบางอย่างที่โลกภายนอกสามารถเรียกใช้เพื่อล้างทรัพยากรที่ไม่มีการจัดการ วิธีนี้สามารถตั้งชื่อตามที่คุณต้องการ:

public void Cleanup()

หรือ

public void Shutdown()

แต่มีชื่อมาตรฐานสำหรับวิธีนี้แทน:

public void Dispose()

มีแม้กระทั่งอินเตอร์เฟสที่สร้างขึ้นIDisposableซึ่งมีเพียงหนึ่งวิธี:

public interface IDisposable
{
   void Dispose()
}

ดังนั้นคุณทำให้วัตถุของคุณเปิดเผยIDisposableอินเทอร์เฟซและวิธีที่คุณสัญญาว่าคุณได้เขียนวิธีการเดียวเพื่อล้างทรัพยากรที่ไม่มีการจัดการของคุณ:

public void Dispose()
{
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);
}

และคุณทำเสร็จแล้ว ยกเว้นคุณสามารถทำได้ดีกว่า


จะเกิดอะไรขึ้นถ้าวัตถุของคุณจัดสรรSystem.Drawing.Bitmap ขนาด 250MB (เช่นคลาส. NET จัดการบิตแมป) เป็นบัฟเฟอร์เฟรมบางประเภท แน่นอนว่านี่เป็นวัตถุ. NET ที่ได้รับการจัดการและตัวรวบรวมขยะจะทำให้เป็นอิสระ แต่คุณต้องการที่จะออกจากหน่วยความจำ 250MB เพียงแค่นั่งอยู่ที่นั่นรอให้นักสะสมขยะทิ้งตัวและปล่อยให้เป็นอิสระในที่สุด ? เกิดอะไรขึ้นถ้ามีการเชื่อมต่อฐานข้อมูลเปิด ? แน่นอนว่าเราไม่ต้องการให้การเชื่อมต่อนั้นเปิดอยู่รอให้ GC ทำการทำให้วัตถุเสร็จสิ้น

หากผู้ใช้ได้เรียกDispose()(หมายถึงพวกเขาไม่ได้วางแผนที่จะใช้วัตถุอีกต่อไป) ทำไมไม่กำจัดบิตแมปที่สิ้นเปลืองและการเชื่อมต่อฐานข้อมูลเหล่านั้นออกไป?

ดังนั้นตอนนี้เราจะ:

  • กำจัดทรัพยากรที่ไม่มีการจัดการ (เพราะเราต้อง) และ
  • กำจัดทรัพยากรที่มีการจัดการ (เพราะเราต้องการเป็นประโยชน์)

ดังนั้นให้ปรับปรุงDispose()วิธีการของเราเพื่อกำจัดวัตถุที่มีการจัดการเหล่านั้น:

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose();
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose();
      this.frameBufferImage = null;
   }
}

และทุกอย่างก็ดียกเว้นคุณทำได้ดีกว่า !


เกิดอะไรขึ้นถ้าคนลืมที่จะโทรหาDispose()วัตถุของคุณ? จากนั้นพวกเขาก็จะรั่วไหลทรัพยากรที่ไม่มีการจัดการบางอย่าง!

หมายเหตุ:พวกเขาจะไม่รั่วไหลทรัพยากรที่มีการจัดการเพราะในที่สุดตัวรวบรวมขยะจะทำงานบนเธรดพื้นหลังและเพิ่มหน่วยความจำที่เกี่ยวข้องกับวัตถุที่ไม่ได้ใช้ ซึ่งจะรวมถึงวัตถุของคุณและวัตถุที่มีการจัดการใด ๆ ที่คุณใช้ (เช่นBitmapและDbConnection)

หากบุคคลนั้นลืมโทรDispose()เรายังคงสามารถบันทึกเบคอนได้! เรายังคงมีวิธีเรียกมันสำหรับพวกเขา: ในที่สุดเมื่อตัวรวบรวมขยะได้รับการแก้ไข (เช่นการจบ) วัตถุของเรา

หมายเหตุ:ในที่สุดตัวรวบรวมขยะจะทำให้วัตถุที่ได้รับการจัดการหมดสิ้น เมื่อใดก็จะเรียกFinalize วิธีการบนวัตถุ GC ไม่รู้เกี่ยวกับวิธีการกำจัดของคุณ นั่นเป็นเพียงชื่อที่เราเลือกสำหรับวิธีการที่เราเรียกเมื่อเราต้องการกำจัดสิ่งที่ไม่มีการจัดการ

การทำลายวัตถุของเราโดย Garbage Collector เป็นเวลาที่เหมาะสมในการปลดปล่อยทรัพยากรที่ไม่มีการจัดการ เราทำสิ่งนี้โดยการเอาชนะFinalize()เมธอด

หมายเหตุ:ใน C # คุณจะไม่แทนที่Finalize()วิธีการอย่างชัดเจน คุณเขียนวิธีที่ดูเหมือนc ++ destructorและคอมไพเลอร์จะใช้เวลาที่จะต้องมีการดำเนินการของคุณFinalize()วิธีการ:

~MyObject()
{
    //we're being finalized (i.e. destroyed), call Dispose in case the user forgot to
    Dispose(); //<--Warning: subtle bug! Keep reading!
}

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

public void Dispose()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.gdiCursorBitmapStreamFileHandle);

   //Free managed resources too
   if (this.databaseConnection != null)
   {
      this.databaseConnection.Dispose(); //<-- crash, GC already destroyed it
      this.databaseConnection = null;
   }
   if (this.frameBufferImage != null)
   {
      this.frameBufferImage.Dispose(); //<-- crash, GC already destroyed it
      this.frameBufferImage = null;
   }
}

ดังนั้นสิ่งที่คุณต้องการคือวิธีที่Finalize()จะบอกDispose()ว่าไม่ควรแตะต้องทรัพยากรที่มีการจัดการใด ๆ (เพราะอาจไม่มีอยู่อีกต่อไป) ในขณะที่ยังคงปล่อยทรัพยากรที่ไม่มีการจัดการ

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

วิธีการภายในนี้อาจได้รับชื่อตามอำเภอใจเช่น "CoreDispose" หรือ "MyInternalDispose" แต่เป็นประเพณีที่เรียกว่าDispose(Boolean):

protected void Dispose(Boolean disposing)

แต่ชื่อพารามิเตอร์ที่เป็นประโยชน์มากขึ้นอาจเป็น:

protected void Dispose(Boolean itIsSafeToAlsoFreeManagedObjects)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //Free managed resources too, but only if I'm being called from Dispose
   //(If I'm being called from Finalize then the objects might not exist
   //anymore
   if (itIsSafeToAlsoFreeManagedObjects)  
   {    
      if (this.databaseConnection != null)
      {
         this.databaseConnection.Dispose();
         this.databaseConnection = null;
      }
      if (this.frameBufferImage != null)
      {
         this.frameBufferImage.Dispose();
         this.frameBufferImage = null;
      }
   }
}

และคุณเปลี่ยนวิธีการใช้งานของคุณเป็นIDisposable.Dispose():

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
}

และผู้เข้ารอบสุดท้ายของคุณไปที่:

~MyObject()
{
   Dispose(false); //I am *not* calling you from Dispose, it's *not* safe
}

หมายเหตุ : หากวัตถุของคุณเคลื่อนลงมาจากวัตถุที่นำไปใช้Disposeอย่าลืมเรียกวิธีกำจัดทิ้งฐานเมื่อคุณแทนที่ทิ้ง:

public override void Dispose()
{
    try
    {
        Dispose(true); //true: safe to free managed resources
    }
    finally
    {
        base.Dispose();
    }
}

และทุกอย่างก็ดียกเว้นคุณทำได้ดีกว่า !


หากผู้ใช้โทรหาDispose()วัตถุของคุณทุกอย่างก็จะถูกกำจัด ในภายหลังเมื่อตัวรวบรวมขยะเข้ามาและโทรออกแล้วมันจะโทรDisposeอีกครั้ง

ไม่เพียง แต่จะสิ้นเปลือง แต่หากวัตถุของคุณมีการอ้างอิงขยะไปยังวัตถุที่คุณกำจัดไปแล้วจากการโทรครั้งสุดท้ายไปDispose()คุณจะพยายามกำจัดพวกเขาอีกครั้ง!

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

เมื่อผู้ใช้โทรDispose(): หมายเลขอ้างอิงCursorFileBitmapIconServiceHandleถูกทำลาย ต่อมาเมื่อตัวรวบรวมขยะทำงานจะพยายามทำลายที่จับเดียวกันอีกครั้ง

protected void Dispose(Boolean iAmBeingCalledFromDisposeAndNotFinalize)
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle); //<--double destroy 
   ...
}

วิธีที่คุณแก้ไขปัญหานี้คือแจ้งให้ผู้รวบรวมขยะทราบว่าไม่จำเป็นต้องกังวลกับการจบวัตถุ - ทรัพยากรได้ถูกล้างข้อมูลแล้วและไม่จำเป็นต้องทำงานอีกต่อไป คุณทำได้โดยการโทรGC.SuppressFinalize()ในDispose()วิธีการ:

public void Dispose()
{
   Dispose(true); //I am calling you from Dispose, it's safe
   GC.SuppressFinalize(this); //Hey, GC: don't bother calling finalize later
}

ตอนนี้ผู้ใช้เรียกDispose()เราได้:

  • ปล่อยทรัพยากรที่ไม่มีการจัดการ
  • การจัดการทรัพยากรที่เป็นอิสระ

ไม่มีประเด็นใดที่ GC จะใช้งาน finalizer - ทุกอย่างได้รับการดูแล

ฉันไม่สามารถใช้ Finalize เพื่อล้างทรัพยากรที่ไม่มีการจัดการได้หรือไม่

เอกสารสำหรับObject.Finalizeพูดว่า:

กระบวนการ Finalize จะใช้เพื่อดำเนินการล้างข้อมูลบนทรัพยากรที่ไม่มีการจัดการที่มีอยู่ในวัตถุปัจจุบันก่อนที่วัตถุจะถูกทำลาย

แต่เอกสาร MSDN ก็บอกว่าสำหรับIDisposable.Dispose:

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

แล้วมันคืออะไร? อันไหนที่ฉันจะล้างทรัพยากรที่ไม่มีการจัดการ? คำตอบคือ:

มันเป็นทางเลือกของคุณ! Disposeแต่เลือก

แน่นอนคุณสามารถวางการล้างข้อมูลที่ไม่มีการจัดการของคุณใน finalizer:

~MyObject()
{
   //Free unmanaged resources
   Win32.DestroyHandle(this.CursorFileBitmapIconServiceHandle);

   //A C# destructor automatically calls the destructor of its base class.
}

ปัญหาที่เกิดขึ้นคือคุณไม่มีความคิดว่าเมื่อใดที่ตัวรวบรวมขยะจะได้รับการแก้ไขวัตถุของคุณ ทรัพยากรดั้งเดิมที่ไม่ได้รับการจัดการและไม่ต้องการและไม่ได้ใช้ของคุณจะติดอยู่รอบ ๆ จนกว่าตัวรวบรวมขยะจะทำงานในที่สุด จากนั้นจะเรียกวิธีการสุดท้ายของคุณ ล้างทรัพยากรที่ไม่มีการจัดการ เอกสารของObject.Finalizeชี้สิ่งนี้:

เวลาที่แน่นอนเมื่อดำเนินการขั้นสุดท้ายไม่ได้กำหนด เพื่อให้แน่ใจว่ามีการปล่อยทรัพยากรอย่างไม่แน่นอนสำหรับอินสแตนซ์ของคลาสของคุณให้ใช้เมธอดปิดหรือจัดให้มีIDisposable.Disposeการนำไปใช้

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


หากต้องการตอบคำถามเดิมของคุณ: ทำไมไม่ปล่อยหน่วยความจำตอนนี้แทนที่จะตัดสินใจเมื่อ GC ตัดสินใจทำ? ฉันมีซอฟต์แวร์การจดจำใบหน้าที่ความต้องการที่จะกำจัดของ 530 MB ของภาพภายในในขณะนี้เนื่องจากพวกเขากำลังไม่จำเป็น เมื่อเราทำไม่ได้เครื่องจะทำการหยุดการแลกเปลี่ยน

การอ่านโบนัส

สำหรับทุกคนที่ชอบสไตล์ของคำตอบนี้ (อธิบายสาเหตุดังนั้นวิธีที่ชัดเจน) ฉันขอแนะนำให้คุณอ่านบทที่หนึ่งใน COM Essential ของ Don Box:

ใน 35 หน้าเขาอธิบายปัญหาของการใช้วัตถุไบนารีและประดิษฐ์ COM ต่อหน้าต่อตาคุณ เมื่อคุณตระหนักถึงสาเหตุของ COM หน้าเว็บที่เหลืออีก 300 หน้านั้นชัดเจนและเพียงแสดงรายละเอียดการใช้งานของ Microsoft

ฉันคิดว่าโปรแกรมเมอร์ทุกคนที่เคยจัดการกับวัตถุหรือ COM ควรอ่านตอนแรกอย่างน้อยที่สุด มันเป็นคำอธิบายที่ดีที่สุดสำหรับทุกสิ่ง

การอ่านโบนัสพิเศษ

เมื่อทุกสิ่งที่คุณรู้ว่าผิดโดย Eric Lippert

มันจึงเป็นเรื่องยากมากอย่างแน่นอนที่จะเขียน finalizer ที่ถูกต้องและคำแนะนำที่ดีที่สุดที่ฉันสามารถให้คุณคือการไม่พยายาม


12
คุณทำได้ดีกว่า - คุณต้องเพิ่มการเรียกไปยัง GC.SuppressFinalize () ใน Dispose
ฐานของ

55
@Daniel Earwicker: จริง Microsoft จะรักให้คุณหยุดใช้ Win32 โดยสิ้นเชิงและยึดติดกับการโทรแบบ. NET Framework ที่เป็นนามธรรมและพกพาได้อย่างอิสระ หากคุณต้องการที่จะไปรอบ ๆ ระบบปฏิบัติการภายใต้; เพราะคุณคิดว่าคุณรู้ว่าระบบปฏิบัติการกำลังทำงานอะไร: คุณกำลังใช้ชีวิตของคุณเอง แอป NET. บางรายการไม่ทำงานบน Windows หรือบนเดสก์ท็อป
เอียนบอยด์

34
นี่เป็นคำตอบที่ดี แต่ฉันคิดว่ามันจะได้ประโยชน์จากการเขียนโค้ดสุดท้ายสำหรับเคสมาตรฐานและสำหรับกรณีที่คลาสมาจาก baseclass ที่ใช้การจัดการแล้ว เช่นได้อ่านที่นี่ ( msdn.microsoft.com/en-us/library/aa720161%28v=vs.71%29.aspx ) และฉันสับสนในสิ่งที่ฉันควรทำเมื่อมาจากคลาสที่ใช้การกำจัดแล้ว ( เฮ้ฉันใหม่สำหรับเรื่องนี้)
integra753

5
@GregS และอื่น ๆ : nullโดยทั่วไปฉันจะไม่รบกวนการตั้งค่าการอ้างอิงไปยัง ประการแรกหมายความว่าคุณไม่สามารถทำได้readonlyและอย่างที่สองคุณต้องทำการ!=nullตรวจสอบที่น่าเกลียดมาก(เช่นในรหัสตัวอย่าง) คุณอาจมีธงdisposedแต่ง่ายกว่าที่จะไม่ไปสนใจมัน .NET GC นั้นก้าวร้าวมากพอที่การอ้างอิงไปยังเขตข้อมูลxจะไม่ถูกนับว่า 'ใช้' อีกต่อไปเมื่อถึงเวลาผ่านx.Dispose()เส้น
porges

7
ในหน้าสองของหนังสือ Don Box ที่คุณพูดถึงเขาใช้ตัวอย่างของการใช้อัลกอริทึมการค้นหา O (1) ซึ่งมี "รายละเอียดเหลือไว้สำหรับการออกกำลังกายสำหรับผู้อ่าน" ฉันหัวเราะ.
wip

65

IDisposableมักใช้เพื่อใช้ประโยชน์จากusingคำสั่งและใช้ประโยชน์จากวิธีง่าย ๆ ในการทำความสะอาดวัตถุที่มีการจัดการ

public class LoggingContext : IDisposable {
    public Finicky(string name) {
        Log.Write("Entering Log Context {0}", name);
        Log.Indent();
    }
    public void Dispose() {
        Log.Outdent();
    }

    public static void Main() {
        Log.Write("Some initial stuff.");
        try {
            using(new LoggingContext()) {
                Log.Write("Some stuff inside the context.");
                throw new Exception();
            }
        } catch {
            Log.Write("Man, that was a heavy exception caught from inside a child logging context!");
        } finally {
            Log.Write("Some final stuff.");
        }
    }
}

6
ฉันชอบสิ่งนั้นเป็นการส่วนตัว แต่มันไม่ได้หลอกลวงกับแนวทางการออกแบบกรอบ
mqp

4
ฉันจะพิจารณาว่าเป็นการออกแบบที่เหมาะสมเพราะมันช่วยให้สามารถกำหนดขอบเขตและสร้างขอบเขต / การล้างข้อมูลได้ง่ายโดยเฉพาะอย่างยิ่งเมื่อผสมกับการจัดการข้อยกเว้นการล็อคและการจัดการทรัพยากรโดยใช้บล็อกในวิธีที่ซับซ้อน ภาษานำเสนอสิ่งนี้เป็นคุณสมบัติชั้นหนึ่ง
yfeldblum

ไม่ปฏิบัติตามกฎที่ระบุใน FDG อย่างแน่นอน แต่เป็นการใช้รูปแบบที่ถูกต้องตามที่ต้องการเพื่อใช้โดย "ข้อความสั่ง"
Scott Dorman

2
ตราบใดที่ Log.Outdent ไม่ได้โยนไม่มีอะไรผิดปกติกับเรื่องนี้
Daniel Earwicker

1
คำตอบที่หลากหลายต่อการใช้ IDisposable และ“ การใช้” เป็นสิ่งที่ไม่เหมาะสมในการใช้“ พฤติกรรมที่กำหนดขอบเขต” เพื่อความปลอดภัย อธิบายรายละเอียดเพิ่มเติมว่าทำไมคนอื่นถึงชอบ / ไม่ชอบเทคนิคนี้ มันค่อนข้างขัดแย้ง
Brian

44

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

คุณสามารถดูบทความนี้สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับวิธีการใช้รูปแบบการกำจัด แต่โดยทั่วไปจะมีลักษณะดังนี้:

public class SimpleCleanup : IDisposable
{
    // some fields that require cleanup
    private SafeHandle handle;
    private bool disposed = false; // to detect redundant calls

    public SimpleCleanup()
    {
        this.handle = /*...*/;
    }

    protected virtual void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Dispose managed resources.
                if (handle != null)
                {
                    handle.Dispose();
                }
            }

            // Dispose unmanaged managed resources.

            disposed = true;
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }
}

วิธีการที่สำคัญที่สุดในที่นี้คือ Dispose (bool) ซึ่งจริงๆแล้วทำงานภายใต้สองสถานการณ์ที่แตกต่างกัน:

  • การกำจัด == จริง: วิธีการที่ได้รับการเรียกโดยตรงหรือโดยอ้อมโดยรหัสของผู้ใช้ ทรัพยากรที่มีการจัดการและไม่มีการจัดการสามารถกำจัดได้
  • การกำจัด == false: วิธีการถูกเรียกใช้โดยรันไทม์จากภายใน finalizer และคุณไม่ควรอ้างอิงวัตถุอื่น ๆ ทรัพยากรที่ไม่มีการจัดการเท่านั้นที่สามารถกำจัดได้

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

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

สถานการณ์ของคุณไม่ถูกต้องเนื่องจากสตริงใน. NET ไม่ได้ใช้ทรัพยากรที่ไม่มีการเปลี่ยนแปลงใด ๆ และไม่ได้ใช้ IDisposable ไม่มีวิธีใดที่จะบังคับให้พวกเขา "ล้างข้อมูล"


4
คุณไม่ลืมที่จะใช้งานตัวเลือกสุดท้ายหรือไม่?
Budda

@Budda: ไม่เขาใช้ SafeHandle ไม่จำเป็นต้องมี destructor
Henk Holterman

9
+1 สำหรับเพิ่มตาข่ายความปลอดภัยสำหรับการโทรหลายครั้งเพื่อทิ้ง () ข้อมูลจำเพาะระบุว่าการโทรหลายครั้งควรปลอดภัย คลาส Microsoft มากเกินไปไม่สามารถใช้งานได้และคุณได้รับ ObjectDisposedException ที่น่ารำคาญ
Jesse Chisholm

5
แต่ Dispose (bool disposing) เป็นวิธีการของคุณเองในคลาส SimpleCleanup ของคุณและจะไม่ถูกเรียกโดย Framework เนื่องจากคุณเรียกมันว่า "true" เป็นพารามิเตอร์การ 'กำจัด' จะไม่เป็นเท็จ รหัสของคุณคล้ายกับตัวอย่าง MSDN สำหรับ IDisposable แต่ไม่มีตัวกำหนดขั้นสุดท้ายตามที่ @Budda ชี้ให้เห็นซึ่งเป็นที่ที่การเรียกใช้การกำจัด = false จะมาจาก
yoyo

19

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

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

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

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

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

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

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

สำหรับทรัพยากรประเภทนั้นคุณสามารถนำไปใช้งานIDisposableได้โดยไม่ต้องมีเครื่องมือขั้นสุดท้าย Finalizer เป็นทางเลือกอย่างแน่นอน - ต้องเป็น นี่มันวาวหรือไม่ได้พูดถึงในหนังสือหลายเล่ม

จากนั้นคุณต้องใช้usingคำสั่งเพื่อให้มั่นใจว่าDisposeมีการเรียกใช้ นี่เป็นหลักเหมือนกับการผูกปมการขับขี่ด้วยสแต็ก (เพื่อให้ finalizer คือ GC usingคือสแต็ก)

ส่วนที่ขาดหายไปคือคุณต้องเขียน Dispose ด้วยตนเองและโทรไปยังเขตข้อมูลและคลาสพื้นฐานของคุณ โปรแกรมเมอร์ C ++ / CLI ไม่จำเป็นต้องทำเช่นนั้น คอมไพเลอร์เขียนมันสำหรับพวกเขาในกรณีส่วนใหญ่

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

แทนที่จะเขียนคลาสคุณเขียนฟังก์ชัน ฟังก์ชั่นรับผู้แทนเพื่อโทรกลับไปที่:

public static void Indented(this Log log, Action action)
{
    log.Indent();
    try
    {
        action();
    }
    finally
    {
        log.Outdent();
    }
}

แล้วตัวอย่างง่ายๆก็คือ:

Log.Write("Message at the top");
Log.Indented(() =>
{
    Log.Write("And this is indented");

    Log.Indented(() =>
    {
        Log.Write("This is even more indented");
    });
});
Log.Write("Back at the outermost level again");

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

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


Re: "ไม่ควรมีการเรียกใช้เมธอดของวัตถุใด ๆ เพิ่มเติมหลังจากมีการเรียกใช้ Dispose" "ควร" เป็นคำผ่าตัด หากคุณมีการดำเนินการแบบอะซิงโครนัสที่ค้างอยู่พวกเขาอาจเข้ามาหลังจากที่วัตถุของคุณถูกกำจัด ทำให้เกิด ObjectDisposedException
Jesse Chisholm

ดูเหมือนว่าคุณจะเป็นคำตอบเดียวที่นอกเหนือจากของฉันซึ่งสัมผัสกับความคิดที่ว่าทรัพยากรที่ไม่มีการจัดการห่อหุ้มรัฐที่ GC ไม่เข้าใจ ลักษณะสำคัญของทรัพยากรที่ไม่มีการจัดการอย่างไรก็ตามคือเอนทิตีหนึ่งรายการขึ้นไปที่รัฐอาจต้องล้างสถานะให้สามารถดำเนินการต่อได้แม้ว่าวัตถุที่ "เป็นเจ้าของ" ทรัพยากรจะไม่ คุณชอบคำจำกัดความของฉันอย่างไร ค่อนข้างคล้ายกัน แต่ฉันคิดว่ามันทำให้ "ทรัพยากร" เป็นคำนามอีกเล็กน้อย (เป็น "ข้อตกลง" โดยวัตถุภายนอกเพื่อเปลี่ยนพฤติกรรมของมันเพื่อแลกกับการแจ้งเตือนเมื่อไม่จำเป็นต้องใช้บริการอีกต่อไป)
supercat

@supercat - หากคุณสนใจฉันเขียนโพสต์ต่อไปนี้สองสามวันหลังจากที่ฉันเขียนคำตอบข้างต้น: smellegantcode.wordpress.com/2009/02/13/ …
Daniel Earwicker

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

1
คู่ของการดำเนินงานenterและexitเป็นแกนหลักของวิธีคิดทรัพยากร การสมัคร / ยกเลิกการเป็นสมาชิกในเหตุการณ์ควรสอดคล้องกับสิ่งนั้นโดยไม่ยาก ในแง่ของคุณสมบัติ orthogonal / fungible มันจะแยกไม่ออกจากหน่วยความจำรั่ว (สิ่งนี้ไม่น่าแปลกใจเนื่องจากการสมัครสมาชิกเป็นเพียงการเพิ่มวัตถุลงในรายการ)
Daniel Earwicker

17

สถานการณ์สมมติที่ฉันใช้ประโยชน์จาก IDisposable: ล้างทรัพยากรที่ไม่มีการจัดการยกเลิกการสมัครสำหรับกิจกรรมปิดการเชื่อมต่อ

สำนวนที่ฉันใช้สำหรับการติดตั้ง IDisposable ( ไม่ใช่ threadsafe ):

class MyClass : IDisposable {
    // ...

    #region IDisposable Members and Helpers
    private bool disposed = false;

    public void Dispose() {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing) {
        if (!this.disposed) {
            if (disposing) {
                // cleanup code goes here
            }
            disposed = true;
        }
    }

    ~MyClass() {
        Dispose(false);
    }
    #endregion
}

คำอธิบายแบบเต็มสามารถดูได้ที่msdn.microsoft.com/en-us/library/b1yfkh5e.aspx
LicenseQ

3
ไม่ควรมีผู้เข้ารอบสุดท้ายรวมอยู่ด้วยเว้นแต่คุณจะมีทรัพยากรที่ไม่มีการจัดการ แม้กระนั้นการใช้งานที่ต้องการคือการห่อทรัพยากรที่ไม่มีการจัดการใน SafeHandle
เดฟสีดำ

11

ใช่รหัสนั้นซ้ำซ้อนอย่างสมบูรณ์และไม่จำเป็นและจะไม่ทำให้ตัวรวบรวมข้อมูลขยะทำสิ่งใดที่มันจะไม่ทำอย่าง.Clear()อื่น

คำตอบสำหรับการแก้ไขของคุณ: เรียงจาก ถ้าฉันทำสิ่งนี้:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has no Dispose() method
    instance.FillItWithAMillionStrings();
}

// 1 million strings are in memory, but marked for reclamation by the GC

มันใช้งานได้เหมือนกันกับสิ่งนี้เพื่อวัตถุประสงค์ในการจัดการหน่วยความจำ:

public void WasteMemory()
{
    var instance = new MyCollection(); // this one has your Dispose()
    instance.FillItWithAMillionStrings();
    instance.Dispose();
}

// 1 million strings are in memory, but marked for reclamation by the GC

GC.Collect()หากคุณจริงๆจริงๆต้องเพิ่มหน่วยความจำนี้มากทันทีโทร ไม่มีเหตุผลที่จะทำเช่นนี้ที่นี่ หน่วยความจำจะถูกปลดปล่อยเมื่อจำเป็น


2
เรื่อง: "หน่วยความจำจะถูกปลดปล่อยเมื่อจำเป็น" ค่อนข้างว่า "เมื่อ GC ตัดสินใจว่าจำเป็นต้องใช้" คุณอาจเห็นปัญหาประสิทธิภาพของระบบก่อนที่ GC จะตัดสินใจว่าจำเป็นต้องใช้หน่วยความจำจริงๆ การเลิกตอนนี้อาจไม่จำเป็น แต่อาจมีประโยชน์
Jesse Chisholm

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

1
... การ zeroing out อาร์เรย์ดังกล่าวก่อนที่จะละทิ้งพวกเขาบางครั้งฉันลดผลกระทบ GC
supercat

11

ถ้าMyCollectionเป็นไปได้ที่จะเก็บขยะล่ะก็คุณไม่จำเป็นต้องกำจัดมัน การทำเช่นนั้นจะทำให้ CPU ปั่นป่วนเกินความจำเป็นและอาจทำให้การวิเคราะห์ที่คำนวณไว้ล่วงหน้าบางอย่างที่ตัวรวบรวมขยะได้ดำเนินการไปแล้ว

ฉันใช้IDisposableในการทำสิ่งต่าง ๆ เช่นตรวจสอบให้แน่ใจว่ามีการจัดการเธรดอย่างถูกต้องพร้อมกับทรัพยากรที่ไม่มีการจัดการ

แก้ไขเพื่อตอบสนองต่อความคิดเห็นของ Scott:

ครั้งเดียวที่ตัวชี้วัดประสิทธิภาพของ GC ได้รับผลกระทบคือเมื่อมีการโทร [sic] GC.Collect () "

ตามหลักการแล้ว GC จะรักษามุมมองของกราฟอ้างอิงวัตถุและการอ้างอิงทั้งหมดจากสแต็กเฟรมของเธรด ฮีปนี้มีขนาดค่อนข้างใหญ่และครอบคลุมหน่วยความจำหลายหน้า เป็นการเพิ่มประสิทธิภาพ GC เก็บการวิเคราะห์หน้าเว็บที่ไม่น่าจะเปลี่ยนแปลงบ่อยนักเพื่อหลีกเลี่ยงการสแกนซ้ำหน้าเว็บโดยไม่จำเป็น GC ได้รับการแจ้งเตือนจากเคอร์เนลเมื่อข้อมูลในหน้าเปลี่ยนแปลงดังนั้นจึงรู้ว่าหน้าเว็บสกปรกและต้องมีการสแกนซ้ำ หากคอลเลกชันอยู่ใน Gen0 ก็เป็นไปได้ว่าสิ่งอื่น ๆ ในหน้านั้นมีการเปลี่ยนแปลงเช่นกัน แต่สิ่งนี้มีโอกาสน้อยกว่าใน Gen1 และ Gen2 โดยปกติตะขอเหล่านี้ไม่สามารถใช้งานได้ใน Mac OS X สำหรับทีมที่ย้าย GC ไปยัง Mac เพื่อให้ปลั๊กอิน Silverlight ทำงานบนแพลตฟอร์มนั้น

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

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

กรอบงาน. NET ใช้IDisposableอินเทอร์เฟซเป็นสัญญาณเตือนผู้พัฒนาว่าคลาสนี้ต้องถูกกำจัด ฉันไม่สามารถคิดของชนิดใด ๆ ในกรอบที่ใช้IDisposable(ไม่รวมการใช้งานอินเตอร์เฟซที่ชัดเจน) ซึ่งเป็นตัวเลือกการกำจัด


การโทรติดต่อถูกต้องสมบูรณ์ถูกกฎหมายและได้รับการสนับสนุน วัตถุที่ใช้ IDisposable มักจะทำด้วยเหตุผล ครั้งเดียวที่ตัวชี้วัดประสิทธิภาพของ GC ได้รับผลกระทบคือเมื่อทำการเรียก GC.Collect ()
Scott Dorman

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

1
ในกรณีของแบบอักษรฉันสงสัยว่าปัญหาคือ Microsoft ไม่ได้กำหนดสิ่งที่รับผิดชอบในการกำจัดวัตถุ "แบบอักษร" ที่กำหนดให้กับตัวควบคุม ในบางกรณีตัวควบคุมอาจใช้แบบอักษรร่วมกับวัตถุที่มีอายุยืนกว่าดังนั้นการมีตัวควบคุมการกำจัดแบบอักษรจะไม่ดี ในกรณีอื่น ๆ แบบอักษรจะถูกกำหนดให้กับตัวควบคุมและไม่มีที่อื่นดังนั้นถ้าตัวควบคุมไม่ได้กำจัดมันจะไม่มีใครทำ อนึ่งความยากลำบากในการใช้แบบอักษรนี้สามารถหลีกเลี่ยงได้ถ้ามีคลาส FontTemplate ที่ไม่ใช้แล้วทิ้งเนื่องจากการควบคุมดูเหมือนจะไม่ใช้ตัวจัดการ GDI ของฟอนต์ของพวกเขา
supercat

ในหัวข้อการDispose()โทรที่เป็นทางเลือกโปรดดู: stackoverflow.com/questions/913228/…
RJ Cuthbertson

7

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


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

ฉันเคยอ่านรหัสที่เป็น RollBack () อย่างง่าย ๆ เกี่ยวกับความล้มเหลวในการใช้ IDisposable คลาส MiniTx ด้านล่างจะตรวจสอบการตั้งค่าสถานะใน Dispose () และหากการCommitโทรไม่เคยเกิดขึ้นมันจะเรียกRollbackด้วยตัวเอง มันเพิ่มเลเยอร์ทางอ้อมทำให้รหัสการโทรนั้นง่ายต่อการเข้าใจและบำรุงรักษามากขึ้น ผลลัพธ์ดูเหมือนว่า:

using( MiniTx tx = new MiniTx() )
{
    // code that might not work.

    tx.Commit();
} 

ฉันเคยเห็นรหัสการจับเวลา / การบันทึกทำสิ่งเดียวกัน ในกรณีนี้เมธอด Dispose () จะหยุดตัวจับเวลาและบันทึกว่าบล็อกได้ออกจากการทำงานแล้ว

using( LogTimer log = new LogTimer("MyCategory", "Some message") )
{
    // code to time...
}

ดังนั้นนี่คือตัวอย่างที่เป็นรูปธรรมที่ไม่ได้ทำการล้างค่าทรัพยากรที่ไม่มีการจัดการใด ๆ แต่ใช้ IDisposable สำเร็จในการสร้างรหัสทำความสะอาด


ดูตัวอย่างของ @Daniel Earwicker โดยใช้ฟังก์ชั่นการสั่งซื้อที่สูงขึ้น สำหรับการเปรียบเทียบการกำหนดเวลาการบันทึกเป็นต้นมันดูเหมือนตรงไปตรงมามากขึ้น
Aluan Haddad


6

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

LargeStuff ระดับสาธารณะ
  ใช้ IDisposable
  ส่วนตัว _Large as string ()

  'โค้ดแปลก ๆ ที่แปลว่า _Large มีสตริงยาวหลายล้านสตริง

  การกำจัดสาธารณะย่อย () ดำเนินการ IDisposable.Dispose
    _Large = ไม่มีอะไร
  ส่วนท้าย

ฉันตระหนักว่าการใช้งานแบบใช้แล้วทิ้งไม่ได้ปฏิบัติตามแนวทางปัจจุบัน แต่หวังว่าคุณทุกคนจะได้รับแนวคิดนี้
ตอนนี้เมื่อ Dispose ถูกเรียกจำนวนหน่วยความจำได้รับอิสระ?

คำตอบ: ไม่มี
การเรียกการกำจัดสามารถปล่อยทรัพยากรที่ไม่มีการจัดการมันไม่สามารถเรียกคืนหน่วยความจำที่มีการจัดการได้เพียง GC เท่านั้นที่สามารถทำได้ ที่จะไม่พูดว่าข้างต้นไม่ได้เป็นความคิดที่ดีตามรูปแบบข้างต้นยังคงเป็นความคิดที่ดีในความเป็นจริง เมื่อเรียกใช้งานการกำจัดไม่มีสิ่งใดที่จะหยุด GC ที่อ้างสิทธิ์หน่วยความจำที่ถูกใช้โดย _Large อีกครั้งแม้ว่าอินสแตนซ์ของ LargeStuff อาจยังอยู่ในขอบเขต สตริงใน _Large อาจอยู่ใน gen 0 แต่อินสแตนซ์ของ LargeStuff อาจเป็น gen 2 ดังนั้นอีกครั้งหน่วยความจำจะถูกอ้างสิทธิ์อีกครั้งในไม่ช้า
ไม่มีจุดในการเพิ่มผู้เข้ารอบสุดท้ายที่จะเรียกวิธีการกำจัดที่แสดงข้างต้นว่า นั่นเป็นเพียงการหน่วงเวลาการอ้างสิทธิ์หน่วยความจำอีกครั้งเพื่อให้ผู้เข้ารอบสุดท้ายสามารถทำงานได้


1
หากเป็นตัวอย่างของLargeStuffได้รับรอบนานพอที่จะทำให้มันเป็นรุ่น 2 และถ้า_Largeถืออ้างอิงถึงสตริงที่สร้างขึ้นใหม่ซึ่งอยู่ในรุ่น 0 แล้วถ้าตัวอย่างของLargeStuffถูกยกเลิกโดยไม่ต้อง nulling ออก_Largeแล้วสตริงอ้างถึงโดย_Largeจะถูกเก็บไว้รอบ ๆ จนกว่าจะมีการรวบรวม Gen2 ถัดไป การ zeroing out _Largeอาจทำให้สตริงถูกตัดออกในคอลเล็กชัน Gen0 ถัดไป ในกรณีส่วนใหญ่การลบล้างการอ้างอิงไม่เป็นประโยชน์ แต่มีบางกรณีที่สามารถให้ประโยชน์บางอย่างได้
supercat

5

นอกเหนือจากการใช้หลักเป็นวิธีการควบคุมอายุการใช้งานของทรัพยากรระบบ (ครอบคลุมโดยคำตอบที่ยอดเยี่ยมของIan , Kudos!), IDisposable / using combo ยังสามารถใช้เพื่อกำหนดขอบเขตการเปลี่ยนแปลงสถานะของทรัพยากรโลก (วิกฤติ) : คอนโซลที่หัวข้อที่กระบวนการใด ๆวัตถุทั่วโลกเหมือนเช่นแอพลิเคชัน

ฉันได้เขียนบทความเกี่ยวกับรูปแบบนี้: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

มันแสดงให้เห็นถึงวิธีการที่คุณสามารถป้องกันบางรัฐทั่วโลกมักจะใช้ในนำมาใช้ใหม่และสามารถอ่านได้ลักษณะ: สีคอนโซลปัจจุบันวัฒนธรรมด้าย , Excel คุณสมบัติของวัตถุการประยุกต์ใช้ ...


4

หากมีสิ่งใดฉันคาดหวังว่ารหัสจะมีประสิทธิภาพน้อยกว่าเมื่อปล่อยทิ้งไว้

การเรียกเมธอด Clear () นั้นไม่จำเป็นและ GC อาจจะไม่ทำเช่นนั้นหากการกำจัดไม่ได้ทำ ...


2

มีสิ่งต่าง ๆ ที่การDispose()ดำเนินการทำในรหัสตัวอย่างที่อาจมีผลกระทบที่จะไม่เกิดขึ้นเนื่องจาก GC ปกติของMyCollectionวัตถุ

หากวัตถุอ้างอิงโดย_theListหรือ_theDictจะเรียกโดยวัตถุอื่น ๆ แล้วที่List<>หรือDictionary<>วัตถุที่จะไม่ต้องอยู่ภายใต้คอลเลกชัน แต่จู่ ๆ จะต้องไม่มีเนื้อหา หากไม่มีการดำเนินการ Dispose () ดังตัวอย่างคอลเลกชันเหล่านั้นจะยังคงมีเนื้อหาอยู่

แน่นอนถ้านี่เป็นสถานการณ์ที่ฉันจะเรียกมันว่าเป็นการออกแบบที่ไม่สมบูรณ์ - ฉันแค่ชี้ให้เห็น (อวดรู้ฉันคิดว่า) การDispose()ดำเนินการอาจไม่ซ้ำซ้อนอย่างสมบูรณ์ทั้งนี้ขึ้นอยู่กับว่ามีการใช้งานอื่น ๆList<>หรือDictionary<>ไม่ แสดงในส่วน


เป็นเขตข้อมูลส่วนตัวดังนั้นฉันคิดว่ามันยุติธรรมที่จะถือว่า OP ไม่ได้ให้การอ้างอิงกับพวกเขา
mqp

1) ส่วนของโค้ดเป็นเพียงตัวอย่างโค้ดดังนั้นฉันแค่ชี้ให้เห็นว่าอาจมีผลข้างเคียงที่มองข้ามได้ง่าย 2) ฟิลด์ส่วนตัวมักจะเป็นเป้าหมายของคุณสมบัติ / วิธีการทะเยอทะยาน - อาจมากเกินไป (บางคนได้รับการพิจารณาจากเซทเทอร์ / เซ็ตเตอร์ว่าเป็นรูปแบบการต่อต้าน)
ไมเคิลเสี้ยน

2

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

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

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


IMO คำจำกัดความของวัตถุที่ไม่มีการจัดการนั้นชัดเจน วัตถุใด ๆ ที่ไม่ใช่ GC
Eonil

1
@Eonil: Unmanaged Object! = ทรัพยากรที่ไม่มีการจัดการ สิ่งต่าง ๆ เช่นเหตุการณ์สามารถดำเนินการได้ทั้งหมดโดยใช้วัตถุที่มีการจัดการ แต่ก็ยังคงเป็นทรัพยากรที่ไม่มีการจัดการเพราะอย่างน้อยในกรณีของวัตถุที่มีอายุสั้นซึ่งสมัครรับกิจกรรมของวัตถุที่มีอายุยืนยาว GC ไม่รู้อะไรเลยเกี่ยวกับวิธีทำความสะอาด .
supercat


2

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

  1. ถ้าสร้างโดยฉันเรียนใช้ทรัพยากรที่ไม่มีการจัดการบางอย่างก็หมายความว่าฉันควรใช้อินเตอร์เฟซ IDisposable เพื่อล้างหน่วยความจำ
  2. ล้างวัตถุทันทีที่ฉันใช้งานเสร็จ
  3. ในวิธีการกำจัดของฉันฉันย้ำสมาชิก IDisposable ทั้งหมดของชั้นเรียนและโทรทิ้ง
  4. ในเมธอด Dispose ของฉันเรียก GC.SuppressFinalize (นี่) เพื่อแจ้งให้ผู้รวบรวมขยะทราบว่าวัตถุของฉันถูกกำจัดแล้ว ฉันทำเพราะการโทรของ GC เป็นการดำเนินการที่มีราคาแพง
  5. ข้อควรระวังเพิ่มเติมฉันพยายามโทรติดต่อ Dispose () หลายครั้ง
  6. บางครั้งฉันเพิ่มสมาชิกส่วนตัว _disposed และการเรียกใช้วิธีการเช็คอินวัตถุไม่ได้ถูกล้างข้อมูล และถ้ามันถูกทำความสะอาดให้สร้างเท็มเพลต ObjectDisposedException
    ต่อไปนี้แสดงให้เห็นถึงสิ่งที่ฉันอธิบายในคำว่าเป็นตัวอย่างของรหัส:

public class SomeClass : IDisposable
    {
        /// <summary>
        /// As usually I don't care was object disposed or not
        /// </summary>
        public void SomeMethod()
        {
            if (_disposed)
                throw new ObjectDisposedException("SomeClass instance been disposed");
        }

        public void Dispose()
        {
            Dispose(true);
        }

        private bool _disposed;

        protected virtual void Dispose(bool disposing)
        {
            if (_disposed)
                return;
            if (disposing)//we are in the first call
            {
            }
            _disposed = true;
        }
    }

1
"สำหรับฉันทรัพยากรที่ไม่มีการจัดการหมายถึงคลาสบางส่วนซึ่งใช้อินเทอร์เฟซ IDisposable หรือสิ่งที่สร้างขึ้นด้วยการใช้การเรียกไปยัง dll" ดังนั้นคุณกำลังบอกว่าประเภทใดที่is IDisposableควรพิจารณาว่าเป็นทรัพยากรที่ไม่มีการจัดการ ดูเหมือนจะไม่ถูกต้อง นอกจากนี้หากประเภทการฝังเป็นประเภทมูลค่าที่บริสุทธิ์คุณดูเหมือนจะแนะนำว่าไม่จำเป็นต้องกำจัด นั่นก็ดูเหมือนว่าผิด
Aluan Haddad

ทุกคนตัดสินด้วยตัวเอง ฉันไม่ต้องการเพิ่มโค้ดของฉันลงไปเพื่ออะไรเพิ่มเติม หมายความว่าถ้าฉันเพิ่ม IDisposable หมายความว่าฉันได้สร้างฟังก์ชันการทำงานบางอย่างที่ GC ไม่สามารถจัดการได้หรือฉันคิดว่ามันจะไม่สามารถจัดการอายุการใช้งานได้อย่างถูกต้อง
Yuriy Zaletskyy

2

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

ตัวอย่างต่อไปนี้แสดงตัวอย่างที่ดีสำหรับรูปแบบ IDisposable ด้วยรหัสและความคิดเห็น

public class DisposeExample
{
    // A base class that implements IDisposable. 
    // By implementing IDisposable, you are announcing that 
    // instances of this type allocate scarce resources. 
    public class MyResource: IDisposable
    {
        // Pointer to an external unmanaged resource. 
        private IntPtr handle;
        // Other managed resource this class uses. 
        private Component component = new Component();
        // Track whether Dispose has been called. 
        private bool disposed = false;

        // The class constructor. 
        public MyResource(IntPtr handle)
        {
            this.handle = handle;
        }

        // Implement IDisposable. 
        // Do not make this method virtual. 
        // A derived class should not be able to override this method. 
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method. 
            // Therefore, you should call GC.SupressFinalize to 
            // take this object off the finalization queue 
            // and prevent finalization code for this object 
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios. 
        // If disposing equals true, the method has been called directly 
        // or indirectly by a user's code. Managed and unmanaged resources 
        // can be disposed. 
        // If disposing equals false, the method has been called by the 
        // runtime from inside the finalizer and you should not reference 
        // other objects. Only unmanaged resources can be disposed. 
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called. 
            if(!this.disposed)
            {
                // If disposing equals true, dispose all managed 
                // and unmanaged resources. 
                if(disposing)
                {
                    // Dispose managed resources.
                    component.Dispose();
                }

                // Call the appropriate methods to clean up 
                // unmanaged resources here. 
                // If disposing is false, 
                // only the following code is executed.
                CloseHandle(handle);
                handle = IntPtr.Zero;

                // Note disposing has been done.
                disposed = true;

            }
        }

        // Use interop to call the method necessary 
        // to clean up the unmanaged resource.
        [System.Runtime.InteropServices.DllImport("Kernel32")]
        private extern static Boolean CloseHandle(IntPtr handle);

        // Use C# destructor syntax for finalization code. 
        // This destructor will run only if the Dispose method 
        // does not get called. 
        // It gives your base class the opportunity to finalize. 
        // Do not provide destructors in types derived from this class.
        ~MyResource()
        {
            // Do not re-create Dispose clean-up code here. 
            // Calling Dispose(false) is optimal in terms of 
            // readability and maintainability.
            Dispose(false);
        }
    }
    public static void Main()
    {
        // Insert code here to create 
        // and use the MyResource object.
    }
}

1

กรณีการใช้งานที่สมเหตุสมผลที่สุดสำหรับการกำจัดทรัพยากรที่มีการจัดการคือการเตรียมการ GC เพื่อเรียกคืนทรัพยากรที่ไม่เคยถูกรวบรวม

ตัวอย่างที่สำคัญคือการอ้างอิงแบบวงกลม

ในขณะที่วิธีปฏิบัติที่ดีที่สุดคือใช้รูปแบบที่หลีกเลี่ยงการอ้างอิงแบบวงกลมหากคุณลงท้ายด้วย (ตัวอย่าง) วัตถุ 'เด็ก' ที่มีการอ้างอิงกลับไปที่ 'ผู้ปกครอง' สิ่งนี้สามารถหยุดการรวบรวม GC ของผู้ปกครองหากคุณละทิ้ง การอ้างอิงและพึ่งพา GC - บวกหากคุณใช้งานเครื่องมือสร้างขั้นสุดท้ายมันจะไม่ถูกเรียกใช้

วิธีเดียวในการปัดเศษนี้คือการแบ่งการอ้างอิงแบบวงกลมด้วยตนเองโดยการตั้งค่าการอ้างอิงหลักเป็นโมฆะบนเด็ก ๆ

การใช้ IDisposable กับผู้ปกครองและเด็ก ๆ เป็นวิธีที่ดีที่สุดในการทำเช่นนี้ เมื่อ Dispose ถูกเรียกบน Parent, Call Dispose บน Children ทั้งหมดและในเมธอด Dispose ให้ตั้งค่า parent parent เป็น null


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

1

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

https://www.codeproject.com/Articles/29534/IDisposable-What-Your-Mother-Never-Told-You-About

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

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


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