Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C# 2008 Step by Step.pdf
Скачиваний:
27
Добавлен:
25.03.2016
Размер:
14 Мб
Скачать
☆

262Part II Understanding the C# Language

3.It deallocates the remaining unreachable objects (those that don’t require finalization) by moving the reachable objects down the heap, thus defragmenting the heap and freeing memory at the top of the heap. When the garbage collector moves a reachable object, it also updates any references to the object.

4.At this point, it allows other threads to resume.

5.It finalizes the unreachable objects that require finalization (now in the freachable queue) by its own thread.

Recommendations

Writing classes that contain destructors adds complexity to your code and to the garbage collection process and makes your program run more slowly. If your program does not contain any destructors, the garbage collector does not need to place unreachable objects in the freachable queue and finalize them. Clearly, not doing something is faster than doing it. Therefore, try to avoid using destructors except when you really need them. For example, consider a using statement instead. (See the section “The using Statement” later in this chapter.)

You need to be very careful when you write a destructor. In particular, you need to be aware that, if your destructor calls other objects, those other objects might have already had their

destructor called by the garbage collector. Remember that the order of finalization is not guaranteed. Therefore, ensure that destructors do not depend on one another or overlap with one another. (Don’t have two destructors that try to release the same resource, for example.)

Resource Management

Sometimes it’s inadvisable to release a resource in a destructor; some resources are just too valuable to lie around waiting for an arbitrary length of time until the garbage collector actually releases them. Scarce resources need to be released, and they need to be released as

soon as possible. In these situations, your only option is to release the resource yourself. You can achieve this by creating a disposal method. A disposal method is a method that explicitly

disposes of a resource. If a class has a disposal method, you can call it and control when the resource is released.

Note The term disposal method refers to the purpose of the method rather than its name. A disposal method can be named using any valid C# identifier.

Chapter 14 Using Garbage Collection and Resource Management

263

Disposal Methods

An example of a class that implements a disposal method is the TextReader class from the System.IO namespace. This class provides a mechanism to read characters from a sequential stream of input. The TextReader class contains a virtual method named Close, which closes the stream. The StreamReader class (which reads characters from a stream, such as an open file) and the StringReader class (which reads characters from a string) both derive from TextReader, and both override the Close method. Here’s an example that reads lines of text from a file by using the StreamReader class and then displays them on the screen:

TextReader reader = new StreamReader(filename); string line;

while ((line = reader.ReadLine()) != null)

{

Console.WriteLine(line);

}

reader.Close();

The ReadLine method reads the next line of text from the stream into a string. The ReadLine method returns null if there is nothing left in the stream. It’s important to call Close when you have finished with reader to release the file handle and associated resources. However, there is a problem with this example: it’s not exception-safe. If the call to ReadLine or WriteLine throws an exception, the call to Close will not happen; it will be bypassed. If this happens of-

ten enough, you will run out of file handles and be unable to open any more files.

Exception-Safe Disposal

One way to ensure that a disposal method (such as Close) is always called, regardless of whether there is an exception, is to call the disposal method inside a finally block. Here’s the preceding example coded using this technique:

TextReader reader = new StreamReader(filename); try

{

string line;

while ((line = reader.ReadLine()) != null)

{

Console.WriteLine(line);

}

}

finally

{

reader.Close();

}

264

Part II Understanding the C# Language

 

Using a finally block like this works, but it has several drawbacks that make it a less than ideal

 

solution:

 

It quickly gets unwieldy if you have to dispose of more than one resource. (You end up

 

with nested try and finally blocks.)

 

In some cases, you might have to modify the code. (For example, you might need to

 

reorder the declaration of the resource reference, remember to initialize the reference

 

to null, and remember to check that the reference isn’t null in the finally block.)

 

It fails to create an abstraction of the solution. This means that the solution is hard to

 

understand and you must repeat the code everywhere you need this functionality.

 

The reference to the resource remains in scope after the finally block. This means that

 

you can accidentally try to use the resource after it has been released.

 

The using statement is designed to solve all these problems.

The using Statement

The using statement provides a clean mechanism for controlling the lifetimes of resources. You can create an object, and this object will be destroyed when the using statement block

finishes.

Important Do not confuse the using statement shown in this section with the using directive that brings a namespace into scope. It is unfortunate that the same keyword has two different meanings.

The syntax for a using statement is as follows:

using ( type variable = initialization )

{

StatementBlock

}

Here is the best way to ensure that your code always calls Close on a TextReader:

using (TextReader reader = new StreamReader(filename))

{

string line;

while ((line = reader.ReadLine()) != null)

{

Console.WriteLine(line);

}

}

Chapter 14 Using Garbage Collection and Resource Management

265

This using statement is precisely equivalent to the following transformation:

{

TextReader reader = new StreamReader(filename); try

{

string line;

while ((line = reader.ReadLine()) != null)

{

Console.WriteLine(line);

}

}

finally

{

if (reader != null)

{

((IDisposable)reader).Dispose();

}

}

}

Note The using statement introduces its own block for scoping purposes. This arrangement means that the variable you declare in a using statement automatically goes out of scope at the end of the

embedded statement and you cannot accidentally attempt to access a disposed resource.

The variable you declare in a using statement must be of a type that implements the IDisposable interface. The IDisposable interface lives in the System namespace and contains just one method, named Dispose:

namespace System

{

interface IDisposable

{

void Dispose();

}

}

It just so happens that the StreamReader class implements the IDisposable interface, and its Dispose method calls Close to close the stream. You can employ a using statement as a clean,

exception-safe, and robust way to ensure that a resource is always released. This approach solves all of the problems that existed in the manual try/finally solution. You now have a

solution that:

Scales well if you need to dispose of multiple resources.

Doesn’t distort the logic of the program code.

Abstracts away the problem and avoids repetition.

Is robust. You can’t use the variable declared inside the using statement (in this case, reader) after the using statement has ended because it’s not in scope anymore—you’ll

get a compile-time error.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]