[c#] الاستخدام السليم للواجهة IDisposable



8 Answers

غالبًا ما يتم استخدام ميزة IDisposable لاستغلال عبارة الاستخدام والاستفادة من طريقة سهلة للقيام بعملية تنظيف حتمية للكائنات المدارة.

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.");
        }
    }
}
Question

وأنا أعلم من قراءة وثائق MSDN أن الاستخدام "الأساسي" للواجهة 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 في الكود الوارد أعلاه يحتوي على مليون سلسلة ، وأنك تريد تحرير هذه الذاكرة الآن ، بدلاً من انتظار جامع البيانات المهملة. هل سينجز القانون المذكور أعلاه ذلك؟




Apart from its primary use as a way to control the lifetime of system resources (completely covered by the awesome answer of Ian , kudos!), the IDisposable/using combo can also be used to scope the state change of (critical) global resources : the console , the threads , the process , any global object like an application instance .

I've written an article about this pattern: http://pragmateek.com/c-scope-your-global-state-changes-with-idisposable-and-the-using-statement/

It illustrates how you can protect some often used global state in a reusable and readable manner: console colors , current thread culture , Excel application object properties ...




In the example you posted, it still doesn't "free the memory now". All memory is garbage collected, but it may allow the memory to be collected in an earlier generation . You'd have to run some tests to be sure.

The Framework Design Guidelines are guidelines, and not rules. They tell you what the interface is primarily for, when to use it, how to use it, and when not to use it.

I once read code that was a simple RollBack() on failure utilizing IDisposable. The MiniTx class below would check a flag on Dispose() and if the Commit call never happened it would then call Rollback on itself. It added a layer of indirection making the calling code a lot easier to understand and maintain. The result looked something like:

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

    tx.Commit();
} 

I've also seen timing / logging code do the same thing. In this case the Dispose() method stopped the timer and logged that the block had exited.

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

So here are a couple of concrete examples that don't do any unmanaged resource cleanup, but do successfully used IDisposable to create cleaner code.




يجب ألا يكون هناك أي مكالمات أخرى لأساليب الكائن بعد استدعاء Dispose (على الرغم من أن الكائن يجب أن يتحمل المزيد من الاستدعاءات للتخلص). لذلك المثال في السؤال سخيف. إذا تم استدعاء Dispose ، فيمكن تجاهل الكائن نفسه. لذلك يجب على المستخدم فقط تجاهل جميع الإشارات إلى هذا الكائن بالكامل (تعيينهم إلى فارغة) وسيتم تلقائيا تنظيف جميع الكائنات ذات الصلة داخله.

أما بالنسبة للسؤال العام حول الإدارة / غير المدارة والمناقشة في إجابات أخرى ، أعتقد أن أي إجابة على هذا السؤال يجب أن تبدأ بتعريف لمورد غير مدار.

ما يتلخص في أن هناك وظيفة يمكنك الاتصال بها لوضع النظام في حالة ، وهناك وظيفة أخرى يمكنك الاتصال بها لإعادتها من تلك الحالة. الآن ، في المثال النموذجي ، قد تكون الدالة الأولى دالة تقوم بإرجاع مؤشر ملف ، وقد يكون الثاني هو استدعاء CloseHandle .

ولكن - وهذا هو المفتاح - يمكن أن يكون أي زوج من الوظائف المتطابقة. واحد يبني دولة ، والدموع الأخرى عليها. إذا تم بناء الولاية ولكن لم يتم هدمها بعد ، فيكون هناك مثيل من المصدر. عليك أن تتعهد بأن يحدث teardown في الوقت المناسب - لا يتم إدارة المورد بواسطة CLR. نوع المورد المدارة تلقائيًا هو الذاكرة فقط. هناك نوعان: GC ، والمكدس. يتم إدارة أنواع القيم من خلال المكدس (أو عن طريق الركوب داخل أنواع المراجع) ، ويتم إدارة أنواع المرجع بواسطة GC.

قد تؤدي هذه الوظائف إلى تغييرات في الحالة يمكن تشذيرها بحرية ، أو قد تحتاج إلى تداخل تام. قد تكون التغييرات التي تطرأ على الحالة خياريًا ، أو قد لا تكون كذلك.

