1. Trang chủ
  2. » Công Nghệ Thông Tin

Exceptions, Attributes, and Reflection

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

Đ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

Thông tin cơ bản

Tiêu đề Exceptions, attributes, and reflection
Tác giả Hogenson
Thể loại Chapter
Năm xuất bản 2006
Định dạng
Số trang 26
Dung lượng 664,28 KB

Các công cụ chuyển đổi và chỉnh sửa cho tài liệu này

Nội dung

In classic C++, exceptions could be thrown by value, which would result in a call to the copy constructor for the exception object.. catch Exception^ exception{ // code to handle the ex

Trang 1

In 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

Trang 2

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

Table 10-1 Some Common NET Framework Exceptions

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

Trang 3

Listing 10-2 Using the Properties of the Exception Class

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,

Trang 4

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

// The first catch blocks are the specific exceptions that

// you are looking for

catch (MyException^ e)

{

Console::WriteLine("MyException occurred! " + e->Message);

}

Trang 5

// 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

Trang 7

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();

Trang 8

// c2 never gets created.

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

{

Trang 9

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,

Trang 10

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

Trang 11

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, you should throw ArgumentException, not an exception of your

own making It is appropriate to define your own exception when it is important 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 when those

specific errors occur

Catch only those exceptions that you can reasonably handle Any exceptions that your

code at this particular level in the application doesn’t know how to handle should be allowed

to propagate up the chain, rather than “recovering” from an exception and attempting to

continue when complete recovery isn’t possible This poor practice is known as swallowing

errors It’s usually better to bring down an application with an unhandled exception than

to continue in an unknown state

Put cleanup code in the finally block, rather than in the catch block The catch block is for

handling and recovering from the error, not cleaning up

When rethrowing exceptions in a catch block, use the throw statement without providing

the exception object This is interpreted correctly by the runtime as continuing the

propaga-tion of the same exceppropaga-tion, rather than starting a new exceppropaga-tion The complete call stack is

then preserved in the exception’s StackTrace property

Much more could be said about exception-handling best practices, and since exception

handling is common to many languages, guidance in one language often applies to all languages

I’ve only scratched the surface here There are many resources available to help use exceptions

properly See, for example, Framework Design Guidelines: Conventions, Idioms, and Patterns

for Reusable NET Libraries by Krzysztof Cwalina and 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 to C You also have to deal with

all the error codes and exceptions in libraries that you’re using Exceptions from native code

are wrapped in managed exceptions Also, error codes from COM (HRESULTs) are wrapped in

exceptions when they propagate to managed code While I cannot go into all the details behind

dealing with these diverse situations in this introductory text, you’ll learn some of the basics in

Chapter 12

Trang 12

Literally, metadata means “data about data.” Attributes represent metadata for the program

element to which it is applied Attributes may be applied to many program elements, including assemblies, classes, constructors, delegates, enumerated types, events, fields, interfaces, methods, portable executable file modules, parameters, properties, return values, structures, or even other attributes

In the context of this discussion, metadata means data that is not directly part of whatever

it is applied to In this sense, attributes allow information to be associated with program ties without affecting the internal structure of the entity For example, an attribute naming the author of a type is not really part of the internal structure of a type Attributes for program elements may be queried at runtime, so programs may make use of the metadata to manipulate objects

enti-The program element to which an attribute is applied is referred to as the attribute target.

How Attributes Work

Attributes are classes defined either in the NET Framework BCL or another library You can define your own attributes as well by creating a class that derives from another attribute The attribute class contains the data to be associated with a program element Attributes are then

applied to program elements using a syntax involving square brackets, called an attribute

speci-fication, as follows:

[ SomeAttribute(arguments) ]

If the attribute doesn’t take any arguments, you can omit the parentheses entirely.Where the attribute is placed in the code often determines the target to which the attribute applies When applied to an entity that takes modifiers, it precedes all modifiers

[ MethodAttribute(arguments) ]

public static int SomeMethod([ParameterAttribute] param1);

Multiple attributes may be specified in a single pair of square brackets or may appear in sequential square brackets

[ returnvalue: ReturnValueAttribute(arguments) ]

ReturnType^ GetValue();

If unspecified, the preceding attribute would be applied to the method, not the return value

Trang 13

The Attribute Class

All attributes inherit from the System::Attribute class, either directly or indirectly By

conven-tion, attribute classes have Attribute as a suffix The Attribute suffix may be omitted when

referring to the name of the attribute in an attribute specification

Attribute Parameters

Attribute constructors may take arguments These arguments are passed in to the attribute’s

class constructor when it is applied to a program element There are two ways to pass in the

constructor arguments: the usual way involving the order of the parameters, and a second way

in which the name of the parameter is given and the assignment operator is used to specify the

value The two methods are illustrated here:

[ SomeAttribute( "AttributeValue1", 200) ] // Positional parameters

[ SomeAttribute( Value = "AttributeValue1", IntegralValue = 200) ] // Named

parameters

Because attribute parameter evaluation occurs during startup, when the CLI

program-ming environment is not yet fully initialized, the language design imposes restrictions on the

types that may be used as attribute parameter types Attribute parameter types are restricted to

primitive types, string handles, object handles, handles to the Type class, enum classes that are

publicly accessible (and, if nested, are nested in a publicly accessible type) as well as

one-dimensional managed arrays of these types These restrictions are in place in order to ensure

that the runtime has access to the types when it needs them and that there are no

dependen-cies on additional external assemblies

Some Useful Attributes

The NET Framework contains many attributes Let’s look at a few of them

The Obsolete Attribute

The Obsolete attribute is one of the simplest of attributes Try compiling the code in Listing 10-9

Listing 10-9 Using the Obsolete Attribute

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

w