In-depth understanding of IDisposable interfaces in C #

Source: Internet
Author: User

Write in front

Before we get started, we need to be clear about what is a C # (or. NET) resource, and we often say to release resources when coding, so what is a resource, in short, every type in C # is a resource, and the resource is divided into managed and unmanaged resources, then what is this?!

Managed resources: The resources allocated and freed by the CLR, that is, the object that we are directly new to;

Unmanaged resources: Resources that are not under the control of the CLR, that is, not. NET itself, often by invoking cross-platform assemblies (such as C + +) or some of the interfaces provided by the operating system, such as Windows kernel objects, file operations, database connections, sockets, WIN32API, networks, and so on.

We discuss the following, primarily the release of unmanaged resources, and managed resources. NET garbage collection has been done for us. In fact, there are some unmanaged resources. NET garbage collection also helps us to achieve, then if you want to let. NET garbage collection helps us to release unmanaged resources and how to implement them.

How to explicitly release resources in an appropriate way

Assuming we're going to use FileStream, we usually do this by using it, or more old-fashioned try...catch...finally ... This practice, because its implementation calls the unmanaged resources, so we have to use the end to explicitly release it, if not to release it, it may cause a memory leak.

This may sound like a simple one, but when we encode it, it can often ignore the issue of releasing resources. NET garbage collection and how to help us release unmanaged resources, let's explore, a standard release of unmanaged resources of the class should be to implement the IDisposable interface:

 Public class myclass:idisposable{     //<summary> performs an application-defined task associated with releasing or resetting an unmanaged resource.  </summary>public    void  Dispose ()    {    }}

We can use this class when we instantiate it:

using (varnew  MyClass ()) {}

It looks very simple, but if it is so simple, there is no need for this article. If we want to implement the IDisposable interface, we should actually do this:

    1. Implement the Dispose method;

    2. A protected dispose virtual method is extracted and the logic of releasing resources is implemented in the method.

    3. Add a destructor;

    4. Add a private bool Type field as a token to release the resource

Next, let's implement a Dispose pattern like this:

 Public classmyclass:idisposable{/// <summary>    ///simulating an unmanaged resource/// </summary>    PrivateIntPtr Nativeresource {Get;Set; } = Marshal.allochglobal ( -); /// <summary>    ///simulating a managed resource/// </summary>     PublicRandom Managedresource {Get;Set; } =NewRandom (); /// <summary>    ///Release Token/// </summary>    Private BOOLdisposed; /// <summary>    ///to prevent forgetting to explicitly call the Dispose method/// </summary>~MyClass () {//must be falseDispose (false); }    /// <summary>executes an application-defined task associated with releasing or resetting an unmanaged resource. </summary>     Public voidDispose () {//must be TrueDispose (true); //Notifies the garbage collector that the finalizer is no longer calledGc. SuppressFinalize ( This); }    /// <summary>    ///not required, just to be more compliant with other language specifications, such as C + +, Java/// </summary>     Public voidClose () {Dispose (); }    /// <summary>    ///non-sealed class overridable Dispose method, easy to override when subclass inherits/// </summary>    /// <param name= "disposing" ></param>    protected Virtual voidDispose (BOOLdisposing) {        if(disposed) {return; }        //clean up Managed resources        if(disposing) {if(Managedresource! =NULL) {Managedresource=NULL; }        }        //Cleanup of unmanaged Resources        if(Nativeresource! =IntPtr.Zero) {Marshal.freehglobal (Nativeresource); Nativeresource=IntPtr.Zero; }        //tell yourself that you've been releaseddisposed =true; }}

If it is not a virtual method, then it is very possible for the developer to ignore the parent class when inheriting the cleanup work, so, based on the inheritance system, we have to provide such a virtual method.

Second, the virtual method provided is a Boolean parameter, with the purpose of this parameter is to release resources when the managed resources and unmanaged resources are differentiated, while implementing self-IDisposable Dispose method call, the incoming is true, and when the finalizer is called, False is passed in, which means that both managed and unmanaged resources are processed while passing in true, whereas passing in false only requires processing unmanaged resources.

So why treat managed and unmanaged resources differently? Before we do this, we should understand: Does the managed resource need to be cleaned up manually? You might want to divide the C # types into two categories: one for IDisposable and the other for none. The former is defined as a non-ordinary type, and the latter is a normal type. Non-trivial types contain unmanaged resources, implement IDisposable, but also contain itself is managed resources, so not ordinary, for our question just now, the answer is: the normal type does not need manual cleanup, but not the normal type needs to be cleaned manually.

The idea behind our dispose pattern is that if you explicitly call Dispose, then the type is going to release all of its resources in a step-by-step manner, and if you forget to call Dispose, assume that all of your resources (even non-trivial types) are given to the GC, so no manual cleanup is required. So this is why it is true that the call to the virtual method in dispose of the self-IDisposable is passed false in the finalizer.

We also note that the virtual method first determines the disposed field, which is used to determine the release state of the object, which means that when you call Dispose multiple times, if the object has been cleaned up, the cleanup does not have to go on.

However, Dispose does not mean that the object is set to NULL, and that it has not been recovered completely. But in fact, the reference to the object may still exist, but it is no longer a normal state, so we understand that sometimes we call the database context and sometimes why the "database connection has been released" such as the exception.

Therefore, the existence of the disposed field is used to indicate whether the object has been disposed.

If the object contains a field or property of an unmanaged type, the type should be disposable

This sentence may seem a bit around, that is to say, if some of the object's fields or properties are IDisposable subclasses, such as FileStream, then this class should also implement IDisposable.

As we said before, C # types are classified as generic and non-generic, and non-trivial types contain normal self and unmanaged resources. Then, if the type of a field or property of a class is non-trivial, then this type should also be an unconventional type, and the IDisposable interface should be implemented as well.

To raise a chestnut, if a type, combined with FileStream, then it should implement the IDisposable interface, the code is as follows:

 Public classmyclass2:idisposable{~MyClass2 () {Dispose (false); }     PublicFileStream FileStream {Get;Set; } /// <summary>    ///Release Token/// </summary>    Private BOOLdisposed; /// <summary>executes an application-defined task associated with releasing or resetting an unmanaged resource. </summary>     Public voidDispose () {Dispose (true); Gc. SuppressFinalize ( This); }    /// <summary>    ///non-sealed class overridable Dispose method, easy to override when subclass inherits/// </summary>    /// <param name= "disposing" ></param>    protected Virtual voidDispose (BOOLdisposing) {        if(disposed) {return; }        //clean up Managed resources        if(disposing) {//Todo        }        //Cleanup of unmanaged Resources        if(FileStream! =NULL) {filestream.dispose (); FileStream=NULL; }        //tell yourself that you've been releaseddisposed =true; }}

Because the type contains a field of type FileStream, it contains a non-trivial type, and we still need to implement the IDisposable interface for that type.

Release resources in a timely manner

Maybe a lot of people will ask Ah, the GC has helped us to release the resources implicitly, why also to voluntarily release resources, we first look at an example:

Private void button6_click (object  sender, EventArgs e) {    varnew FileStream ( @" C:\1.txt " , Filemode.openorcreate,fileaccess.readwrite);} Private void Button7_click (object  sender, EventArgs e) {    GC. Collect ();}

The above code is in the WinForm program, click Button 6, open a file stream, click Button 7 to perform GC recycle all "generation" (hereafter will indicate the concept of generation) of garbage, if you click the two times button 6, will throw an exception:

This problem does not occur if you click Button 6 and then click Button 7, and then click Button 6.

Let's analyze: When I click Button 6 to open a file, the method has been executed, FS has not been referenced anywhere, so it is marked for garbage, then when to be recycled, or when the GC began to work? Microsoft's official explanation is that the GC will only work when one of the following conditions is met:

    1. The system has lower physical memory;

    2. The memory used by the allocated objects on the managed heap is outside the acceptable range;

    3. Call the Gc.collect method manually, but almost all of the cases, we do not have to call, because the garbage collector will automatically call it, but in the above example, in order to experience the damage caused by garbage collection, so manually call the Gc.collect, you can also carefully understand the operation of this method brings the difference.

GC also has a "generation" concept, altogether divided into 3 generations: 0 generations, 1 generations, 2 generations. And these three generations, the equivalent is three queue container, the No. 0 generation contains some short-lived objects, the above example of FS is a short-term object, when the method is executed, FS was thrown into the No. 0 generation of GC, but not garbage collection, only when the No. 0 generation full, the system thinks that this time satisfies the low memory condition, The garbage collection event is triggered. So we never know when FS is recycled, before recycling, it is actually useless, but always occupy the system resources (manger), this is a huge waste to the system, and this waste will also interfere with the entire system operation, such as our example, because it always occupies resources, We are no longer able to access the file.

Not releasing resources in a timely manner will also bring another problem, although we previously said that implementing the IDisposable interface class, the GC can automatically help us release, but this process has been extended, because it is not in a collection to complete all the cleanup work, even if the GC automatically help us release, It is also the finalizer that calls FileStream first, and is actually released at the next garbage collection.

Knowing the hazards, we are in the process of coding, if we know that it should be using up, must be using:

using (varnew FileStream (@ "C:\1.txt", FileMode.OpenOrCreate, FileAccess.ReadWrite)) {}
You do not need to place objects that are no longer in use to null

In the above, we all mentioned the release of resources, but did not say that it is not necessary to set the object no longer used to null, and this problem is a long-disputed issue, some people think that the object is NULL to allow the GC to find garbage earlier, and some people think that this is not an egg. In fact, this problem is initiated from the inside of the method, in order to better illustrate the problem, we first to the code to check:

Private voidButton6_click (Objectsender, EventArgs e) {    varMC1 =NewMyClass () {Name ="MC1" }; varMC2 =NewMyClass () {Name ="MC2" }; MC1=NULL;}Private voidButton7_click (Objectsender, EventArgs e) {GC. Collect ();} Public classmyclass{ Public stringName {Get;Set; } ~MyClass () {MessageBox.Show (Name+"was destroyed."); }}

Click Button 6, and then click Button 7, and we find:

A MC2 that is not NULL is released first, although it is set to null after MC1;

In CLR-managed applications, there is a concept of "root", where static fields, method parameters, and local variables can exist as "roots" (value types cannot be "roots" and only reference types can be "roots").

In the above code, MC1 and MC2 create a "root" in memory, respectively, during code execution. In the garbage collection process, the GC scans the "root" along the thread stack (the stack features advanced, that is, MC2 in the stack after MC1, but MC2 is out of the stack before MC1), and checks the collection of all the static fields of the reference type, when the "root" in the method is checked. If you find that there is no reference to this local variable, regardless of whether you have explicitly set null this means that the "root" has been stopped, and then the GC will find that the reference to the root is empty, it will be marked to be released, which also means that MC1 and MC2 memory space can be freed, So the above code mc1=null doesn't make any sense (and so does the method's parameter variables).

In fact. NET's JIT compiler is an optimized compiler, so if we set the local variable to NULL in our code, the statement will be ignored:

S=null;

If our project is under Release configuration, the above code will not be compiled to the DLL, it is because of our analysis above, so many people will think it is not necessary to assign the object to null, but in another case, it is absolutely necessary to assign the object to null, That is a static field or property, but this bin does not mean that assigning an object to null is the assignment of its static field to null:

Private voidButton6_click (Objectsender, EventArgs e) {    varMC =NewMyClass () {Name ="MC" };}Private voidButton7_click (Objectsender, EventArgs e) {GC. Collect ();} Public classmyclass{ Public stringName {Get;Set; }  Public StaticMyClass2 MyClass2 {Get;Set; } =NewMyClass2 (); ~MyClass () {//MyClass2 = null;MessageBox.Show (Name +"was destroyed."); }} Public classmyclass2{~MyClass2 () {MessageBox.Show ("MyClass2 was released ."); }}

The above code runs we will find that when the MC is recycled, its static properties are not collected by GC, and we will MyClass the Myclass2=null in the Terminator to cancel the comment, and then run, when we two times click on the button 7, the property MyClass2 is really released, Because the first GC only in the Terminator to the MyClass property is set to NULL, in the second GC when the garbage collection, the reason that the static variable is not released (even if the assignment is null is not optimized by the compiler), because the type of static field once created, as the "root" exists , basically not participating in GC, so the GC will never consider it a garbage, not a static field.

So in actual work, once we feel that the static variable occupies a large amount of memory space, and no longer use, it can be set to NULL, the most typical case is the implementation of the cache expiration policy, the static variable is set to null this may not be necessary, but this is definitely a good habit, Imagine a project, if a static variable as a global cache, if you do not have an expiration policy, once the project runs, then it occupies only the memory space increase, and eventually burst the machine memory, so there is a suggestion: try to use less static variables .

In-depth understanding of IDisposable interfaces in C #

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.