انظر إلى المثال في سؤال القاضي. يجب أن تكون التغييرات في المسافة البادئة لملف السجل متداخلة تمامًا ، وإلا فسيحدث خطأ. أيضا هم من غير المرجح أن تكون threadsafe.

من الممكن الوصول إلى أداة تجميع باستخدام أداة تجميع البيانات المهملة للحصول على الموارد غير المُدارة. ولكن فقط إذا كانت وظائف تغيير الحالة هي خيوط آمنة ، ويمكن أن يكون للحالتين عمر يتداخلان بأي طريقة. لذلك يجب أن يكون مثال "العدالة" لمورد ليس له صفة النهائية! فقط لن يساعد أحدا.

لهذه الأنواع من الموارد ، يمكنك فقط تطبيق IDisposable ، دون finalizer. النهائي هو اختياري تماما - يجب أن يكون. تم تغطيتها أو عدم ذكرها في العديد من الكتب.

يجب عليك عندئذ استخدام العبارة المستخدمة للحصول على أي فرصة لضمان استدعاء Dispose . هذا هو أساسا مثل مربوط ركوب مع المكدس (كما هو Finalizer إلى GC ، using هو إلى المكدس).

الجزء المفقود هو أن تضطر إلى الكتابة يدوياً والتخلص منها في الحقول الخاصة بك والفئة الأساسية الخاصة بك. لا يتعين على مبرمجي C ++ / CLI القيام بذلك. يكتب المترجم لهم في معظم الحالات.

هناك بديل ، أفضل من الدول التي تتعايش بشكل تام وليست سلسة (عدا عن أي شيء آخر ، تتجنب أي مشكلة يمكن أن تتسبب في وجود مشكلة مع وجود شخص لا يمكنه مقاومة إضافة أداة نهائية إلى كل فصل يقوم بتطبيق مفهوم قابل للتعريف) .

بدلاً من كتابة فصل دراسي ، تكتب دالة. تقبل الدالة مفوضاً للاتصال مرة أخرى بـ:

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 ثم بعد ذلك تقتل المورد B. لا يمكنك فعل ذلك إذا كنت قد أجبرت المستخدم على التعشيش تمامًا هكذا. ولكن بعد ذلك تحتاج إلى استخدام IDisposable (ولكن لا يزال بدون finalizer ، إلا إذا قمت بتطبيق threadafety ، وهو ليس مجانيًا).




إذا كانت MyCollection عبارة عن جمع البيانات المهملة على أية حال ، فلن تحتاج إلى التخلص منها. سيؤدي القيام بذلك إلى زيادة وحدة المعالجة المركزية (CPU) أكثر من اللازم ، وقد يؤدي إلى إبطال بعض التحليلات المحسوبة مسبقًا التي أجراها مجمع تجميع البيانات المهملة بالفعل.

يمكنني استخدام IDisposable لفعل أشياء مثل يتم التأكد من مؤشرات الترابط بشكل صحيح ، جنبا إلى جنب مع الموارد غير المدارة.

EDIT ردًا على تعليق Scott:

المرة الوحيدة التي تتأثر فيها مقاييس أداء GC عندما يتم إجراء استدعاء [sic] GC.Collect () "

من الناحية المفاهيمية ، يحتفظ GC بطريقة عرض الرسم البياني المرجعي للكائن ، وكل المراجع إليه من إطارات تراص الخيوط. يمكن أن تكون هذه الكومة كبيرة جدًا وتمتد على العديد من صفحات الذاكرة. كتحسين ، يخزن GC تحليله للصفحات التي من غير المرجح أن تتغير في كثير من الأحيان لتجنب إعادة مسح الصفحة دون داع. يتلقى GC إعلامًا من kernel عندما تتغير البيانات في صفحة ، لذلك فهو يعلم أن الصفحة غير نظيفة وتتطلب إعادة تفحص. إذا كانت المجموعة في Gen0 فمن المرجح أن أشياء أخرى في الصفحة تتغير أيضًا ، ولكن هذا أقل احتمالًا في Gen1 و Gen2. روايات متسلسلة ، لم تكن هذه الخطافات متوفرة في نظام Mac OS X للفريق الذي قام بتحويل GC إلى Mac من أجل الحصول على المكون الإضافي Silverlight على هذا النظام الأساسي.

