การใช้ IDisposable อย่างถูกต้อง


145

ในชั้นเรียนของฉันฉันใช้ IDisposable ดังนี้

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int UserID)
    {
        id = UserID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

    public void Dispose()
    {
        // Clear all property values that maybe have been set
        // when the class was instantiated
        id = 0;
        name = String.Empty;
        pass = String.Empty;
    }
}

ใน VS2012 การวิเคราะห์รหัสของฉันบอกว่าจะใช้ IDisposable อย่างถูกต้อง แต่ฉันไม่แน่ใจว่าสิ่งที่ฉันทำผิดที่นี่
ข้อความที่แน่นอนมีดังนี้:

CA1063 Implement IDisposable อย่างถูกต้องจัดให้มีการใช้งาน Disrid (bool) overridable ใน 'ผู้ใช้' หรือทำเครื่องหมายประเภทเป็นปิดผนึก การเรียกใช้การกำจัด (false) ควรล้างทรัพยากรดั้งเดิมเท่านั้น การเรียกร้องให้จัดการ (จริง) ควรล้างทรัพยากรที่มีการจัดการและดั้งเดิม stman User.cs 10

สำหรับการอ้างอิง: CA1063: ใช้ IDisposable อย่างถูกต้อง

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

หากทุกคนสามารถอธิบายได้ในแง่ของปัญหาที่เกิดขึ้นและ / หรือวิธีการที่ IDisposable ควรนำมาใช้นั่นจะช่วยได้จริงๆ!


1
นั่นคือรหัสทั้งหมดที่อยู่ภายในDispose?
Claudio Redi

42
คุณควรใช้เมธอด Dispose () เพื่อเรียกเมธอด Dispose () กับสมาชิกในคลาสของคุณ ไม่มีสมาชิกเหล่านั้นมีหนึ่ง คุณควรดังนั้นจึงไม่ใช้ IDisposable การรีเซ็ตค่าคุณสมบัติไม่มีจุดหมาย
Hans Passant

13
คุณจะต้องดำเนินการIDispoableหากคุณมีทรัพยากรที่ไม่มีการจัดการในการกำจัด (ซึ่งรวมถึงทรัพยากรที่ไม่มีการจัดการที่ถูกห่อ ( SqlConnection, FileStreamฯลฯ )) คุณไม่ควรและไม่ควรนำไปใช้IDisposableหากคุณมีทรัพยากรที่มีการจัดการเช่นที่นี่นี่คือ IMO ปัญหาสำคัญกับการวิเคราะห์รหัสเป็นเรื่องที่ดีมากในการตรวจสอบกฎเล็ก ๆ น้อย ๆ ที่โง่ แต่ไม่เก่งในการตรวจสอบข้อผิดพลาดทางแนวคิด
jason

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

2
ดังนั้นอย่าลงคะแนนโหวตอย่าโพสต์ที่ศูนย์และปิดคำถามด้วยตัวชี้ที่มีประโยชน์
tjmoore

คำตอบ:


113

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

  1. คุณมีทรัพยากรที่ไม่มีการจัดการ
  2. คุณกำลังอ้างอิงถึงสิ่งต่าง ๆ ที่ทิ้งตัวเอง

ไม่มีอะไรในรหัสที่คุณโพสต์จะต้องกำจัด

public class User : IDisposable
{
    public int id { get; protected set; }
    public string name { get; protected set; }
    public string pass { get; protected set; }

    public User(int userID)
    {
        id = userID;
    }
    public User(string Username, string Password)
    {
        name = Username;
        pass = Password;
    }

    // Other functions go here...

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

    protected virtual void Dispose(bool disposing)
    {
        if (disposing) 
        {
            // free managed resources
        }
        // free native resources if there are any.
    }
}

2
ฉันบอกเมื่อฉันเริ่มเขียนใน C # ที่ดีที่สุดเพื่อใช้ประโยชน์using(){ }เมื่อใดก็ตามที่เป็นไปได้ แต่การทำเช่นนั้นคุณต้องใช้ IDisposable ดังนั้นโดยทั่วไปฉันต้องการเข้าถึงชั้นเรียนผ่านการใช้งานโดยเฉพาะ ถ้าฉันต้องการเพียงชั้นเรียนในหนึ่งหรือสองฟังก์ชั่น
Ortund

