Objects in C # are classified into value and reference types. The biggest difference between them is the data storage method and storage location. in Windows, the virtual addressing system is used to manage the data storage generated when the program runs. to put it simply, the system manages a memory area and allocates a part of it to store value type variables, called stacks. Stacks adopt the principle of advanced and later release, store value type variables from the highest address bit of the region to the lowest address, first and then, the later-in, first-out management ensures that value-type variables can clear occupied memory even when they are out of scope. Because of the fast stack speed, the stored data is generally not very large, this part generally does not require special user operations. the value type is stored in the stack summary. The stack has very high performance, but it is not flexible for all variables. We usually want to use a method to allocate memory to store some data, and the data can still be used for a long period of time after the method exits. As long as the new operator is used to request a bucket, this possibility exists-for example, all reference types. In this case, use the managed heap. It works under the control of the garbage collector. The managed heap (or heap for short) is another memory area in the large memory area managed by the system. To learn how the heap works and how to allocate memory for the referenced data type, see the following code:
Customer arabel = new customer ();
This line of code completes the following operations: first, allocate the memory on the stack to store the customer instance (a real instance, not just an address ). Set the arabel value of the variable to the memory address allocated to the new customer object (it also calls the fields in the initialization class instance of the appropriate customer () constructor, but we don't have to worry about this part ).
The customer instance is not placed in the stack, but in the memory heap. If we do this:
Customer newaddress = arabel;
At this time, newaddress will also be saved in the stack, and its value is the same as that of arabel, which is the heap address for storing the customer instance.
Once we know this, we will find such a problem. If the arabel and newaddress variables in the stack expire and destroy them, what will happen to the customer object stored in the stack? In fact, it remains in the heap until the program stops or the Garbage Collector deletes it. if the C # garbage collector does not display a call, it regularly runs and checks the memory to delete data without any variable reference. it looks good, but think about it. The garbage collector does not always check and runs on a regular basis. During this period, if a large amount of expired data is generated, it will reside in the memory ..... then, we can call system. GC. collect (), forces the Garbage Collector to run somewhere in the code, system. GC is a garbage collector. net base class. The collect () method calls the garbage collector. However, this method is rarely used. (is it possible to let garbage collection check for memory when an object is destroyed ?) For example, if a large number of objects in the code just stop referencing, it is suitable to call the garbage collector. Besides, the logic of the garbage collector cannot ensure that all expired data is deleted from the heap during a garbage collection process, it is powerless for unmanaged objects (such as file handles, network connections, and database connections) that are not managed by the garbage collector. What should we do?
In this case, you need to develop special rules to ensure that unhosted resources are released when an instance of the recycle class is released.
When defining a category, you can use two mechanisms to automatically release unmanaged resources. These mechanisms are often implemented together because each mechanism provides a slightly different solution to the problem. The two mechanisms are:
● Declare an destructor as a member of the class
● Implement the system. idisposable interface in the class
Next we will discuss these two mechanisms in sequence, and then introduce how to implement them at the same time to achieve the best results.
Destructor
The previous section describes some operations that a constructor can perform when creating a class instance. When the Garbage Collector deletes an object, it can also call the destructor. Because of this operation, the Destructor seems to be the best place to place the code for releasing unmanaged resources and executing general cleanup operations. However, it is not that simple. The running rules of the spam review determine that the code to run at a certain time cannot be placed in the destructor. If the object occupies valuable and important resources, release these resources as quickly as possible, so you cannot wait for the garbage collector to release them.
Idisposable Interface
We recommend that you use the system. idisposable interface to replace the destructor. The idisposable interface defines a mode (with language-level support) and provides a definite mechanism for releasing unmanaged resources, and avoid the inherent problems related to the spam function. The idisposable interface declares a method dispose (), which does not contain parameters. The Execution Code of void and myclass method dispose () is returned as follows:
class Myclass : IDisposable{ public void Dispose() { // implementation }}
The Execution Code of dispose () explicitly releases all unmanaged resources directly used by the object, and CALLS dispose () on all encapsulated objects that implement the idisposable interface (). In this way, the dispose () method provides precise control when releasing unmanaged resources.
Suppose there is a class resourcegobbler, which uses some external resources and executes the idisposable interface. If you want to instantiate an instance of this class, use it, and then release it, you can use the following code:
Resourcegobbler theinstance = new resourcegobbler (); // The usage process of theinstance object theinstance. Dispose ();
If an exception occurs during the processing, the Code does not release the resources used by theinstance. Therefore, use the try block to write the following code:
Resourcegobbler theinstance = NULL; try {theinstance = new resourcegobbler (); // The usage process of theinstance object} finally {If (theinstance! = NULL) theinstance. Dispose ();}
Even if an exception occurs during processing, this version ensures that dispose () is always called on theinstance and resources used by theinstance are always released. However, if you always need to repeat such a structure, the code will be easily obfuscated. C # provides a syntax to ensure that dispose () (but not close () is automatically called on the object when the reference is out of scope ()). This syntax uses the using keyword to complete this job-but currently, it has nothing to do with namespaces in completely different environments. The following code generates the Il code corresponding to the try block:
Using (resourcegobbler theinstance = new resourcegobbler () {// The usage process of theinstance object}
The Using statement is followed by a pair of parentheses, which are the Declaration and instantiation of referenced variables. This statement places the variables in the included compound statement. In addition, the dispose () method is automatically called even if an exception occurs when the variable exceeds the scope. If you have already used the try block to capture other exceptions, it will be clearer. To avoid using the using statement, you can only call dispose () in the finally clause of the existing try block (), you can also avoid extra indentation.
Note:
For some classes, using close () is more logical than dispose (), for example, when processing file or database connections. In these cases, the idisposable interface is often implemented, and an independent close () method is executed to call dispose (). This method is clear in class usage and supports the using statement provided by C.
The previous chapter discussed the two methods used by the class to release unmanaged resources:
● Execute the Destructor forcibly executed by the running database, but the execution of the Destructor is uncertain. In addition, due to the way the garbage collector works, it adds unacceptable system overhead to the Runtime Library.
● The idisposable interface provides a mechanism to allow class users to control the time for resource release, but ensure that dispose () is executed ().
In general, the best way is to execute these two mechanisms to obtain the advantages of these two mechanisms and overcome their shortcomings. Assume that most programmers can call dispose () correctly to implement the idisposable interface, and use the Destructor as a secure mechanism to prevent dispose () from being called (). The following is an example of dual implementation:
Public class resourceholder: idisposable {private bool isdispose = false; // display the called dispose method public void dispose () {dispose (true); GC. suppressfinalize (this);} // The actual cleanup method protected virtual void dispose (bool disposing) {If (! Isdisposed) {If (disposing) {// Delete the managed object here .} // here, clear the unmanaged object} isdisposed = true;} // destructor ~ Resourceholder () {dispose (false );}}
As you can see, dispose () has the second protected overload method, which includes a bool parameter, which is the method to actually complete the cleaning. Dispose (bool) is called by the Destructor and idisposable. Dispose. This method focuses on ensuring that all the cleanup code is put in one place.
The parameter passed to dispose (bool) indicates whether dispose (bool) is called by the Destructor or by idisposable. dispose () call -- dispose (bool) should not be called from other aspects of the Code, the reason is:
● If the customer calls idisposable. Dispose (), the customer specifies that all resources related to the object should be cleared, including managed and unmanaged resources.
● If the Destructor is called, in principle, all resources still need to be cleared. However, in this case, the Destructor must be called by the garbage collector, and other hosted objects should not be accessed, because we can no longer determine their statuses. In this case, it is best to clear known unmanaged resources, and you want to reference the hosted object and the destructor to execute your own cleaning process.
The isdispose member variable indicates whether the object has been deleted and allows you to delete the member variable multiple times. This simple method is not thread-safe and requires the caller to ensure that there is only one thread to call the method at the same time. It is a reasonable assumption that the customer needs to perform synchronization. This assumption is repeatedly used in the entire. Net class library (for example, in the collection class ). Finally, idisposable. Dispose () contains a call to the system. gc. suppressfinalize () method. The suppressfinalize () method tells the Garbage Collector that a class no longer needs to call its destructor. Because dispose () has completed all the necessary cleaning work, The Destructor does not need to do any work. Calling suppressfinalize () means that the Garbage Collector considers this object to have no destructor at all.
Correct understanding of the above content can greatly optimize the system performance and release unnecessary data in a timely manner. instead of relying solely on the automatic recovery mechanism provided by C #, programmers need to use more flexible methods! The combination of the two can not only make the program run fast, but also make the system more stable!