Exceptions, Attributes, and Reflection

26 331 0
Tài liệu đã được kiểm tra trùng lặp
Exceptions, Attributes, and Reflection

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

Thông tin tài liệu

259 ■ ■ ■ CHAPTER 10 Exceptions, Attributes, and Reflection I n this chapter, you’ll begin by looking at aspects of exception handling in C++/CLI that are not present in classic C++. Then you’ll look at attributes, which supply metadata for a type and, although not part of standard C++, may be familiar if you’ve used previous versions of Visual C++. You’ll learn how to use the existing .NET Framework attributes, examine some of the common ones, and look at how to define and use your own attributes. Finally, you’ll get a brief overview of the reflection features of the .NET Framework, which provide a way to discover information on a type at runtime and use that information to interact dynamically with a type. Exceptions Exceptions are supported in classic C++, but not universally used. In .NET Framework programming, exceptions are ubiquitous, and you cannot code without them. This chapter assumes you are aware of the basic concepts of exception handling, throwing exceptions, and the try/catch statement. All of these features of classic C++ are valid in C++/CLI code. A key difference between exception handling in C++/CLI and in classic C++ is that excep- tions are always thrown and caught by reference (via a handle), not by value. In classic C++, exceptions could be thrown by value, which would result in a call to the copy constructor for the exception object. In C++/CLI, exceptions are always on the managed heap, never the stack. Therefore, you must use a handle when throwing a C++/CLI exception, as in Listing 10-1. Listing 10-1. Throwing an Exception try { bool error; // other code if (error) { throw gcnew Exception(); } } Hogenson_705-2C10.fm Page 259 Thursday, October 19, 2006 8:04 AM 260 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION catch( Exception^ exception) { // code to handle the exception } The Exception Hierarchy All .NET exceptions inherit from a single root class, System::Exception. Table 10-1 shows some of the common exceptions thrown by the runtime in C++/CLI code. What’s in an Exception? A .NET Framework exception contains useful information that captures information about what triggered the exception and how to address the problem. For example, the exception source is stored as a string in the Source property; a text representation of the call stack is included in the form of the StackTrace property; and there’s a Message property, which contains a message suitable for display to a user. It’s use is demonstrated in Listing 10-2. Table 10-1. Some Common .NET Framework Exceptions Exception Condition System::AccessViolationException Thrown when an attempt to read or write protected memory occurs. System::ArgumentException Thrown when an argument to a method is not valid. System::ArithmeticException Thrown when an error occurs in an arithmetic expression or numeric casting operation. This is a base class for DivideByZeroException, NotFiniteNumberException, and OverflowException. System::DivideByZeroException Thrown when division by zero occurs. System::IndexOutOfRangeException Thrown when an array access out of bounds occurs. System::InvalidCastException Thrown when a cast fails. System::NullReferenceException Thrown when a null handle is dereferenced or used to access a nonexistent object. System::OutOfMemory Thrown when memory allocation with gcnew fails. System::TypeInitializationException Thrown when an exception occurs in a static constructor but isn’t caught. Hogenson_705-2C10.fm Page 260 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION 261 Listing 10-2. Using the Properties of the Exception Class // exception_properties.cpp using namespace System; int main() { try { bool error = true; // other code if (error) { throw gcnew Exception("XYZ"); } } catch( Exception^ exception) { Console::WriteLine("Exception Source property {0}", exception->Source); Console::WriteLine("Exception StackTrace property {0}", exception->StackTrace); Console::WriteLine("Exception Message property {0}", exception->Message); } } The output of Listing 10-2 is as follows: Exception Source property exception_properties Exception StackTrace property at main() Exception Message property XYZ When an unhandled exception occurs in a console application, the Message and StackTrace data are printed to the standard error stream, like this: Unhandled Exception: System.Exception: XYZ at main() There’s also a property of the Exception class called InnerException, which may reference an exception that gives rise to the exception we’re looking at. In this way, a cascading series of exceptions may be nested one within the other. This could be useful if an exception occurs deep down in low-level code, but there are several layers of libraries between the problem and the code that knows how to handle such situations. As a designer of one of the intermediate libraries, you could choose to wrap that lower exception as an inner exception and throw a higher exception of a type that is more intelligible to your clients. By passing the inner exception, Hogenson_705-2C10.fm Page 261 Thursday, October 19, 2006 8:04 AM 262 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION the inner exception information can be used by the error-handling code to respond more appropriately to the real cause of the error. Creating Exception Classes You will often want to create your own exception classes specific to particular error conditions; however, you should avoid doing this and use one of the standard Exception classes, if possible. Writing your own exception class lets you filter on and write exception handlers specific to that error. To do this, you may derive from System::Exception. You would normally override the Message property in the Exception base class to deliver a more relevant error message (see Listing 10-3). Listing 10-3. Creating a Custom Exception // exceptions_custom.cpp using namespace System; ref class MyException : Exception { public: virtual property String^ Message { String^ get() override { return "You must supply a command-line argument."; } } }; int main(array<String^>^ args) { try { if (args->Length < 1) { throw gcnew MyException(); } throw gcnew Exception(); } // The first catch blocks are the specific exceptions that // you are looking for. catch (MyException^ e) { Console::WriteLine("MyException occurred! " + e->Message); } Hogenson_705-2C10.fm Page 262 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION 263 // You may also catch other exceptions with multiple try blocks, // although it's better. catch (Exception^ exception) { Console::WriteLine("Unknown exception!"); } } The output of Listing 10-3 (with no command-line arguments) is shown here: MyException occurred! You must supply a command-line argument. Using the Finally Block C++/CLI recognizes the finally contextual keyword, which is a feature of other languages that support exception handling such as Java and C#. The finally keyword precedes a block of code known as a finally block. Finally blocks appear after catch blocks and execute whether or not an exception is caught. Use a finally block (see Listing 10-4) to put any cleanup code that you don’t want to duplicate in both the try block and the catch blocks. The syntax is like that in other languages. Listing 10-4. Using a Finally Block try { // . } catch( Exception^ ) { } finally { Console::WriteLine("finally block!"); } In the case of multiple finally blocks, they are executed “from the inside out” as demonstrated in Listing 10-5. Listing 10-5. Using Multiple Finally Blocks // multiple_finally_blocks.cpp using namespace System; Hogenson_705-2C10.fm Page 263 Thursday, October 19, 2006 8:04 AM 264 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION int main() { try { Console::WriteLine("Outer try"); try { Console::WriteLine("Inner try"); throw gcnew Exception("XYZ"); } catch( Exception^ exception) { Console::WriteLine("Inner catch"); } finally { Console::WriteLine("Inner finally"); } } catch(Exception^ exception) { Console::WriteLine("Outer catch"); } finally { Console::WriteLine("Outer finally"); } } Here is the output of Listing 10-5: Outer try Inner try Inner catch Inner finally Outer finally The first finally block to execute is the one paired with the last try block to execute. The finally block is a separate scope from the try block, so, for example, any variables declared in the try block aren’t available in the finally block. Also, if you created any stack objects, their destructors would be called at the end of the try block and before the finally block executes. Don’t try to use jump statements (e.g., continue, break, or goto) to move into or out of a finally block; it is not allowed. Also, you cannot use the return statement from inside a finally block. If allowed, these constructs would corrupt the stack and return value semantics. Hogenson_705-2C10.fm Page 264 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION 265 Dealing with Exceptions in Constructors A difficult problem in any language is what to do with objects that fail to be constructed prop- erly. When an exception is thrown in a constructor, the result is a partially constructed object. This is not a largely theoretical concern; it’s almost always possible for an exception to be thrown in a constructor. For example, OutOfMemoryException could be thrown during any memory alloca- tion. The finalizer will run on such partially constructed objects. In C++, destructors do not run on partially constructed objects. The finalizer is called by the runtime to clean up before the runtime reclaims the memory. As usual, the execution of the finalizer is nondeterministic, so it won’t necessarily happen right away, but will happen eventually. This is another reason to write finalizers carefully, without assuming any objects are valid. In Listing 10-6, an exception is thrown in the construction of a member of A in A’s constructor. The finalizer is called to clean up; the destructor is not called. Listing 10-6. Throwing an Exception in a Constructor // exceptions_ctor.cpp using namespace System; // the type of the member ref class Class1 { public: Class1() { // Assume a fatal problem has occurred here. throw gcnew Exception(); } }; ref class A { Class1^ c1; Class1^ c2; public: A() { // c1 has a problem; it throws an exception. c1 = gcnew Class1(); Hogenson_705-2C10.fm Page 265 Thursday, October 19, 2006 8:04 AM 266 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION // c2 never gets created. c2 = gcnew Class1(); } void F() { Console::WriteLine("Testing F"); } ~A() // Never gets called, even if A is created with stack semantics { Console::WriteLine("A::~A()"); } !A() // Gets called for partially constructed object { Console::WriteLine("A::!A()"); // Don't try to use C2 here without checking for null first. } }; int main() { A a; a.F(); // never reached } This example shows what happens in the simple case of a class without a base class other than Object. In the case where some base classes have already been initialized, the finalizers for any base classes will also execute. Throwing Nonexception Types C++/CLI allows you to throw objects that are not in the exception class hierarchy. If you’ve done a lot of programming in C# or Visual Basic .NET, this may be somewhat of a surprise, since in those languages, you are limited to throwing exception objects that derive, directly or indirectly, from System::Exception. In C++/CLI, you’re not limited in this way. However, if you are calling C++/CLI code from C# or VB .NET code, and an exception object of an unusual type is thrown, it will be wrapped in an exception from the point of view of the C# or VB .NET code. The basic idea is simple, as Listing 10-7 shows. Listing 10-7. Throwing an Object That’s Not an Exception // throw_string.cpp using namespace System; public ref class R { Hogenson_705-2C10.fm Page 266 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION 267 public: static void TrySomething() { throw gcnew String("Error that throws string!"); } }; int main() { try { R::TrySomething(); } catch(String^ s) { Console::WriteLine(s); } } The subtlety arises when you run this C++/CLI code from another language. If the code in Listing 10-7 is compiled to a DLL assembly and reference in C#, and you call the R::TrySomething method, a RuntimeWrappedException object is created. Note that cross-language work is best done in the Visual Studio IDE, so you can be sure that the right references, assembly signing, and manifests are all done properly. Create two projects in the same solution (see Listing 10-8). Set the C# project as the startup project, and configure the C++/CLI project as a DLL. Reference the C++/CLI project from the C# project, and build. Listing 10-8. Wrapping a Nonexception Object // runtimewrappedexception.cs using System; using System.Collections.Generic; using System.Text; using System.Runtime.CompilerServices; class Program { static void Main(string[] args) { try { R.TrySomething(); } Hogenson_705-2C10.fm Page 267 Thursday, October 19, 2006 8:04 AM 268 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION catch (RuntimeWrappedException e) { String s = (String)e.WrappedException; Console.WriteLine(e.Message); Console.WriteLine(s); } } } The output of Listing 10-8 is as follows: An object that does not derive from System.Exception has been wrapped in a RuntimeWrappedException. Error that throws string! I do not recommend throwing nonexception objects. Throwing exception objects that all derive from the root of the same exception hierarchy has the advantage in that a catch filter that takes the Exception class will capture all exceptions. If you throw objects that don’t fit this scheme, they will pass through those filters. There may be times when that behavior is desired, but most of the time you are introducing the possibility that your nonstandard exception will be erroneously missed, which would have undesired consequences. (The paradox is that a non-Exception exception is an exception to the rule that all exceptions derive from Exception. You can see how confusing it could be.) Unsupported Features Exception specifications are a C++ feature that allow a programmer to declare what exceptions a particular function can throw. This is intended as a heads-up to users of a function that they should be prepared to deal with these exceptions. Exception specifications are not supported in Visual C++ even in native code, and C++/CLI does not support this feature either. In general, this feature is impractical because it is not usually feasible to list the complete set of exceptions that a given block of code might generate, most particularly exceptions that propagate from any function called that doesn’t have exception specifications. Furthermore, some common exceptions, such as OutOfMemoryException, could be generated almost anywhere. Should these be included in all exception specifications? Another problem is performance, since this feature adds to the already intensive runtime overhead associated with exception handling. For all these reasons, the designers of the CLI chose not to implement this feature. Exception-Handling Best Practices Exception handling is controversial. All aspects of exception handling, it seems, are up for debate. Regardless of what your position is, one thing remains certain: if your framework uses exceptions, you, too, must use exceptions. For CLI types, there is no option not to use exception handling. However, you must use it sensibly and with restraint. Exceptions should not be used in normal flow control, because they do incur a significant performance penalty when thrown and caught. Exceptions should be used for truly exceptional conditions, errors that would not be expected from normal, correct program functioning. Hogenson_705-2C10.fm Page 268 Thursday, October 19, 2006 8:04 AM [...]... file name and reflect over it for each (String^ s in args) { Console::WriteLine( "Reflection on {0}", s); r->LoadAndReflect(s); } } 281 Hogenson_705-2C10.fm Page 282 Thursday, October 19, 2006 8:04 AM 282 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION The output of Listing 10-17, when reflection_ general.cpp is compiled with /clr:safe, is as follows: C:\code \reflection> reflection_general reflection_ general.exe... Brad Abrams (Addison-Wesley, 2005) Exceptions and Errors from Native Code When dealing with an application that includes native code and managed code, you will be dealing with potentially many different types of error codes and exceptions In addition to C++/CLI exceptions, you will have C++ exceptions, COM and Win32 error codes, and possibly structured exceptions, which are a Microsoft-specific extension... namespace System; using namespace System: :Reflection; // a class to reflect upon ref class Reflector { public: Hogenson_705-2C10.fm Page 281 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION // Load an assembly, and print out the methods in the types in the // assembly, invoking the specified method on the specified type void LoadAndReflect(String^ assemblyFileName)... named parameters [ Owner(DevOwner="John Smith") ] ref class C2 { // etc }; Hogenson_705-2C10.fm Page 279 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION Or, you could have both the constructor and the public properties and allow both methods The named parameters don’t have to be properties; public fields work just as well The AttributeUsageAttribute is an attribute...Hogenson_705-2C10.fm Page 269 Thursday, October 19, 2006 8:04 AM CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION Here are some best practices for handling exceptions: Avoid unnecessary proliferation of exception types If an appropriate NET Framework standard exception exists, then it should be used For example, if you are reporting invalid arguments to a function,... serialization and deserialization of a simple class The attributes are very simple, and because they take no arguments, the parentheses may be omitted Only the presence or absence of the attribute makes a difference; there is no “internal structure” to these, the simplest of attributes 275 Hogenson_705-2C10.fm Page 276 Thursday, October 19, 2006 8:04 AM 276 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION. .. EXCEPTIONS, ATTRIBUTES, AND REFLECTION // Get the type Type^ t= assembly->GetType(typeName); // Get the method of interest MethodInfo^ method = t->GetMethod(methodName); // Create an instance of the object Object^ obj = Activator::CreateInstance(t); // Invoke the method method->Invoke(obj, parameterList); } }; int main() { Reflector r ; // Pass the assembly file name, class name, and method name, and. .. previous section, you could load an assembly and have it start in a new app domain instead of the default app domain You can load many assemblies into these app domains You can “run an assembly” and have its main method executed To do all this, 283 Hogenson_705-2C10.fm Page 284 Thursday, October 19, 2006 8:04 AM 284 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION you use the AppDomain class You can... class and reviewed exception-handling best practices You then looked at the syntax for applying attributes to various targets, examined the Attribute class, and learned about some useful CLI attributes, including the Out parameter attribute, the Obsolete attribute, serialization attributes, and so on You also saw how to define your own attributes Finally, you examined reflection, the NET Framework... to filter on that exception and respond uniquely to it Throw and catch specific exceptions, not the System::Exception class at the root hierarchy Also, catch blocks should be ordered so that you catch the most specific exceptions first, followed by more general exceptions If you do both of these things, you can write code that knows how to handle the specific exceptions and be sure that it is called . 259 ■ ■ ■ CHAPTER 10 Exceptions, Attributes, and Reflection I n this chapter, you’ll begin by looking at aspects of exception handling in C++/CLI that. 19, 2006 8:04 AM 260 CHAPTER 10 ■ EXCEPTIONS, ATTRIBUTES, AND REFLECTION catch( Exception^ exception) { // code to handle the exception } The Exception

Ngày đăng: 05/10/2013, 07:20

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan