Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Pro Visual C++-CLI And The .NET 2.0 Platform (2006) [eng]-1

.pdf
Скачиваний:
70
Добавлен:
16.08.2013
Размер:
24.18 Mб
Скачать

168

C H A P T E R 4 A D V A N C E D C + + / C L I

catch (InvalidCastException ^e)

{

Console::WriteLine("Invalid Cast Exception"); Console::WriteLine(e->StackTrace);

}

}

Figure 4-4 shows the results of this little program.

Figure 4-4. Results of CatchException.exe

.NET Framework Base Class: Exception Classes

The .NET Framework has an extensive set of exceptions that it may throw. You’ll encounter two different types of exceptions while using .NET:

ApplicationException

SystemException

System::ApplicationException is the base class of those exceptions that are user-defined or, in other words, the ones that you have defined yourself.

System::SystemException, on the other hand, handles exceptions created within the CLR, for example, exceptions caused by stream I/O, databases, security, threading, XML, and so on. You can be sure that if the program has aborted as a result of a system problem, you can catch it using the generic System::SystemException.

Both of these exceptions derive from the System::Exception class, which is the root of all .NET exceptions. The System::Exception class provides many useful properties (see Table 4-4) to help resolve any exceptions that might occur.

Table 4-4. Key System::Exception Member Properties

Property

Description

Helplink

The Uniform Resource Name (URN) or Uniform Resource Locator (URL),

 

if appropriate, to a help file providing more information about the exception.

InnerException

This property gives access to the exception that caused this exception,

 

if any.

Message

A textual description of the error.

C H A P T E R 4 A D V A N C E D C + + / C L I

169

Table 4-4. Key System::Exception Member Properties

Property

Description

Source

The name of the object, assembly, or application that caused the exception.

StackTrace

A text string of all the method calls on the stack made before triggering

 

the exception.

TargetSite

The name of the method that triggered the exception.

 

 

SystemException

You can’t begin to explore all the exceptions that the .NET Framework class library provides to developers. Even the following illustration, which displays some of the more common exceptions, shows only the tip of the iceberg.

The .NET Framework provides developers with a huge set of classes. If something could go wrong, the .NET Framework class library provides an exception for it. As you can see from the preceding illustration, the names of the exceptions are self-explanatory, and if you add to them the properties mentioned previously, you have a great tool for finding where your application threw its exception and why.

170

C H A P T E R 4 A D V A N C E D C + + / C L I

The best resource to use to explore exceptions is the documentation provided by the .NET Framework. You should start your search by looking up System.Exception. From there you should quickly be able to navigate to the exception in question.

There is nothing special about catching exceptions thrown by the system. As long as you place the methods that might throw an exception within a try block, all you have to do is catch the systemthrown exception. Here is an example of exception handling, about as simple as it comes:

try

{

// Methods that throw OutOfMemoryException

}

catch (OutOfMemoryException *oome) // If a method throws an exception

{

// Execution will continue here

 

// Process exception

}

 

ApplicationException

Truthfully, there is nothing stopping you from throwing exceptions derived from the class System::SystemException or System::Exception. It is even possible to derive an exception from one of the exceptions derived from System::SystemException. The .NET Framework only really added the System::ApplicationException class for readability purposes. In fact, neither

System::SystemException nor System::ApplicationException adds any additional functionality to System::Exception.

There is nothing difficult about creating an application exception class. It is just a standard C++/CLI class, but instead of inheriting from System::Object or some other class, you inherit from

System::ApplicationException.

ref class MyException : public ApplicationException

{

};

Within the custom exception, you can implement anything you want, but in practice, you probably only want to implement things that will help resolve the cause of the exception.

If you are an experienced traditional C++ developer, you know that you could derive your exception from any data type. For example, you could create your exception simply from the System::Object class or even a built-in type such as int. This still works in C++/CLI as well, but if you do this, you will lose the ability to have your exceptions caught by other languages besides C++/CLI.

Note All exceptions you create for your applications should be inherited from

System::ApplicationException.

Throwing ApplicationExceptions

Obviously, if you can create your own exceptions, you must be able to throw them, too. Technically, you can throw an exception at any time you want, but in practice, it is best only to throw an exception when something in your program fails unexpectedly and normal process flow can no longer continue. The reason is that the processing of an exception has a lot of overhead, which can slow the program down when executing. Often, it is better to use if statements to process errors.

Syntactically, throwing an exception is very easy. Simply throw a new instance of an exception class. In other words, add code with the following syntax:

C H A P T E R 4 A D V A N C E D C + + / C L I

171

throw gcnew <Exception-Class>(<constructor-parameters>);

or, for example:

throw gcnew ApplicationException("Error Message");

If you create your own derived exception, just replace ApplicationException with it and pass any parameters to its constructor—if the construct has any parameters, that is.

The actual throw statement does not have to be physically in the try block. It can be located in any method that is executed within the try block or any nested method that is called within a try block.

Listing 4-11 shows how to create a custom exception from the .NET Framework’s System::ApplicationException. Notice that because you’re using the System namespace, you don’t have to prefix the exceptions with System::. This program simply loops through the for loop three times, throwing an exception on the second iteration.

Note that the try block is within the for loop. This is because although you can resolve an exception and allow code to continue processing, the only place you are allowed to start or resume a try block is from its beginning. So, if the for loop was found within the try block, there would be no way of resuming the loop, even if you used the dreaded goto statement to try to jump into the middle of the try block.

Listing 4-11. ThrowDerived.exe: Throwing an Exception

using namespace System;

ref class MyException : public ApplicationException

{

public:

MyException( String ^err );

};

MyException::MyException(System::String ^err) : ApplicationException(err)

{

}

void main()

{

for (int i = 0; i < 3; i++)

{

Console::WriteLine("Start Loop"); try

{

if (i == 0)

{

Console::WriteLine("\tCounter equal to 0");

}

else if (i == 1)

{

throw gcnew MyException("\t**Exception** Counter equal to 1");

}

else

{

Console::WriteLine("\tCounter greater than 1");

}

}

172 C H A P T E R 4 A D V A N C E D C + + / C L I

catch (MyException ^e)

{

Console::WriteLine(e->Message);

}

Console::WriteLine("End Loop");

}

}

Figure 4-5 shows the results of this little program.

Figure 4-5. Results of ThrowDerived.exe

As you can see, there is nothing spectacular about throwing an exception of your own. It is handled exactly the same way as a system exception, except now you are catching an exception class you created instead of one created by the .NET Framework.

Rethrowing Exceptions and Nested try Blocks

Sometimes your program may catch an exception that it cannot completely resolve. In these cases, the program might want to rethrow the exception so that another catch block can resolve the exception.

To rethrow an exception, simply add this statement within the catch block:

throw;

Once you rethrow the exception, exactly the same exception continues to make its way up the stack, looking for another catch block that matches the exception. Rethrowing an exception only works with nested try blocks. It will not be caught in a catch block at the same level as it was originally caught and thrown but instead will be caught in a catch block at a higher level.

There is no limit on nesting try blocks. In fact, it is a common practice to have one try block that surrounds the entire program within the main() function and to have multiple try blocks surrounding other areas of the code where an exception has a higher probability of occurring. This format allows the program to catch and resolve exceptions close to where the exception occurred, but it still allows the program to catch other unexpected exceptions before the program ends, so that the program may shut down more gracefully.

Listing 4-12 is a contrived example showing an exception being rethrown within nested try blocks. Of course, nesting try blocks immediately together like this doesn’t make much sense.

C H A P T E R 4 A D V A N C E D C + + / C L I

173

Listing 4-12. RethrowException.exe: Rethrowing an Exception

using namespace System;

void main()

{

try

{

try

{

throw gcnew ApplicationException("\t***Boom***"); Console::WriteLine("Imbedded Try End");

}

catch (ApplicationException ^ie)

{

Console::WriteLine("Caught Exception "); Console::WriteLine(ie->Message);

throw;

}

Console::WriteLine("Outer Try End");

}

catch (ApplicationException ^oe)

{

Console::WriteLine("Recaught Exception "); Console::WriteLine(oe->Message);

}

}

Figure 4-6 shows the results of this little program.

Figure 4-6. Results of RethrowException.exe

Catching Multiple Exceptions

So far, you have only dealt with a single catch block associated with a try block. In reality, you can have as many catch blocks associated with a try block as there are possible exception classes that can be thrown by the try block. (Actually, you can have more, but catching exceptions that are not thrown by the try block is a waste of time and code.)

Using multiple catch blocks can be a little trickier in C++/CLI than in traditional C++ because all exceptions are derived from a single class. The order in which the catch blocks are placed after the try block is important. For catch blocks to work properly in C++/CLI, the most-derived class must appear first and the least-derived class or the base class, System::Exception, must appear last.

174

C H A P T E R 4 A D V A N C E D C + + / C L I

For example, System::IO::FileNotFoundException must be caught before System:IO::IOException is caught, which in turn must be caught before System::SystemException is caught, which ultimately must be caught before System::Exception. You can find the order of system exception inheritance in the documentation provided by the .NET Framework.

