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

C# Bible - Jeff Ferguson, Brian Patterson, Jason Beres

.pdf
Скачиваний:
64
Добавлен:
24.05.2014
Размер:
4.21 Mб
Скачать

{

Console.WriteLine("The CLR is out of memory.");

}

}

}

The code in Listing 16-1 tries to allocate an array of two billion integers. Because one integer takes four bytes of memory, eight billion bytes are needed to hold an array of this size. Chances are good that this amount of memory is not available on your machine, and the allocation will fail. The code surrounds the allocation in a try block and also defines a catch clock to handle any OutOfMemoryException exceptions thrown by the CLR.

Note The code in Listing 16-1 does not list an identifier for the exception in the catch block. This syntax - in which you specify the class of an exception but do not give it a name - is legal. It works well when you want to catch a class of exceptions but do not need any information from the specific exception object itself.

StackOverflowException

The CLR throws the StackOverflowException exception when it runs out of stack space. The CLR manages a data structure called a stack, which keeps track of the methods that were called and the order in which they were called. The CLR has a finite amount of stack space available, and if it gets full, the exception is thrown Listing 16-2 shows the StackOverflowException exception.

Listing 16-2: StackOverflowException Exception

using System;

class MainClass

{

public static void Main()

{

try

{

Recursive();

}

catch(StackOverflowException)

{

Console.WriteLine("The CLR is out of stack space.");

}

}

public static void Recursive()

{

Recursive();

}

}

The code in Listing 16-2 implements a method called Recursive(), which calls itself before returning. This method is called by the Main() method and eventually causes the CLR to run

out of stack space because the Recursive() method never returns. The Main() method calls Recursive(), which in turn calls Recursive(), which again calls Recursive(), and so on. Eventually, the CLR runs out of stack space and throws the StackOverflowException exception.

NullReferenceException

In this example, the compiler catches an attempt to dereference a null object. Listing 16-3 shows the NullReferenceException exception.

Listing 16-3: NullReferenceException Exception

using System;

class MyClass

{

public int Value;

}

class MainClass

{

public static void Main()

{

try

{

MyObject = new MyClass(); MyObject = null;

MyObject.Value = 123;

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(NullReferenceException)

{

Console.WriteLine("Cannot reference a null object.");

// wait for user to acknowledge the results Console.Read();

}

}

}

The code in Listing 16-3 declares an object variable of type MyClass and sets the variable to null. (If you do not use the new statement, but just declares an object variable of type MyClass, the compiler will issue the following error when compiled, "Use of unassigned local variable MyObject.") It then tries to work with the object's public Value field, which is illegal because null objects cannot be referenced. The CLR catches this error and throws the NullReferenceException exception.

TypeInitializationException

The CLR throws the TypeInitializationException exception when a class defines a static constructor and the constructor throws an exception. If there are no catch blocks in the constructor to catch the exception, the CLR throws a TypeInitializationException exception.

InvalidCastExpression

The CLR throws the InvalidCastExpression exception if an explicit conversion fails. This situation can occur in interface situations. Listing 16-4 shows an InvalidCastExpression exception.

Listing 16-4: InvalidCastException Exception

using System;

class MainClass

{

public static void Main()

{

try

{

MainClass

MyObject = new MainClass();

IFormattable

Formattable;

Formattable = (IFormattable)MyObject;

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(InvalidCastException)

{

Console.WriteLine("MyObject does not implement the IFormattable interface.");

// wait for user to acknowledge the results Console.Read();

}

}

}

The code in Listing 16-4 uses a cast operator to try to obtain a reference to a .NET interface called IFormattable. Because the MainClass class does not implement the IFormattable interface, the cast operation fails and the CLR throws the InvalidCastException exception.

ArrayTypeMismatchException

The CLR throws the ArrayTypeMismatchException exception when the code attempts to store an element in an array whose type does not match the type of the array.

IndexOutOfRangeException

The CLR throws the IndexOutOfRangeException exception when the code attempts to store an element in an array using an element index that is out of the bounds of the array. Listing 16-5 illustrates the IndexOutOfRangeException exception.

Listing 16-5: IndexOutOfRangeException Exception

using System;

class MainClass

{

public static void Main()

{

try

{

int [] IntegerArray = new int [5];

IntegerArray[10] = 123;

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(IndexOutOfRangeException)

{

Console.WriteLine("An invalid element index access was attempted.");

// wait for user to acknowledge the results Console.Read();

}

}

}

The code in Listing 16-5 creates an array with five elements and then tries to set a value in array element 10. Because an index of 10 is out of bounds for the integer array, the CLR throws the IndexOutOfRangeException exception.

DivideByZeroException

The CLR throws the DivideByZeroException exception when the code attempts to execute a mathematical operation that results in a division by zero.

OverflowException

The CLR throws the OverflowException exception when a mathematical operation guarded by the C# checked operator results in an overflow. Listing 16-6 shows the OverflowException exception.

Listing 16-6: OverflowException Exception

using System;

class MainClass

{

public static void Main()

{

try

{

checked

{

int Integer1; int Integer2; int Sum;

Integer1 = 2000000000;

Integer2 = 2000000000; Sum = Integer1 + Integer2;

}

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(OverflowException)

{

Console.WriteLine("A mathematical operation caused an overflow.");

// wait for user to acknowledge the results Console.Read();

}

}

}

The code in Listing 16-6 adds two integers, each having a value of two billion. The result, four billion, is assigned to a third integer. The problem is that the result of the addition is larger than the largest possible value that can be assigned to a C# integer, and a mathematical overflow exception is thrown.

Working with Your Own Exceptions

You can define your own exceptions and use them in your code just as you would an exception defined by the .NET Framework. This design consistency enables you to write catch blocks that work with any exception that can be thrown from any piece of code, whether that code is in the .NET Framework, in one of your own classes, or in an assembly that you execute at runtime.

Defining your own exceptions

The .NET Framework declares a class called System.Exception, which serves as the base class for all exceptions in the .NET Framework. The pre-defined common language runtime classes are derived from System. SystemException, which itself derives from System.Exception. The exceptions to this rule are the DivideByZeroException, NotFiniteNumberException, and OverflowException exceptions, which derive from a class called System.ArithmeticException, which itself derives from System.SystemException. Any

exception classes that you define must derive from System.ApplicationException, which also derives from System.Exception.

The System.Exception class contains four read-only properties that the code in catch blocks can use to get more information about the exception that was thrown:

The Message property contains a description of the reason for the exception.

The InnerException property contains the exception that caused the current exception to be thrown. This property may be null, which indicates that no inner exception is available. If the InnerException is not null, it refers to an exception object that was thrown, which caused the current exception to be thrown. It is possible for a catch block to catch one exception and throw a different one. In this case, the InnerException property would contain a reference to the original exception object caught by the catch block.

The StackTrace property contains a string that displays the stack of method calls that were underway when the exception was thrown. Eventually, this stack trace will trace all the way back to the CLR's original call to the application's Main() method.

The TargetSite property contains the method that has thrown the exception.

Some of these properties can be specified in one of the constructors for the System.Exception class:

public Exception(string message);

public Exception(string message, Exception innerException);

The exceptions that you define can call the base class's constructor in your constructor so that the properties can be set, as shown in the following code:

using System;

class MyException : ApplicationException

{

public MyException() : base("This is my exception message.")

{

}

}

This code defines a class called MyException, which derives from the ApplicationException class. Its constructor uses the base keyword to call the base class's constructor. The base class's Message property is set to This is my exception message.

Throwing your exceptions

You can throw your own exceptions using the C# throw keyword. The throw keyword must be followed by an expression that evaluates to an object of class System.Exception or a class derived from System.Exception.

Consider the code shown in Listing 16-7.

Listing 16-7: Throwing Your Own Exceptions

using System;

class MyException : ApplicationException

{

public MyException() : base("This is my exception message.")

{

}

}

class MainClass

{

public static void Main()

{

try

{

MainClass MyObject = new MainClass();

MyObject.ThrowException();

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(MyException CaughtException)

{

Console.WriteLine(CaughtException.Message);

// wait for user to acknowledge the results Console.Read();

}

}

public void ThrowException()

{

throw new MyException();

}

}

The code in Listing 16-7 declares a new class called MyException, which derives from the base class ApplicationException as defined by the .NET Framework.

The MainClass class contains a method called ThrowException, which throws a new object of type MyException. The method is called by the Main() method, which surrounds the call in a try block. The Main() method also contains a catch block, whose implementation outputs the exception's message to the console. Because the message was set when the MyException class object was constructed, it is available and can be printed. Compiling and running Listing 16-7 prints the following to the console:

This is my exception message.

Using exceptions in constructors and properties

A few constructs in C# contain code that can execute but can't return a value to indicate the success or failure of the code being executed. Class constructors and set property accessors are a prime example. Throwing exceptions is a great way to report errors from blocks of code like these.

In Chapter 9, you examined a class that implemented a point on a computer screen. The class had properties that represented the point's x and y coordinates, and the set accessors for the properties ensured that the value was in a valid range before it was actually stored. The problem with the code in Listing 9-1 is that there is no error reporting for situations in which the supplied value is out of range.

Listing 16-8 is an improvement over Listing 9-1 because it adds exception handling to report on coordinates that are out of range.

Listing 16-8: Throwing Exceptions from Property Accessors

using System;

public class CoordinateOutOfRangeException : ApplicationException

{

public CoordinateOutOfRangeException()

: base("The supplied coordinate is out of range.")

{

}

}

public class Point

{

private int XCoordinate; private int YCoordinate;

public int X

{

get

{

return XCoordinate;

}

set

{

if((value >= 0) && (value < 640)) XCoordinate = value;

else

throw new CoordinateOutOfRangeException();

}

}

public int Y

{

get

{

return YCoordinate;

}

set

{

if((value >= 0) && (value < 480)) YCoordinate = value;

else

throw new CoordinateOutOfRangeException();

}

}

public static void Main()

{

Point MyPoint = new Point();

try

{

MyPoint.X = 100;

MyPoint.Y = 200;

Console.WriteLine("({0}, {1})", MyPoint.X, MyPoint.Y); MyPoint.X = 1500;

MyPoint.Y = 600;

Console.WriteLine("({0}, {1})", MyPoint.X, MyPoint.Y);

// wait for user to acknowledge the results Console.WriteLine("Hit Enter to terminate..."); Console.Read();

}

catch(CoordinateOutOfRangeException CaughtException)

{

Console.WriteLine(CaughtException.Message);

// wait for user to acknowledge the results Console.Read();

}

catch

{

Console.WriteLine("An unexpected exception was caught.");

// wait for user to acknowledge the results Console.Read();

}

}

}

The code in Listing 16-8 checks the value in the set property accessors to ensure that the supplied value is in a valid range. If it is not, an exception is thrown. The first point assignment is successful, as the values are both in the allowable range. The second point assignment, however, is not successful, as the x coordinate is out of range. This out-of-range value causes an object of class CoordinateOutOfRangeException to be thrown.

Compiling and running Listing 16-8 writes the following out to the console:

(100, 200)

The supplied coordinate is out of range.

Summary

The .NET Framework uses exceptions to report a variety of errors to .NET applications. The C# language fully supports exception processing and enables you to define your own exceptions in addition to working with the exceptions defined by the .NET Framework. Exceptions can be thrown and caught by C# code. C# code can also catch exceptions thrown by the .NET Framework.

The advantage to using exceptions is that you don't need to check every single method call for an error. You can enclose a group of method calls in a try block, and you can write your code as if every method call in the block were successful. This makes the code in your try block

much cleaner, because it does not need any inline error checking. Any exceptions thrown from code in your try block are dealt with in a catch block.

Chapter 17: Working with Attributes

In This Chapter

Previous chapters looked at keywords that define the behavior of a class and its members. The public, private, protected, and internal keywords, for example, define the accessibility of the declaration to other classes in your code. These modifiers are implemented by predefined keywords whose meanings are built into the C# language and cannot be changed.

C# also enables you to improve your class and class member declarations with information that is interpreted by other C# classes at runtime. This information is specified using a construct called an attribute. Attributes enable you to include directives in your class and its members. The behavior of the attribute is defined by either code that you write or code that is provided by the .NET Framework. Attributes enable you to extend the C# language by writing attribute classes that enhance the behavior of other classes when the code executes, although you write your attribute implementation class before other users apply the attribute to their own classes.

When you compile your applications, the attribute information that you added is emitted to the metadata of the assembly, enabling other applications or tools to view the attribute usage. Using the IL Disassembler (ILDASM) or the classes in the System.Reflection namespace, you can easily see which attributes have been added to sections of code, and you can determine whether they are useful. In C#, two types of attributes can be used: those that are built into the language, and custom attributes that you create. In the first section of this chapter, you learn how to use attributes and examine some of the built-in attributes that C# offers. In the second section, you learn how to write custom attributes and how your application or other applications can take advantage of those attributes.

Introducing Attributes

C# allows attributes to appear as a prefix on the following C# language constructs:

Classes

Class members, including constants, fields, methods, properties, events, indexers, operator overloads, constructors, destructors

Structures

Interfaces

Interface members, including methods, properties, events, and indexers

Enumerations and enumeration members

Delegates

You specify an attribute in your code by using its name and placing it in square brackets. The attribute specification must appear before the declaration to which the attribute should apply. Simple attributes may look like the following:

Соседние файлы в предмете Программирование