(Other Format - BK&CD-ROM)
This title gives readers with previous Microsoft Visual C++® or Microsoft Visual Basic® programming experience the foundation they need to hit the ground running with Microsoft Visual C#.
More Reviews and RecommendationsJohn Sharp is a Principal Technologist at Content Master Ltd, a technical authoring company in the United Kingdom. There he researches and develops technical content for technical training courses, seminars, and white papers. Throughout his development career, John has been active in training, developing and delivering courses, and he currently trains full time. He conducts courses on subjects ranging from Unix Systems Programming, to SQL Server Administration, to Enterprise Java Development. He has used his experience to create a broad range of training materials covering many subjects. John is deeply involved with .NET development, writing courses, building tutorials, and delivering conference presentations covering C# development and ASP.NET. He lives in Tetbury, Gloucestershire in the United Kingdom.
Jon Jagger is an independent software trainer/designer/consultant specializing in C#, C++, Java, C, OO, patterns, design, and general programming. His work on this book was done through Content Master Ltd, a technical authoring company in the United Kingdom. Jon is a UK C++ standards panel member and a regular contributor to the ACCU Overload journal. His interests include training excellence, design, simplicity, problem solving, and Monty Python (which he says is required knowledge for all software developers). Jon, his wife Natalie and their three small children (Ellie, Penny, and Patrick) live in a delightful 104-year-old house overlooking a 7-acre field of barley in a village called Trull (population totals 300).You can send him email at jon@jaggersoft.com.
Reader Rating:
See Detailed Ratings
May 07, 2002: Good book for inexperienced developers. It assumes a certain amount of knowledge of general programming principles, but most newbies can probably make up what they're missing at the beginning. One word of caution, however. This book is full of minor errors and inaccuracies, so make sure to check the publisher's website before diving too far into it. If you do this, you can save yourself some headaches, and you WILL learn to program with Visual C#
This title gives readers with previous Microsoft Visual C++(r) or Microsoft Visual Basic(r) experience the foundation they need to hit the ground running with the versatile Microsoft Visual C# .NET object-oriented Web development language. Real-world programming scenarios and easy-to-follow, step-by-step exercises offer the fast and clear instruction readers need to begin creating stable, efficient business-level objects and system-level applications. Readers also get code samples on a companion CD-ROM to study and reuse in their own projects.
Loading...| Acknowledgments | ||
| Introduction | ||
| Pt. 1 | Introducing Microsoft Visual C# and Visual Studio.Net | 1 |
| Ch. 1 | Welcome to C# | 3 |
| Ch. 2 | Working with Variables, Operators, and Expressions | 29 |
| Ch. 3 | Writing Methods and Applying Scope | 45 |
| Ch. 4 | Using Decision Statements | 63 |
| Ch. 5 | Using Iteration Statements | 79 |
| Ch. 6 | Managing Errors and Exceptions | 99 |
| Pt. 2 | Understanding the C# Language | 123 |
| Ch. 7 | Creating and Managing Classes and Objects | 125 |
| Ch. 8 | Understanding Values and References | 147 |
| Ch. 9 | Creating Value Types with Enumerations and Structs | 165 |
| Ch. 10 | Using Arrays and Collections | 183 |
| Ch. 11 | Understanding Parameter Arrays | 205 |
| Ch. 12 | Working with Inheritance | 219 |
| Ch. 13 | Using Garbage Collection and Resource Management | 245 |
| Pt. 3 | Creating Components | 261 |
| Ch. 14 | Implementing Properties to Access Attributes | 263 |
| Ch. 15 | Using Indexers | 279 |
| Ch. 16 | Delegates and Events | 293 |
| Ch. 17 | Operator Overloading | 311 |
| Pt. 4 | Working with Windows Applications | 327 |
| Ch. 18 | Introducing Windows Forms | 329 |
| Ch. 19 | Working with Menus | 351 |
| Ch. 20 | Performing Validation | 365 |
| Ch. 21 | Using Complex Controls | 379 |
| Ch. 22 | Using MDI, Windows, and Dialog Boxes | 405 |
| Ch. 23 | Creating GUI Components | 421 |
| Pt. 5 | Managing Data | 439 |
| Ch. 24 | Using a Database | 441 |
| Ch. 25 | Working with Data Binding and DataSets | 461 |
| Ch. 26 | Handling XML | 479 |
| Pt. 6 | Building Web Applications | 499 |
| Ch. 27 | Introducing ASP.NET | 501 |
| Ch. 28 | Understanding Validation Controls | 523 |
| Ch. 29 | Accessing Data with Web Forms | 533 |
| Ch. 30 | Building ASP.NET Applications | 555 |
| Ch. 31 | Building an XML Web Service | 573 |
| Ch. 32 | Consuming a Web Service | 591 |
| About the Authors | 603 | |
| Index | 605 |
Chapter 13 Using Garbage Collection and Resource Management
In this chapter, you will learn how to:
One of the great strengths of the Microsoft Visual C# language is that it makes a fundamental distinction between values and objects. Values and objects are different.
The distinction between values and objects was covered in detail in Chapter 8. Here's a brief recap:
int i = 42; // i is a value
TextBox box = new TextBox(); // box refers to an object
{
int i = 42;
TextBox message = new TextBox();
} // i ends its life here,
// but the object referred to by TextBox lives on
The Life and Times of an Object
You create an object like this:
new TextBox();
From your point of view, this is an atomic operation, but underneath, object creation is really a two-phase process. First you have to allocate some raw memory from the heap. You do this using the new keyword. You have no control over this phase of an object's creation. Second you have to convert the raw memory into an object; you have to initialize the object. You do this by using a constructor. In contrast to allocation, you do have control over this phase of an object's creation.
It's common to create an object when initializing a reference variable. For example:
TextBox message = new TextBox();
You can then use the object that the reference refers to by using the dot operator. For example:
message.Text = "People of Earth, your attention please";
Object death is also a two-phase process. The two phases exactly mirror the two phases of creation. First you have to convert the object back into raw memory. You do this by writing a destructor. The destructor is the opposite of the constructor. Second the raw memory has to be given back to the heap; the binary bits that the object lived in have to be deallocated. Once again you have no control over this phase. The process of returning memory back to the heap is known as garbage collection.
The syntax for writing a destructor is a tilde (~) followed by the name of the class. For example, here's a simple class that counts the number of live instances by incrementing a static count in the constructor and decrementing the static count in the destructor:
(Code Unavailable)
There are some very important destructor restrictions:
struct Tally
{
~Tally() { ... } // compile-time error
}
public ~Tally() { ... } // compile-time error
~Tally(int parameter) { ... } // compile-time errorThe compiler automatically translates a destructor into an override of the Object.Finalize method. In other words, the compiler translates the following destructor:
class Tally
{
~Tally() { ... }
}
Into this:
class Tally
{
protected override void Finalize()
{
try { ... }
finally { base.Finalize(); }
}
}
The compiler-generated Finalize method contains the destructor body inside a try block, followed by a finally block that calls the base class Finalize. This ensures that a destructor always calls its base class destructor (just like in C++).
It's important to realize that only the compiler can make this translation. You can't override Finalize yourself and you can't call Finalize yourself. In other words, Finalize really is just another name for the destructor.
Why Use the Garbage Collector?
The fundamental thing to remember about destructors is that you can never call them. The reason for this is that, in C#, you can never destroy an object yourself. There just isn't any syntax to do it. There are good reasons why the designers of C# decided to forbid you from explicitly writing code to destroy objects. If it was your responsibility to destroy objects, sooner or later:
These problems are unacceptable in a language like C#, which places robustness and security high on its list of design goals. Instead, the garbage collector is responsible for destroying objects for you. Only the garbage collector can destroy objects. The garbage collector guarantees that:
These guarantees are tremendously useful and free you, the programmer, from tedious housekeeping chores that are easy to get wrong. They allow you to concentrate on the logic of the program itself and be more productive.
However, as with all design trade-offs, garbage collection comes at a price. You don't know the order in which objects will be destroyed. Objects are not necessarily destroyed in the reverse order that they are created in (as they are in C++). Neither do you know when the garbage collector will decide to destroy objects. An object is not destroyed at the moment that it becomes unreachable. Because destroying objects can be a time-consuming operation, the garbage collector destroys objects only when it is necessary (when the heap memory is exhausted) or when you explicitly ask it to (by calling the System.GC.Collect method). Clearly, this makes C# unsuitable for some time-critical applications.
How Does the Garbage Collector Run?
The garbage collector runs in its own thread and only runs when other threads are in a safe state (for example, when they are suspended). This is important because, as the garbage collector runs, it needs to move objects and update object references. The steps that the garbage collector takes when it runs are as follows:
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 perform Steps 3 and 5 (in the previous section). Clearly, not doing something is faster than doing it. (There's no code faster than no code.) The first recommendation is, therefore, to try to avoid using destructors except when you really need them (for example, consider a using statement instead; these are covered in the following section).
If you write a destructor, you need to write it very carefully. 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, the order of finalization is not guaranteed). The second recommendation is therefore to ensure that each destructor reclaims one resource and nothing else. This can require splitting a class into two classes (one of which is the class dedicated to managing the resource).
Sometimes it's inadvisable to release a resource in a destructor; some resources are just too valuable and too scarce to lie around unreleased for arbitrary lengths of time. (Remember, you don't know when the garbage collector will call an object's destructor.) 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. A disposal method is a method that disposes of a resource. If a class has a disposal method, you can call it explicitly and thus control when the resource is released.
An example of a class that contains a disposal method is the TextReader class from the System.IO namespace. TextReader contains a virtual method called Close. The StreamReader (which reads characters from a stream) and StringReader (which reads characters from a string) classes both derive from TextReader and both override the Close method. Here's an example that reads lines from a file using the StreamReader class:
TextReader reader = new StreamReader(filename);
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
reader.Close();
It's important to call Close when you have finished with reader to release the file handle (and encoding) 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.
One way to ensure that a disposal method is always called, regardless of whether there is an exception, is to call the disposal method inside a finally block. Here's the previous example using this technique:
TextReader reader = new StreamReader(filename);
try
{
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
finally
{
reader.Close();
}
Using a finally block like this works, but it has several drawbacks that make it a less than ideal solution:
The using statement is designed to solve all these problems.
The syntax for a using statement is as follows:
using ( type variable = initialization ) embeddedStatement
Such a using statement is precisely equivalent to the following translation:
{
type variable = initialization;
try
{
embeddedStatement
}
finally
{
if (variable != null)
{
((IDisposable)variable).Dispose();
}
}
}
This equivalence means that 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 called Dispose:
namespace System
{
interface IDisposable
{
void Dispose();
}
}
You can use a using statement as a clean, exception-safe, robust way to ensure that a resource is always automatically released. You just need to make sure that:
For example:
(Code Unavailable)
Here is the best way to make sure 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);
}
}
This solves all of the problems that existed in the manual try and finally solution. You now have a solution that:
It's instructive to consider how you could have used a using statement to ensure that your code always called TextReader.Close if the TextReader class didn't implement the IDisposable interface. You could do it like this:
struct AutoClosing : IDisposable
{
public AutoClosing(TextReader target)
{
this.target = target;
}
public TextReader GetTextReader()
{
return target;
}
public void Dispose()
{
target.Close();
}
private readonly TextReader target;
}
Notice that:
You can rewrite the GetTextReader accessor method as a read-only property. Properties are covered in Chapter 14.
You could then use this struct as follows:
using (AutoClosing safe = new AutoClosing(new StreamReader(filename)))
{
TextReader reader = safe.GetTextReader();
string line;
while ((line = reader.ReadLine()) != null)
{
Console.WriteLine(line);
}
}
Notice how the reader variable is still in the scope of the using statement.
Calling a Disposal Method from a Destructor
One of the drawbacks of the disposal method pattern is that it relies on you (the programmer) to call the disposal method, and programmers tend to occasionally forget to do things like this. The trade-off in deciding whether to use destructors or disposal methods is this: A call to a destructor will happen, you just don't know when, whereas you know exactly when a call to a disposal method happens, you just can't be sure that it will actually happen because you might forget to call it. However, it is possible to ensure that a disposal method is always called. The solution is to call the disposal method from a destructor. This acts as a useful "backup." You might forget to call the disposal method, but at least you can be sure that it will be called, even if it's only when the program shuts down. An example of how to do this is:
(Code Unavailable)
Notice that:
In the following exercise, you will rewrite a small piece of code. The code opens a text file, reads its contents one line at a time, writes these lines to a rich text box on a Windows form, and then closes the text file. The problem is that the code is not exception-safe. If an exception arises as the file is read or as the lines are written to the rich text box, the call to close the text file will be bypassed. You will rewrite the code to use a using statement instead, thus ensuring that the code is exception-safe.
The UsingStatement project opens.
The Windows form appears.
The Open dialog box opens.
The contents of the file are loaded into the Windows form.
(Image unavailable)
You return to Visual Studio .NET.
This method should look exactly like this:
private void openFileDialog_FileOk(object sender,
System.ComponentModel.CancelEventArgs e)
{
string fullPathname = openFileDialog.FileName;
FileInfo src = new FileInfo(fullPathname);
filename.Text = src.Name;
source.Text = "";
TextReader reader = new StreamReader(fullPathname);
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
reader.Close();
}
The filename, openFileDialog, and the source identifiers are three private fields of the Form1 class. The problem with this code is that the final statement, the call to reader.Close, is not guaranteed to happen. If you are not used to handling exceptions, it can take a while to get used to this and even longer to spot it as a potential problem.
private void openFileDialog_FileOk(object sender,
System.ComponentModel.CancelEventArgs e)
{
string fullPathname = openFileDialog.FileName;
FileInfo src = new FileInfo(fullPathname);
filename.Text = src.Name;
source.Text = "";
TextReader reader = new StreamReader(fullPathname);
try
{
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
}
finally
{
reader.Close();
}
}
private void openFileDialog_FileOk(object sender,
System.ComponentModel.CancelEventArgs e)
{
string fullPathname = openFileDialog.FileName;
FileInfo src = new FileInfo(fullPathname);
filename.Text = src.Name;
source.Text = "";
using (TextReader reader = new StreamReader(fullPathname))
{
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
}
}
To do this, try making a call to reader.Close after the using statement, like this:
private void openFileDialog_FileOk(object sender,
System.ComponentModel.CancelEventArgs e)
{
string fullPathname = openFileDialog.FileName;
FileInfo src = new FileInfo(fullPathname);
filename.Text = src.Name;
source.Text = "";
using (TextReader reader = new StreamReader(fullPathname))
{
string line;
while ((line = reader.ReadLine()) != null)
{
source.Text += line + "\n";
}
}
reader.Close();
}
You will find that this does not compile. You will see an error message such as:
The type or namespace name 'reader' could not be found (are you missing a using directive or an assembly reference?)
This error message appears because in the previous example, use of the variable reader is only permitted within the body of the using statement.
If you want to continue to the next chapter
If you want to exit Visual Studio .NET now
If you see a Save dialog box, click Yes.
Chapter 13 Quick Reference
| To | Do this |
| Write a destructor | Write a method whose name is the same as the name of the class and is prefixed with a tilde (~). The method must not have an access modifier (such as public) and cannot have any parameters. For example: (Code Unavailable) |
| Call a destructor | Forget it. You can't call a destructor. Only the garbage collector can. |
| Force garbage collection | Call System.GC.Collect(); |
| Release a resource at a known point in time | Write a disposal method (a method that disposes of a resource) and call it explicitly from the program. For example: (Code Unavailable) |
| Release a resource at a known point in time in an exception-safe manner | Release the resource with a using statement. For example: (Code Unavailable) |
loading...
loading...
loading...
Terms of Use, Copyright, and Privacy Policy
© 1997-2009 Barnesandnoble.com llc
