The Delphi Language, Part 2 - Using Interfaces, SEH
(Page 13 of 14 )
Using Interfaces
A few important language rules apply when you're using variables of interface types in your applications. Like other .NET types, interfaces are lifetime managed. The garbage collector will release an object when all reference to the object and its implemented interfaces have been released or go out of scope. Before use, an interface type is always initialized to nil. Manually setting an interface to nil releases the reference to its implementation object.
Another unique rule of interface variables is that an interface is assignment compatible with objects that implement the interface. However, this compatibility is only one way: You can assign an object reference to an interface reference, but not the other way around. For example, the following code is legal using the TFooBar class defined earlier:
procedure Test(FB: TFooBar)
var
F: IFoo;
begin
F := FB; // supported because FB supports IFoo
.
.
.
If FB did not support IFoo, the code would still compile, but the interface reference would be assigned nil. Any subsequent attempt to use that reference would result in NullReferencedException being raised at runtime.
Finally, the as typecast operator can be used to query a given interface variable for another interface on the same object. This is illustrated here:
var
FB: TFooBar;
F: IFoo;
B: IBar;
begin
FB := TFooBar.Create;
F := FB; // supported because FB supports IFoo
B := F as IBar; // cast for IBar
.
.
.
If the requested interface isn't supported, the expression returns nil.
Structured Exception Handling Structured exception handling (SEH) is a method centralizing and normalizing error handling, providing both non-invasive error handling within source code as well as the capability to gracefully handle nearly any kind of error condition. The Delphi language's SEH is mapped to that provided in the CLR by .NET.
Exceptions are, at the most basic level, merely classes that happen to contain information about the location and nature of a particular error. This makes exceptions as easy to implement and use in your applications as any other class.
.NET provides predefined exceptions for many dozens of program-error conditions, such as out of memory, divide by zero, numerical overflow and underflow, and file I/O errors. Borland provides even more exception classes within Delphi's RTL and VCL. And of course, there is nothing to prevent you from defining your own exception classes as you see fit in your applications.
Listing 5.5 demonstrates how to use exception handling during file I/O.
Listing 5.5 File I/O Using Exception Handling
1: program FileIO;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses System.IO;
6:
7: var
8: F: TextFile;
9: S: string;
10: begin
11: AssignFile(F, 'FOO.TXT');
12: try
13: Reset(F);
14: try
15: ReadLn(F, S);
16: WriteLn(S);
17: finally
18: CloseFile(F);
19: end;
20: except
21: on System.IO.IOException do
22: WriteLn('Error Accessing File!');
23: end;
24: ReadLn;
25: end
In Listing 5.5, the inner try..finally block is used to ensure that the file is closed regardless of whether any exceptions come down the pike. What this block means in English is "Hey, program, try to execute the statements between the try and the finally. Whether you finish them or run into an exception, execute the statements between the finally and the end in any case. If an exception does occur, move on to the next exception-handling block." This means that the file will be closed and the error can be properly handled no matter what error occurs.
Note - The statements after finally in a try..finally block execute regardless of whether an exception occurs. Make sure that the code in your finally block doesn't assume that an exception has occurred. Also, because the finally statement doesn't stop the migration of an exception, the flow of your program's execution will continue on to the next exception handler.
The outer try..except block is used to handle the exceptions as they occur in the program. After the file is closed in the finally block, the except block prints a message to the console informing the user that an I/O error occurred.
One of the key advantages that exception handling provides over more old school methods of check-the-function-return-value-error-handling is the capability of distinctly separating the error-detection code from the error-correction code. This is a good thing primarily because it makes your code easier to read and maintain by enabling you to concentrate on one distinct aspect of the code at a time.
The fact that you cannot trap any specific exception by using the try..finally block is significant. When you use a try..finally block in your code, it means that you don't care what exceptions might occur. You just want to perform some tasks when they do occur to gracefully get out of a tight spot. The finally block is an ideal place to free any resources you've allocated (such as files or Windows resources) because it will always execute in the case of an error. In many cases, however, you need some type of error handling that's capable of responding differently depending on the type of error that occurs. You can trap specific exceptions by using a try..except block, which is again illustrated in Listing 5.6.
Listing 5.6 A try..except Exception-Handling Block
1: program HandleIt;
2:
3: {$APPTYPE CONSOLE}
4:
5: var
6: D1, D2, D3: Double;
7: begin
8: try
9: Write('Enter a decimal number: ');
10: ReadLn(D1);
11: Write('Enter another decimal number: ');
12: ReadLn(D2);
13: Writeln('I will now divide the first number by the second...');
14: D3 := D1 / D2;
15: Writeln('The answer is: ', D3:5:2);
16: except
17: on System.OverflowException do
18: Writeln('Overflow in performing division!');
19: on System.DivideByZeroException do
20: Writeln('You cannot divide by zero!');
21: on Borland.Delphi.System.EInvalidInput do
22: Writeln('That is not a valid number!');
23: end;
24: end
Although you can trap specific exceptions with the try..except block, you also can catch other exceptions by adding the catchall else clause to this construct. The syntax of the try..except..else construct follows:
try
statements
except
On ESomeException do Something;
else
{ do some default exception handling }
end;
Caution - When using the try..except..else construct, you should be aware that the else part will catch all exceptions—even exceptions you might not expect, such as out-of-memory or other runtime-library exceptions. Be careful when using the else clause, and use it sparingly. You should always reraise the exception when you trap with unqualified exception handlers. This is explained in the section "Reraising an Exception."
You can achieve the same effect as a try..except..else construct by not specifying the exception class in a try..except block, as shown in this example:
try
Statements
except
HandleException // almost the same as else statement
end;
This chapter is from Delphi for .NET Developer's Guide, by Xavier Pacheco (Sams, 2004, ISBN: 0-672-32443-1). Check it out at your favorite bookstore today.
Buy this book now. |
Next: Exception Classes >>
More .NET Articles
More By Xavier Pacheco