نقطة أخرى ضد التخلص غير الضروري من الموارد: تخيل موقف يتم فيه تفريغ العملية. تخيل أيضا أن العملية قد تم تشغيلها لبعض الوقت. من المحتمل أن العديد من صفحات الذاكرة الخاصة بالعملية قد تم تبديلها إلى القرص. على أقل تقدير أنها لم تعد في ذاكرة التخزين المؤقت L1 أو L2. في مثل هذه الحالة ، ليس هناك نقطة لتطبيق يقوم بتنزيل كل هذه البيانات وصفحات الشفرة مرة أخرى في الذاكرة إلى موارد "الإفراج" التي سيصدرها نظام التشغيل على أي حال عند إنهاء العملية. وهذا ينطبق على الموارد المدارة وحتى غير المدارة. يجب التخلص فقط من الموارد التي تحافظ على سلاسل العمليات غير الخلفية ، وإلا ستبقى العملية على قيد الحياة.

الآن ، أثناء التنفيذ العادي توجد موارد سريعة الزوال التي يجب تنظيفها بشكل صحيح (مثل @ fezmonkey تشير إلى اتصالات قاعدة البيانات ، مقابس ، مقابض النوافذ ) لتجنب تسرب الذاكرة غير المُدار. هذه هي أنواع الأشياء التي يجب التخلص منها. If you create some class that owns a thread (and by owns I mean that it created it and therefore is responsible for ensuring it stops, at least by my coding style), then that class most likely must implement IDisposable and tear down the thread during Dispose .

The .NET framework uses the IDisposable interface as a signal, even warning, to developers that the this class must be disposed. I can't think of any types in the framework that implement IDisposable (excluding explicit interface implementations) where disposal is optional.




عينة التعليمات البرمجية المقدمة ليست مثالًا جيدًا IDisposableللاستخدام. يجب أن لا تخرج عملية مسح القاموس عادةً إلى Disposeالطريقة. سيتم مسح عناصر القاموس والتخلص منها عندما يخرج عن نطاقها. 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.
    }
}






One problem with most discussions of "unmanaged resources" is that they don't really define the term, but seem to imply that it has something to do with unmanaged code. While it is true that many types of unmanaged resources do interface with unmanaged code, thinking of unmanaged resources in such terms isn't helpful.

Instead, one should recognize what all managed resources have in common: they all entail an object asking some outside 'thing' to do something on its behalf, to the detriment of some other 'things', and the other entity agreeing to do so until further notice. If the object were to be abandoned and vanish without a trace, nothing would ever tell that outside 'thing' that it no longer needed to alter its behavior on behalf of the object that no longer existed; consequently, the 'thing's usefulness would be permanently diminished.

An unmanaged resource, then, represents an agreement by some outside 'thing' to alter its behavior on behalf of an object, which would useless impair the usefulness of that outside 'thing' if the object were abandoned and ceased to exist. A managed resource is an object which is the beneficiary of such an agreement, but which has signed up to receive notification if it is abandoned, and which will use such notification to put its affairs in order before it is destroyed.




First of definition. For me unmanaged resource means some class, which implements IDisposable interface or something created with usage of calls to dll. GC doesn't know how to deal with such objects. If class has for example only value types, then I don't consider this class as class with unmanaged resources. For my code I follow next practices:

  1. If created by me class uses some unmanaged resources then it means that I should also implement IDisposable interface in order to clean memory.
  2. Clean objects as soon as I finished usage of it.
  3. In my dispose method I iterate over all IDisposable members of class and call Dispose.
  4. In my Dispose method call GC.SuppressFinalize(this) in order to notify garbage collector that my object was already cleaned up. I do it because calling of GC is expensive operation.
  5. As additional precaution I try to make possible calling of Dispose() multiple times.
  6. Sometime I add private member _disposed and check in method calls did object was cleaned up. And if it was cleaned up then generate ObjectDisposedException
    Following template demonstrates what I described in words as sample of code:

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



Related