62
@Ortund คุณเข้าใจผิด มันเป็นเรื่องที่ดีที่สุดที่จะใช้usingบล็อกเมื่อการดำเนินการระดับ IDisposable หากคุณไม่ต้องการชั้นเรียนให้ทิ้งอย่าใช้มัน มันไม่มีจุดประสงค์
Daniel Mann

5
@DanielMann ความหมายของusingบล็อกมีแนวโน้มที่จะดึงดูดความสนใจนอกเหนือจากIDisposableอินเตอร์เฟสเพียงอย่างเดียว ฉันคิดว่ามีการละเมิดมากกว่าสองสามครั้งIDisposableเพื่อวัตถุประสงค์ในการกำหนดขอบเขต
โทมัส

1
เช่นเดียวกับบันทึกย่อด้านข้างหากคุณต้องการให้ทรัพยากรที่ไม่มีการจัดการใด ๆ ได้รับการปลดปล่อยคุณควรรวมการเรียกใช้ Finalizer Call (false) ซึ่งจะอนุญาตให้ GC โทรไปยัง Finalizer เมื่อทำการรวบรวมขยะ (ในกรณีที่ยังไม่ถูกเรียก ทรัพยากร
mariozski

4
หากไม่มีตัวเลือกขั้นสุดท้ายในการเรียกใช้งานของคุณGC.SuppressFinalize(this);จะไม่มีจุดหมาย ในฐานะที่เป็น @mariozski ชี้ให้เห็นว่าผู้เข้ารอบสุดท้ายจะช่วยให้มั่นใจได้ว่าDisposeจะถูกเรียกเลยถ้าไม่มีการใช้คลาสในusingบล็อก
Haymo Kutschbach

57

ครั้งแรกของทั้งหมดที่คุณไม่จำเป็นต้อง "ทำความสะอาด" stringและints - พวกเขาจะได้รับการดูแลโดยอัตโนมัติโดยการเก็บขยะ สิ่งเดียวที่จะต้องทำความสะอาดในDisposeเป็นทรัพยากรที่ไม่มีการจัดการหรือ recources IDisposableการจัดการที่ใช้

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

public void Dispose()
{
    Dispose(true);

    // Use SupressFinalize in case a subclass 
    // of this type implements a finalizer.
    GC.SuppressFinalize(this);   
}
protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing) 
        {
            // Clear all property values that maybe have been set
            // when the class was instantiated
            id = 0;
            name = String.Empty;
            pass = String.Empty;
        }

        // Indicate that the instance has been disposed.
        _disposed = true;   
    }
}

3
+1 มีธงเพื่อให้แน่ใจว่ารหัส Cleanup จะถูกดำเนินการเพียงครั้งเดียวเป็นวิธีวิธีที่ดีกว่าการตั้งค่าคุณสมบัติโมฆะหรืออะไรก็ตาม (โดยเฉพาะอย่างยิ่งตั้งแต่รบกวนที่มีreadonlyความหมาย)
โทมัส

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

42

ตัวอย่างต่อไปนี้แสดงแนวปฏิบัติทั่วไปที่ดีที่สุดในการใช้IDisposableส่วนต่อประสาน การอ้างอิง

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

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.
    }
}

14

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

ทรัพยากรทั้งหมดที่คุณ "ล้างข้อมูล" เป็นทรัพยากรที่มีการจัดการและDisposeวิธีการของคุณไม่ประสบความสำเร็จ ชั้นเรียนของคุณไม่ควรติดตั้งIDisposableเลย The Garbage Collector จะดูแลทุกฟิลด์เหล่านั้นให้ดีเอง


1
เห็นด้วยกับเรื่องนี้ - มีความคิดในการกำจัดทุกอย่างเมื่อไม่จำเป็น การกำจัดควรใช้เฉพาะเมื่อคุณมีทรัพยากรที่ไม่มีการจัดการในการทำความสะอาด !!
Chandramouleswaran Ravichandra

4
ไม่เป็นความจริงอย่างเด็ดขาดวิธีการกำจัดยังช่วยให้คุณสามารถกำจัด "ทรัพยากรที่มีการจัดการที่ใช้งาน IDisposable" ได้ด้วย
Matt Wilko