Listing 4-13 shows the correct order of catching exceptions of derived exception class, but this time they are all derived from the System::ApplicationException class. You might want to change the order of the catch blocks to see what happens.

Listing 4-13. MultiException.exe: Catching Multiple Exceptions

using namespace System;

/// <Summary>Base Class</Summary>

ref class LevelOneException : public ApplicationException

{

public:

LevelOneException( String ^err );

};

LevelOneException::LevelOneException(String ^err) : ApplicationException(err)

{

}

/// <Summary>Inherited Class</Summary>

ref class LevelTwoException : public LevelOneException

{

public:

LevelTwoException( String ^err );

};

LevelTwoException::LevelTwoException(String ^err) : LevelOneException(err)

{

}

/// <Summary>Catching multiple exceptions</Summary> void main()

{

C H A P T E R 4 A D V A N C E D C + + / C L I

175

for (int i = 0; i < 4; i++)

{

Console::WriteLine("Start Loop"); try

{

if (i == 1)

throw gcnew ApplicationException("\tBase Exception Thrown"); else if (i == 2)

throw gcnew LevelOneException("\tLevel 1 Exception Thrown"); else if (i == 3)

throw gcnew LevelTwoException("\tLevel 2 Exception Thrown");

Console::WriteLine("\tNo Exception");

}

catch (LevelTwoException ^e2)

{

Console::WriteLine(e2->Message); Console::WriteLine("\tLevel 2 Exception Caught");

}

catch (LevelOneException ^e1)

{

Console::WriteLine(e1->Message); Console::WriteLine("\tLevel 1 Exception Caught");

}

catch (ApplicationException ^e)

{

Console::WriteLine(e->Message); Console::WriteLine("\tBase Exception Caught");

}

Console::WriteLine("End Loop");

}

}

Figure 4-7 shows the results of this little program.

Figure 4-7. Results of MultiException.exe

176

C H A P T E R 4 A D V A N C E D C + + / C L I

Catching All Previously Uncaught Exceptions

If you want to correctly code C++/CLI code, which is used in a multilanguage environment, then the easiest way of catching all exceptions is simply to add the catching of System::Exception to the end of your catch block, because all .NET exceptions—of both system and application origin—are derived from this class.

There is also another way of catching all uncaught exceptions, even those not derived from System::Exception. It is simply a catch block without an exception call. In the class’s place is an ellipsis:

catch (...)

{

}

Unsafe Code The catch(...) block is an unsafe coding construct. You can only throw or catch handles to a ref class with /clr:safe.

This form of catch block doesn’t provide much in the way of information to help determine what caused the exception, because it doesn’t have as a parameter any type of exception to derive from. Thus, there’s no way to print out the stack or messages associated with the exception that’s generated. All you actually know is that an exception occurred.

In C++/CLI, this form of catch block should probably only be used as a last resort or during testing, because if this catch block is executed, your code will not work properly in the .NET portable managed multilanguage environment anyway. Of course, if your code is not destined for such an environment, then you may need to use this form of catch block.

The usual reason that this type of exception occurs in C++/CLI is that the developer forgot to derive the exception class from System::ApplicationException. Listing 4-14 shows this occurring.

Listing 4-14. CatchAll.exe: Catching All Exceptions

using namespace System;

ref class MyDerivedException : public ApplicationException

{

public:

MyDerivedException( String ^err );

};

MyDerivedException::MyDerivedException(String ^err) : ApplicationException(err)

{

}

ref class MyException // Not derived from Exception class

{

};

C H A P T E R 4 A D V A N C E D C + + / C L I

177

void main()

{

for (int i = 0; i < 4; i++)

{

Console::WriteLine("Start Loop"); try

{

if (i == 1)

throw gcnew ApplicationException("\tBase Exception"); else if (i == 2)

throw gcnew MyDerivedException("\tMy Derived Exception"); else if (i == 3)

throw gcnew MyException();

Console::WriteLine("\tNo Exception");

}

catch (ApplicationException ^e)

{

Console::WriteLine(e->Message);

}

catch (...)

{

Console::WriteLine("\tMy Exception");

}

Console::WriteLine("End Loop");

}

}

Figure 4-8 shows the results of this little program.

Figure 4-8. Results of CatchAll.exe

Executing Code Regardless of an Exception

There are times when code needs to be run at the completion of a try block, whether the try block completed cleanly or threw an exception. For example, you may want to close a file stream or database that has been open in the try block. Up until now, if you threw an exception, there was no way to ensure that such code would always run unless you put the close statement at the end of each of the try and catch blocks.