Objective C # Principle 15: Use Using and try/finally to clear Resources
Item 15: Utilize using and try/finally for resource cleanup
To use an unmanaged resource type, you must implement the idisposable interface's dispose () method to precisely release system resources .. This rule in the. NET environment causes the release of resources.Source codeIs a type of user, rather than a type or system. Therefore, whenever you use a type with the dispose () method, you have the responsibility to call the dispose () method to release resources. The best way to ensure that dispose () is to use the using statement or try/Finally block.
All types that contain unmanaged resources should implement the idisposable interface. In addition, when you forget to properly process these types, they will passively create destructor. If you forget to process these objects, those non-memory resources will be released later when the Destructor are called accurately. This makes these objects wait for a longer time in the memory, which will make your applicationProgramThe speed will decrease because the system resources are occupying too much resources.
Fortunately, the precise release of resources by C # Language designers is a common task. They added a keyword to make it easy.
Suppose you wrote the followingCode:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Two processable objects in this example are not properly released: sqlconnection and sqlcommand. Both objects are stored in the memory until the Destructor is called. (Both classes are inherited from system. componentmodel. component .)
The solution to this problem is to call their dispose after using the command and link:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
Mysqlcommand. Dispose ();
Myconnection. Dispose ();
}
This is good, unless the SQL command throws an exception during execution, your dispose () call will never succeed. The Using statement ensures that the dispose () method is called. When you allocate objects to the using statement, the C # compiler places these objects in a try/Finally block:
Public void executecommand (string connstring,
String commandstring)
{
Using (sqlconnection myconnection = new
Sqlconnection (connstring ))
{
Using (sqlcommand mysqlcommand = new
Sqlcommand (commandstring,
Myconnection ))
{
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
}
}
When you use a processing object in a function, the using statement is the easiest way to ensure that the object is properly processed. When these objects are allocated, they will be put into a try/Finally block by the compiler. The following two pieces of code are compiled into the same Il:
Sqlconnection myconnection = NULL;
// Example using clause:
Using (myconnection = new sqlconnection (connstring ))
{
Myconnection. open ();
}
// Example try/Catch Block:
Try {
Myconnection = new sqlconnection (connstring );
Myconnection. open ();
}
Finally {
Myconnection. Dispose ();
}
In my personal experience in using try/catch/finally blocks, I think the above practice is very inconvenient. Resources can be released, but errors cannot be found. For more information about how to throw exceptions and release resources at the same time, see other related resources, such as Jeffrey's. NET Framework Program Design and revision)
If you place a variable that cannot be processed in the using statement, the C # compiler gives an error, for example:
// Does not compile:
// String is sealed, and does not support idisposable.
Using (string MSG = "this is a message ")
Console. writeline (MSG );
Using can only be used when compiling, And the types that support the idispose interface can be used, not any objects:
// Does not compile.
// Object does not support idisposable.
Using (Object OBJ = factory. createresource ())
Console. writeline (obj. tostring ());
If OBJ implements the idispose interface, the using statement will generate the resource cleaning code. If it is not, using will degrade to using (null), which is safe, but has no effect. If you are not sure whether an object should be placed in the using statement, it is better to be safer: Suppose you want to do this and put it in the using statement in the previous method.
Here is a simple case: Whenever you use a processing object in a method, put this object in the using statement. Now you can learn more complex applications. In the preceding example, we need to release two objects: links and commands. The preceding example shows that two different using statements are created, one containing a processing object. Each using statement generates a different try/Finally block. You write the following code:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Sqlcommand mysqlcommand = NULL;
Try
{
Myconnection = new sqlconnection (connstring );
Try
{
Mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (mysqlcommand! = NULL)
Mysqlcommand. Dispose ();
}
}
Finally
{
If (myconnection! = NULL)
Myconnection. Dispose ();
}
}
Each using statement generates a new nested try/Finally block. I found that this is a very bad structure, so if you encounter multiple objects that implement the idisposable interface, I prefer to write my own try/Finally block:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Sqlcommand mysqlcommand = NULL;
Try {
Myconnection = new sqlconnection (connstring );
Mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (mysqlcommand! = NULL)
Mysqlcommand. Dispose ();
If (myconnection! = NULL)
Myconnection. Dispose ();
}
}
The author's judgment on whether an object is null is very important. In particular, some objects encapsulated with COM are implicitly released, an exception occurs when you release some empty objects. For example, if one COM is referenced by variables of two different interfaces and dispose is called on one of them, the other call will fail. In. net, you should also pay attention to this problem, so you must determine whether the object is null)
However, please do not try to use as to write such a using statement:
Public void executecommand (string connstring,
String commandstring)
{
// Bad idea. potential resource leak lurks!
Sqlconnection myconnection =
New sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Using (myconnection as idisposable)
Using (mysqlcommand as idisposable)
{
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
}
This seems refreshing, but there is a tricky bug. If the sqlcommand () constructor throws an exception, the sqlconnection object cannot be processed. You must ensure that each object implementing the idispose interface is allocated within the using range or within the try/Finally block. Otherwise, resource leakage may occur.
So far, you have learned the two most common cases. Whenever an object is processed in a method, using the using statement is the best way to ensure that the requested resources are released in various situations. When you assign multiple (implemented idisposable Interface) objects to a method, create multiple using blocks or use your own try/finally blocks.
There is a slight difference in understanding the objects that can be processed. Some objects support both disponse and close methods to release resources. Sqlconnection is one of them. You can close sqlconnection as follows:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Try {
Myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (myconnection! = NULL)
Myconnection. Close ();
}
}
This version closes the link, but it is indeed different from the processing object. The dispose method releases more resources and tells GC that this object no longer needs to be destructed, refer to other books ). Dispose calls GC. suppressfinalize (), but close () generally does not. The result is that the object is queued in the Destructor queue, even if the Destructor is not required. When you have a choice, dispose () is better than colse. You will learn more exciting content in Principle 18.
Dispose () does not remove objects from the memory. It is a hook for releasing an object from an unmanaged resource. This means that you may encounter such a problem: releasing an object that is still in use. Do not release an object that is still referenced elsewhere in the program.
In some cases, resource management in C # is more difficult than C ++. You cannot expect definite destructor to clear all the resources you use. However, the garbage collector makes it easier for you to implement the idisposable interface without having to implement the big data type. Of the more than 1500 classes in the. NET Framework, less than 100 classes implement the idisposable interface. When you use an object that implements the idisposeable interface, remember to process them in all classes. You should include them in using statements or try/finally blocks. No matter which one is used, make sure that the objects are correctly released every moment.
==================================
Item 15: Utilize using and try/finally for resource cleanup
types that use unmanaged system resources shocould be explicitly released using the dispose () method of the idisposable interface. the rules of. NET environment make that the responsibility of the Code that uses the type, not the responsibility of the type or the system. therefore, anytime you use types that have a dispose () method, it's your responsibility to release those resources by calling dispose (). the best way to ensure that dispose () always gets called is to utilize the using statement or a try/Finally block.
All types that own unmanaged resources implement the idisposable interface. in addition, they defensively create a finalizer for those times when you forget to dispose properly. if you forget to dispose of those items, those nonmemory resources are freed later, when finalizers get their chance to execute. all those objects then stay in memory that much longer, and your application becomes a slowly executing resource hog.
Luckily for you, the C # Language designers knew that explicitly releasing resources wocould be a common task. They added keywords to the language that make it easy.
Suppose you wrote this Code:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Two disposable objects are not properly cleaned up in this example: sqlconnection and sqlcommand. both of these objects remain in memory until their finalizers are called. (both of these classes inherit their finalizer from system. componentmodel. component .)
You fix this problem by calling dispose when you are finished with the command and the connection:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
Mysqlcommand. Dispose ();
Myconnection. Dispose ();
}
That's fine, unless any exceptions get thrown while the SQL command executes. in that case, your callto dispose () never happen. the Using statement ensures that dispose () is called. you allocate an object inside a using statement, and the C # compiler generates a try/Finally block around each object:
Public void executecommand (string connstring,
String commandstring)
{
Using (sqlconnection myconnection = new
Sqlconnection (connstring ))
{
Using (sqlcommand mysqlcommand = new
Sqlcommand (commandstring,
Myconnection ))
{
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
}
}
Whenever you use one disposable object in a function, the using clause is the simplest method to use to ensure that objects get disposed of properly. the Using statement generates a try/Finally block around the object being allocated. these two blocks generate exactly the same Il:
Sqlconnection myconnection = NULL;
// Example using clause:
Using (myconnection = new sqlconnection (connstring ))
{
Myconnection. open ();
}
// Example try/Catch Block:
Try {
Myconnection = new sqlconnection (connstring );
Myconnection. open ();
}
Finally {
Myconnection. Dispose ();
}
If you use the using statement with a variable of a type that does not support the idisposable interface, the C # compiler generates an error. For example:
// Does not compile:
// String is sealed, and does not support idisposable.
Using (string MSG = "this is a message ")
Console. writeline (MSG );
The Using statement works only if the compile-time type supports the idisposable interface. You cannot use it with arbitrary objects:
// Does not compile.
// Object does not support idisposable.
Using (Object OBJ = factory. createresource ())
Console. writeline (obj. tostring ());
A quick defensive as clause is all you need to safely dispose of objects that might or might not implement idisposable:
// The correct fix.
// Object may or may not support idisposable.
Object OBJ = factory. createresource ();
Using (OBJ as idisposable)
Console. writeline (obj. tostring ());
If OBJ implements idisposable, the using statement generates the cleanup code. if not, the using statement degenerates to using (null), which is safe but doesn' t do anything. if you're not sure whether you shoshould wrap an object in a using block, err on the side of safety: assume that it does and wrap it in the using clause shown earlier.
That covers the simple case: Whenever you use one disposable object that is local to a method, wrap that one object in a using statement. now you can look at a few more complicated usages. two different objects need to be disposed in that first example: the connection and the command. the example I showed you creates two different using statements, one Wrapping each of the two objects that need to be disposed. each using statement generates a different try/Finally block. in effect, you have written this construct:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Sqlcommand mysqlcommand = NULL;
Try
{
Myconnection = new sqlconnection (connstring );
Try
{
Mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (mysqlcommand! = NULL)
Mysqlcommand. Dispose ();
}
}
Finally
{
If (myconnection! = NULL)
Myconnection. Dispose ();
}
}
Every using statement creates a new nested try/Finally block. I find that an uugly construct, so when I allocate multiple objects that implement idisposable, I prefer to write my own try/finally blocks:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Sqlcommand mysqlcommand = NULL;
Try {
Myconnection = new sqlconnection (connstring );
Mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (mysqlcommand! = NULL)
Mysqlcommand. Dispose ();
If (myconnection! = NULL)
Myconnection. Dispose ();
}
}
However, don't get too cute and try to build one using clause with as statements:
Public void executecommand (string connstring,
String commandstring)
{
// Bad idea. potential resource leak lurks!
Sqlconnection myconnection =
New sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Using (myconnection as idisposable)
Using (mysqlcommand as idisposable)
{
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
}
It looks cleaner, but it has a subtle bug. the sqlconnection object never gets disposed if the sqlcommand () constructor throws an exception. you must make sure that any objects that implement idisposable are allocated inside the scope of a using block or a try block. otherwise, resource leaks can occur.
So far, you 've handled the two most obvious cases. whenever you allocate one disposable object in a method, the using statement is the best way to ensure that the resources you 've allocated are freed in all cases. when you allocate multiple objects in the same method, create multiple using blocks or write your own single try/Finally block.
There is one more nuance to freeing disposable objects. Some types support both a dispose method and a close method to free resources. sqlconnection is one of those classes. You cocould close sqlconnection like this:
Public void executecommand (string connstring,
String commandstring)
{
Sqlconnection myconnection = NULL;
Try {
Myconnection = new sqlconnection (connstring );
Sqlcommand mysqlcommand = new sqlcommand (commandstring,
Myconnection );
Myconnection. open ();
Mysqlcommand. executenonquery ();
}
Finally
{
If (myconnection! = NULL)
Myconnection. Close ();
}
}
This version does close the connection, but that's not exactly the same as disposing of it. the dispose method does more than free resources: it also notifies the Garbage Collector that the object no longer needs to be finalized. dispose callgc. suppressfinalize (). close typically does not. as a result, the object remains in the Finalization queue, even though finalization is not needed. when you have the choice, dispose () is better than close (). you'll learn all the gory details in item 18.
Dispose () does not remove objects from memory. it is a hook to let objects release unmanaged resources. that means you can get into trouble by disposing of objects that are still in use. do not dispose of objects that are still being referenced elsewhere in your program.
in some ways, resource management can be more difficult in C # than it was in C ++. you can't rely on deterministic finalization to clean up every resource you use. but a garbage-collected environment really is much simpler for you. the vast majority of the types you make use of do not implement idisposable. less than 100 classes in. net Framework implement idisposablethat's out of more than 1,500 types. when you use the ones that do implement idisposable, remember to dispose of them in all cases. you shoshould wrap those objects in using clses or try/finally blocks. whichever you use, make sure that objects get disposed properly all the time, every time.