@MattWilko ทำให้เป็นวิธีการทิ้งทรัพยากรที่ไม่มีการจัดการทางอ้อมเพราะจะทำให้ทรัพยากรอื่นสามารถกำจัดทรัพยากรที่ไม่มีการจัดการได้ ที่นี่ไม่มีแม้แต่การอ้างอิงทางอ้อมไปยังทรัพยากรที่ไม่มีการจัดการผ่านทรัพยากรที่มีการจัดการอื่น
Servy

@MattWilko การกำจัดจะถูกเรียกโดยอัตโนมัติในทรัพยากรที่มีการจัดการใด ๆ ที่ใช้งาน IDesposable
sharma panky

@pankysharma ไม่มันจะไม่ มันจะต้องถูกเรียกด้วยตนเอง นั่นคือจุดรวมของมัน คุณไม่สามารถสรุปได้ว่ามันจะถูกเรียกโดยอัตโนมัติคุณรู้ว่ามีคนควรจะเรียกมันด้วยตนเอง แต่คนอื่นทำผิดพลาดและลืม
Servy

14

คุณต้องใช้รูปแบบที่ใช้แล้วทิ้งแบบนี้:

private bool _disposed = false;

protected virtual void Dispose(bool disposing)
{
    if (!_disposed)
    {
        if (disposing)
        {
            // Dispose any managed objects
            // ...
        }

        // Now disposed of any unmanaged objects
        // ...

        _disposed = true;
    }
}

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

// Destructor
~YourClassName()
{
    Dispose(false);
}

1
มันจะฉลาดกว่าไหมถ้าจะเรียกให้ GC.SuppressFinalize (นี่) ใน destructor ด้วยเหรอ? มิฉะนั้นวัตถุนั้นจะถูกเรียกคืนใน GC ถัดไป
Sudhanshu Mishra

2
@dotnetguy: ตัวทำลายวัตถุถูกเรียกเมื่อ gc ทำงาน ดังนั้นการโทรสองครั้งจึงเป็นไปไม่ได้ ดูที่นี่: msdn.microsoft.com/en-us/library/ms244737.aspx
schoetbi

1
ตอนนี้เรากำลังเรียกโค้ด "ต้นแบบ" ชิ้นส่วนสำเร็จรูปใด ๆ
เชล

4
@rdhs ไม่เราไม่ได้ MSDN ระบุว่าเป็นรูปแบบ "รูปแบบการทิ้ง" ที่นี่ - msdn.microsoft.com/en-us/library/b1yfkh5e(v=vs.110).aspxดังนั้นก่อนลงคะแนนบางที Google อาจจะเล็กน้อย?
Belogix

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

10

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

public class User: IDisposable {
  ...
  protected virtual void Dispose(Boolean disposing) {
    if (disposing) {
      // There's no need to set zero empty values to fields 
      // id = 0;
      // name = String.Empty;
      // pass = String.Empty;

      //TODO: free your true resources here (usually IDisposable fields)
    }
  }

  public void Dispose() {
    Dispose(true);

    GC.SuppressFinalize(this);
  } 
}

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

3

Idisposable จะดำเนินการเมื่อใดก็ตามที่คุณต้องการคอลเลกชันขยะ (ยืนยัน)

class Users : IDisposable
    {
        ~Users()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
            // This method will remove current object from garbage collector's queue 
            // and stop calling finilize method twice 
        }    

        public void Dispose(bool disposer)
        {
            if (disposer)
            {
                // dispose the managed objects
            }
            // dispose the unmanaged objects
        }
    }

เมื่อสร้างและใช้คลาสผู้ใช้ให้ใช้บล็อก "using" เพื่อหลีกเลี่ยงการเรียกวิธีการกำจัดอย่างชัดเจน:

using (Users _user = new Users())
            {
                // do user related work
            }

ในตอนท้ายของการใช้บล็อกที่สร้างขึ้นวัตถุผู้ใช้จะถูกกำจัดโดยการร้องขอโดยนัยของวิธีการกำจัด


2

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

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

สิ่งอื่น ๆ ที่น่าจะมีประโยชน์คือการยับยั้งการเตือนการวิเคราะห์โค้ด ... https://docs.microsoft.com/en-us/visualstudio/code-quality/in-source-suppression-overview?view=vs- 2017

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