You are now ready to look at a second example that illustrates exceptions. This example, called SolicitColdCall, contains two nested try blocks and also illustrates the practice of defining your own custom exception classes and throwing another exception from Inside a try block.
This example assumes that a sales company wants to have additional customers on its sales list. The company’s sales team is going to phone a list of people to invite them to become customers, a practice known in sales Jargon as cold Calling. To this end, you have a text file available that contains the names of the people to be cold called. The file should be in a well-defined format in which the first line contains the number of people in the file and each subsequent line contains the name of the next person. In other word , a correctly formatted file of names might look like this:
This version of cold calling is designed to display the name of the person on the screen (perhaps for the sales person to read). That is why only names and not phone numbers of the individuals are contained in the file.
For this example, your program will ask the user for the name of the file and will then simply read it in and display the names of people. That sounds like a simple task, but even some couple of things can go wrong and require you to abandon the entire procedure:
- The user might type the name of a file that does not exist. This will be caught as a FileNotFound exception.
- The file might not be in the correct format. There are two possible problems here. First, the first line of the file might not be an integer. Second, there might not be as many names in the file as the first line of the file indicates. In both cases, you want to trap this oddity as a custom exception that has been written specially for this purpose, ColdCallFileFormatException.
There is something else that can go wrong that, while not causing you to abandon the entire proceed, will mean that you need to abandon that person and move on to the next person in the file (and therefore will need to be trapped by an inner try block). Some people are spies working for rival sales companies, and obviously, you would not want to let these people know what you are up to by accidentally phoning one of them. Your research has indicated that you can identify who the spies are because their names begin with B: Such people should have been screened out when the data file was first prepared, but just in case any have slipped through, you will need to check each name in the file and throw a SalesSpyFoundException if you detect a sales spy. This, of course, is another custom exception object.
Finally, you will implement this example coding a class, ColdCallFpeReader, which maintains the connection to the cold-call file and retrieves data from it. You will code this class in a very safe way, which means that its methods will all throw exceptions if they are called inappropriately; for example, if a method that will read a file is called before the file has even been opened- For this purpose, you will write another exception class, UnexpectedException.
Catching the User-Defined Exceptions
Let’s start with the Main () method of the SolicitColdCall sample, which catches your user-defined exceptions. Note that you will need to call up file-handling classes in the System. 10 namespace as well as the System namespace.
This code is a little more than just. loop to principle from the file. You start by asking the user fur the name of the file. Then you instantiate an object of a class called ColdCallFileReader, which is defined shortly. The ColdCallFileReader calls is the class that handles the file reading. Notice that you do this outside the initial try block – that’s because the variables that you instantiate here need to be available in the subsequent catch and finally blocks, and if you declared them inside the try block they would go out of scope at the closing curly brace of the try block, which would not be a good thing .
.In the try block, you open the file (using the ColdCallFileReader .Open() method) and loop over all the people in it. The ColdCallFileReader. ProcessNextPerson () method reads in and displays the name of the next person in the file, and the ColdCallFileReader .PeopleToRing property that you how many people should be in the file (obtained by reading the first line of the file). There are three catch blocks: one:.torFileNotFoundException, one for ColdCallFileFormatException, and one to
trap any other .NET exceptions. In the cue of a FileNotFoundException, you display a message to that effect. Notice that in this catch block, the exception instance is not actually used at all. This catch block is used to illustrate the user friendliness of the application. Exception objects generally contain technical information that is useful for developers, but not the sort of stuff you ‘want to show to your end users. So in this case, you create a simpler message of your own.
For tlw ColdCallFileFormatException handler, you have done the opposite, and ‘illustrated how to give fuller technical information, including details of the inner exception, if one is present.
Finally, if you catch any other generic exceptions, you display a user-friendly message, Instead of letting any such exceptions fall through to the .NET runtime. Note that you have chosen not to handle any other exceptions not derived from System. Exception, because you are not calling directly into non-.NET code.
The finally block is there to clean up resources. In this case, this means closing any open file – performed by the ColdCallFileReader .Dispose () method.
Throwing the User Defined Exceptions
Now take a look at the definition of the class that handles the file reading and (potentially) throws your . user-defined exceptions: ColdCallFileReader. Because this class maintains an external file connection, you will need to make sure that it is disposed of correctly in accordance with the principles laid down for the disposing. Therefore, you derive this class from IDisposable.
First, you declare some variables:
FileStream and StreamReader, both in the System. 10 namespace, are the base causes that you will use to read the file. FileStream allows you to connect to the the first place, whereas StreamReader is specially geared up to reading text files and implements a method, ReadLine ( ), which reads a line of text from a file.
The is Disposed field indicates whether the Dispose () method has been called. ColdCallFileReader is implemented so that once Dispose () has been called, it is not permitted to reopen connection and reuse the object. Open is also used for error checking – in this case, checking whether the StreamReader actually connects to an open file.
The process of opening the file and reading in that first line – the one that tell. you how many people are in the file – is handled by the Open () method:
The first thing you do in this method (as with all other ColdCallFileReader methods) is check whether the client code has inappropriately called it after the object has been disposed of, and if so, throw a predefined ObjectDisposedException object. The Open() method checks the is Disposed field to see whether Dispose () has already been called. Because calling Dispose () implies that the caller has now finished with this object, you regard it as an error to attempt to open a new file connection
if Dispose () has been called.
Next, the method contains the first of two inner try blocks. The purpose of this one is to catch any errors resulting from the first line of the file not containing an integer. If that problem arises, the .NET runtime will throw a FormatException, which you trap and convert to a more meaningful exception that indicates there is actually a problem with the format of the cold-call file.Note that System. FormatException is there to indicate format problems with basic data types, not with files. and is not a particularly useful
exception to pus back to the calls routine in this cue. The new exception thrown will be trapped by the outer try block. Because no cleanup needed here. there is no need for a Unary block.
If everything is fine. you set the isOpen field to true to indicate that there is now a valid file connection from which data can be read.
The process NextPerson () method also contain an inner try block:
A problem might also occur if you .try to read the next name and discover that you have already reached the end-of the file. The way that the StreamReader object’s ReadLine () method works is if it has gone past the end of the file. it doesn’t throw an exception. but simply returns null. Therefore. if you find a null string, you know that the format of the file was incorrect because the number in the first line of the file indicated a larger number of names than were actually present in the file. If that happens, you throw a ColdCallFileFormatExceptibn, which will be caught by the outer exception handler (which will cause execution to terminate).
Once again, you don’t need a finally block here because there is no cleanup to do; however, this time an empty finally block is included, just to show that you can do so, if you want.
The example is nearly finished. You have just two more members of ColdCallFileReader to look at: the PeopleToRing property, which returns the number of people supposed to be in the file, and me Dispose () method, which closes an open file. Notice that the Dispose () method just returns if it already been called – this is the recommended way of implementing it. It also checks that there actually is a file stream to close before closing it. This example is shown here to illustrate defensive coding techniques, so that’s what you are doing!
Defining the User-Defined Exception Classes
Finally, you need to define your own three exception classes. Defining your own exception is quite easy because there are rarely any extra methods to add. It is just a case of implementing a constructor to ensure that the base class constructor is called correctly. Here is the full implementation of SalesSpyFoundException:
Notice that it is derived from ApplicationException, as you would expect for a custom exception. In fact, in practice, you would probably have put in an intermediate class, something like ColdCallFileException, derived from ApplicationException, and derived both of your exception classes from this class. This would ensure that the handling code has that extra-fine degree of control over which exception handler handles which exception. However, to keep the example simple, you will not do that.
You have done one bit of processing in SalesSpyFoundException. You have assumed that the message passed into its constructor is just the name of the spy found, so you turn this string into a more meaningful error message. You have also provided two constructors, one that simply takes a message, and one that also takes an inner exception as a parameter. When defining your own exception classes, it is best to include, at a minimum, at least these two constructors (although you will not actually be using the second SalesSpyFoundException constructor in this example).
Now for the ColdCallFileFormatException. This follows the same principles as the previous exception, except that you don’t do any processing on the message:
Now you are ready to test the program. First,try the people. txt file whose contents are defined here.
This has four names (which match the number given in the first line of the file), including one spy.Then try the following people2 •txt file,which has an obvious formatting error:
Finally,try the example but specify the name of a file that does not exist,say , people3. txt. Running the program three times for the three file names gives these results:
In the end, this application shows you a number of different ways in which you can handle the errors and exceptions that you might find in your own applications.