The Delphi Language, Part 2

In this second half of the chapter (Delphi for .NET Developer's Guide, Sams, ISBN: 0-672-32443-1, 2004), author Xavier Pacheco talks about how Delphi enables you to pass parameters by value or by reference to functions and procedures, provides an explanation of units, using Delphi objects and more. (Here's the link to part 1 for reference.)

Contributed by
Rating: 4 stars4 stars4 stars4 stars4 stars / 21
July 13, 2004
Rate this Article:
MEH MEH++


SEARCH ASP FREE
TOOLS YOU CAN USE

advertisement

delphiProcedures and Functions

As a programmer, you should already be familiar with the basics of procedures and functions. A procedure is a discrete program part that performs some particular task when it's called and then returns to the calling part of your code. A function works the same except that a function returns a value after its exit to the calling part of the program.

Listing 5.1 demonstrates a short Delphi program with a procedure and a function.

Listing 5.1 An Example of a Function and a Procedure

1:  program FuncProc;
2:
3:  {$APPTYPE CONSOLE}
4:
5:  procedure BiggerThanTen(I: Integer);
6:  { writes something to the screen if I is greater than 10  }
7:  begin
8:    if I > 10 then
9:    writeln('Funky.');
10: end;
11:
12: function IsPositive(I: Integer): Boolean;
13: { Returns True if I is 0 or positive, False if I is negative }
14: begin
15:   Result := I >= 0;
16: end;
17:
18: var
19:   Num: Integer;
20: begin
21:   Num := 23;
22:   BiggerThanTen(Num);
23:   if IsPositive(Num) then
24:     writeln(Num, ' Is positive.')
25:   else
26:   writeln(Num, ' Is negative.');
27: end.

Result - The local variable Result in the IsPositive() function deserves special attention. Every Delphi function has an implicit local variable called Result that contains the return value of the function. Note that unlike C#'s return statement, the function doesn't terminate as soon as a value is assigned to Result.

If you want to duplicate the behavior of C#'s return statement, you can call Exit immediately after assigning to Result. The Exit statement immediately exits the current routine.

You also can return a value from a function by assigning the name of a function to a value inside the function's code. This is standard Delphi syntax and a holdover from previous versions of Borland Pascal. If you choose to use the function name within the body, be careful to note that there is a huge difference between using the function name on the left side of an assignment operator and using it somewhere else in your code. If on the left, you are assigning the function return value. If somewhere else in your code, you are calling the function recursively!

Note that the implicit Result variable isn't allowed when the compiler's Extended Syntax option is disabled in the Project, Options, Compiler dialog box or when you're using the {$EXTENDEDSYNTAX OFF} (or {$X-}) directive.

Passing Parameters

Delphi enables you to pass parameters by value or by reference to functions and procedures. The parameters you pass can be of any basic or user-defined type or an open array. (Open arrays are discussed later in this chapter.) Parameters also can be constant if their values will not change in the procedure or function.

Value Parameters

Value parameters are the default mode of parameter passing. When a parameter is passed by value, it means that a local copy of that variable is created, and the function or procedure operates on the copy. Consider the following example:

procedure Foo(I: Integer);

When you call a procedure in this way, a copy of Integer I will be made, and Foo() will operate on the local copy of I. This means that you can change the value of I without having any effect on the variable passed into Foo().

Reference Parameters

Delphi enables you to pass variables to functions and procedures by reference; parameters passed by reference are also called variable parameters. Passing by reference means that the function or procedure receiving the variable can modify the value of that variable. To pass a variable by reference, use the keyword var in the procedure's or function's parameter list:

procedure ChangeMe(var x: longint); 
begin
 
x := 2; { x is now changed in the calling procedure }
end;

Instead of making a copy of x, the var keyword causes the address of the parameter to be copied so that its value can be directly modified.

Using var parameters is equivalent to passing variables by reference in C# using the ref keyword or in Visual Basic .NET using the ByRef directive.

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.

Out Parameters, Constant Parameters, and Open Array Parameters

 

Like var parameters, out parameters provide a means for a routine to return a value to the caller in the form of a parameter. However, although var parameters must be initialized to a valid value prior to calling the routine, out parameters make no assumption about the validity of the incoming parameter. For reference types, this means that the reference will be completely discarded on entering the routine.

procedure ReturnMe(out O: TObject);
begin
  O := SomeObject.Create;
end;

Here's a rule of thumb: Use var for in/out parameters and out for out only parameters.

Constant Parameters

If you don't want the value of a parameter passed into a function to change, you can declare it with the const keyword. Here's an example of a procedure declaration that receives a constant string parameter:

procedure Goon(const s: string);

Open Array Parameters

Open array parameters provide you with the capability for passing a variable number of arguments to functions and procedures. You can either declare open arrays of some homogenous type or const arrays of differing types. The following code declares a function that accepts an open array of integers:

function AddEmUp(A: array of Integer): Integer;

You can pass to open array routines variables, constants, or expressions of any array type (dynamic, static, or incoming open). The following code demonstrates this by calling AddEmUp() and passing a variety of different elements:

var
  I, Rez: Integer;
const
  J = 23;
begin
  I := 8;
  Rez := AddEmUp([I, I + 50, J, 89]);

You can also directly pass an array to an open array routine as shown here:

var 
  A: array of integer;
begin
  SetLength(A, 10);
  for i := Low(A) to High(A) do
    A[i] := i; 
  Rez := AddEmUpConst(A);

In order to work with an open array inside the function or procedure, you can use the High(), Low(), and Length() functions to obtain information about the array. To illustrate this, the following code shows an implementation of the AddEmUp() function that returns the sum of all the numbers passed in A:

function AddEmUp(A: array of Integer): Integer;
var
  i: Integer;
begin
  Result := 0;
  for i := Low(A) to High(A) do
    inc(Result, A[i]);
end;

The Delphi language also supports an array of const, which allows you to pass heterogeneous data types in an array to a routine. The syntax for defining a routine that accepts an array of const is as follows:

procedure WhatHaveIGot(A: array of const);

You could call the preceding procedure with the following syntax:

WhatHaveIGot(['Tabasco', 90, 5.6, 3.14159, True, 's']);

The compiler passes each element of the array as a System.Object, so it can easily be handled as such in the receiving function. As an example of how to work with array of const, the following implementation for WhatHaveIGot() iterates through the array and shows a message to the user indicating what type of data was passed in which index:

procedure WhatHaveIGot(A: array of const);
var
  i: Integer;
begin
  for i := Low(A) to High(A) do
    WriteLn('Index ', I, ': ', A[i].GetType.FullName);
  ReadLn;
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.

Scope and Units and Namespaces

Scope refers to some part of your program in which a given function or variable is visible to the compiler. A global constant is in scope at all points in your program, for example, whereas a variable local to some procedure only has scope within that procedure. Consider Listing 5.2.

Listing 5.2 An Illustration of Scope

1:  program Foo;
2:
3:  {$APPTYPE CONSOLE}
4:
5:  const
6:    SomeConstant = 100;
7:
8:  var
9:    SomeGlobal: Integer;
10:   D: Double;
11:
12: procedure SomeProc;
13: var
14:   D, LocalD: Double;
15: begin
16:   LocalD := 10.0;
17:   D := D - LocalD;
18: end;
19:
20: begin
21:   SomeGlobal := SomeConstant;
22:   D := 4.593;
23:   SomeProc;
24: end.

SomeConstant, SomeGlobal, and D have global scope—their values are known to the compiler at all points within the program. Procedure SomeProc() has two variables in which the scope is local to that procedure: D and LocalD. If you try to access LocalD outside of SomeProc(), the compiler displays an unknown identifier error. If you access D within SomeProc(), you'll be referring to the local version; but if you access D outside that procedure, you'll be referring to the global version.

Units and Namespaces

Units are the individual source code modules that make up a Delphi program. A unit is a place for you to group functions and procedures that can be called from your main program. To be a unit, a source module must consist of at least three parts:

  • A unit statement—Every unit must have as its first noncomment, nonwhitespace line a statement saying that it's a unit and identifying the unit name. The name of the unit must always match the filename, excluding the file extension. For example, if you have a file named FooBar.pas, the statement would be
    unit FooBar;
  • The interface part—After the unit statement, a unit's next functional line of code should be the interface statement. Everything following this statement, up to the implementation statement, are the types, constants, variables, procedures, and functions that you want to make available to your main program and to other units. Only declarations—never routine bodies—can appear in the interface. The interface statement should be one word on one line:
    interface
  • The implementation part—This follows the interface part of the unit. Although the implementation part of the unit contains primarily procedures and functions, it's also where you declare any types, constants, and variables that you don't want to make available outside of this unit. The implementation part is where you must define any functions or procedures that you declared in the interface part. The implementation statement should be one word on one line:
    implementation

Optionally, a unit can also include two other parts:

  • An initialization part—This portion of the unit, which is located near the end of the file, contains any initialization code for the unit. This code will be executed before the main program begins execution, and it executes only once.

  • A finalization part—This portion of the unit, which is located in between the initialization and end. of the unit, contains any cleanup code that executes when the program terminates. The finalization section was introduced to the language in Delphi 2.0. In Delphi 1.0, unit finalization was accomplished by adding a new exit procedure using the AddExitProc() function. If you're porting an application from Delphi 1.0, you should move your exit procedures into the finalization part of your units.

A unit also implies a namespace. A namespace provides a logical, hierarchical means to organize an application or library. Dot notation can be used to create nested namespaces to prevent name collisions. It's common practice to nest namespaces three levels: company.product.area; for example, Borland.Delphi.System or Borland.Vcl.Controls.


Note - When several units have initialization/ finalization code, execution of each section proceeds in the order in which the units are encountered by the compiler (the first unit in the program's uses clause, then the first unit in that unit's uses clause, and so on). Also, it's a bad idea to write initialization and finalization code that relies on such ordering because one small change to the uses clause can cause some difficult-to-find bugs!


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.

The uses Clause and Circular Unit References

The uses clause is where you list the namespaces that you want to include in a particular program or unit. For example, if you have a program called FooProg that uses functions and types in two namespaces, UnitA and UnitB, the proper uses declaration is as follows:

program FooProg;

uses UnitA, UnitB;

Units can have two uses clauses: one in the interface section and one in the implementation section.

Here's code for a sample unit:

unit FooBar;

interface

uses BarFoo;

  { public declarations here }

implementation

uses BarFly; 

{ private declarations here }
{definition of routines declared in interface section}

initialization
  { unit initialization here }
finalization
  { unit clean-up here }
end.


Note - The uses clause might have the fully qualified namespaces, or Delphi permits using just the innermost namespace name in the uses clause for backward compatibility (for example, Controls) using the namespace prefixes control of the Tools, Options, Delphi Options, Library dialog.


Circular Unit References

Occasionally, you'll have a situation in which UnitA uses UnitB and UnitB uses UnitA. This is called a circular unit reference. The occurrence of a circular unit reference is often an indication of a design flaw in your application; you should avoid structuring your program with a circular reference. The optimal solution is often to move a piece of data that both UnitA and UnitB need to use out to a third unit. However, as with most things, sometimes you just can't avoid the circular unit reference. Note that circular references in both the interface or implementation section are illegal. Therefore, in such a case, move one of the uses clauses to the implementation part of your unit and leave the other one in the interface part. This usually solves the problem.

Packages and Assemblies

Delphi packages enable you to place portions of your application into separate modules, which can be shared across multiple applications as .NET assemblies.

Packages and Assemblies are described in detail in Chapter 6, "Assemblies: Libraries and Packages."

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.

Object-Oriented Programming

Volumes have been written on the subject of object-oriented programming (OOP). Often, OOP seems more like a religion than a programming methodology, spawning arguments about its merits (or lack thereof) that are passionate and spirited enough to make the Crusades look like a slight disagreement. We're not orthodox OOPists, and we're not going to get involved in the relative merits of OOP; we just want to give you the lowdown on a fundamental principle on which Delphi's language is based.

OOP is a programming paradigm that uses discrete objects—containing both data and code—as application building blocks. Although the OOP paradigm doesn't necessarily lend itself to easier-to-write code, the result of using OOP traditionally has been easy-to-maintain code. Having objects' data and code together simplifies the process of hunting down bugs, fixing them with minimal effect on other objects, and improving your program one part at a time. Traditionally, an OOP language contains implementations of at least three OOP concepts:

  • Encapsulation—Deals with combining related data fields and hiding the implementation details. The advantages of encapsulation include modularity and isolation of code from other code.

  • Inheritance—The capability to create new objects that maintain the properties and behavior of ancestor objects. This concept enables you to create object hierarchies such as VCL—first creating generic objects and then creating more specific descendants of those objects that have more narrow functionality.

    The advantage of inheritance is the sharing of common code. Figure 5.1 presents an example of inheritance—how one root object, fruit, is the ancestor object of all fruits, including the melon. The melon is ancestor of all melons, including the watermelon. You get the picture.


Figure 5.1
An illustration of inheritance.

  • Polymorphism—Literally, polymorphism means "many shapes." Calls to virtual methods of an object variable will call code appropriate to whatever instance is actually in the variable at runtime.

Note that the .NET CLR does not support the concept of C++-style multiple inheritance, and neither does the Delphi language.

You should understand the following three terms before you continue to explore the concept of objects:

  • Field—Also called field definitions or instance variables, fields are data variables contained within objects. A field in an object is just like a field in a Delphi record. In C#, fields sometimes are referred to as data members.

  • Method—The name for procedures and functions belonging to an object. Methods are called member functions in C#.

  • Property—An entity that acts as an accessor to the data and code contained within an object. Properties work similar to fields with attached logic to insulate the end user from the implementation details of an object.


Note - It's generally considered bad OOP style to access an object's fields directly because the implementation details of the object might change. Instead, use accessor properties, which allow a standard object interface without becoming embroiled in the details of how the objects are implemented. Properties are explained in the "Properties" section later in this chapter.


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.

Using Delphi Objects

Using Delphi Objects 

As mentioned earlier, classes are entities that can contain both data and code. Objects are created instances of those classes. Delphi classes provide you with all the power of object-oriented programming in offering full support of inheritance, encapsulation, and polymorphism.

Declaration and Instantiation

Of course, before using an object, you must have defined it using the class keyword. As described earlier in this chapter, classes are declared in the type section of a unit or program:

type
  TFooObject = class;

In addition to a class declaration, you usually also will have a variable of that class type, or instance, declared in the var section:

var
 
FooObject: TFooObject;

You create an instance of an object in Delphi by calling one of its constructors. A constructor is responsible for creating an instance of your object and allocating any memory or initializing any fields necessary so that the object is in a usable state upon exiting the constructor. Delphi objects always have at least one constructor called Create()—although it's possible for an object to have more than one constructor. Depending on the type of object, Create() can take different numbers of parameters. This chapter focuses on the simple case in which Create() takes no parameters.

Object constructors in the Delphi language aren't called automatically, and it's incumbent on the programmer to call the object constructor. The syntax for calling a constructor is as follows:

FooObject := TFooObject.Create;

Notice that the syntax for a constructor call is a bit unique. You're referencing the Create() constructor of the class by the type rather than the instance, as you would with other methods. This might seem odd at first, but it does make sense. FooObject, a variable, is undefined at the time of the call, but the code for TFooObject, a type, is static in memory. A static call to its Create() constructor is therefore totally valid.

The act of calling a constructor to create an instance of an object is often called instantiation.


Note - When an object instance is created using the constructor, the CLR will ensure that every field in your object is zero-initialized. You can safely assume that all numbers will be initialized to 0, all objects to nil, all Boolean values to False, and all strings will be empty.


Destruction

All .NET classes inherit a method called Finalize(), which can be overridden to perform any necessary class cleanup. Finalize() is called automatically for each instance by the .NET garbage collector. Note however, that there is no guarantee as to when Finalize() will actually be called or even that it will execute in its entirety in certain circumstances. For these reasons, it is not recommended that critical or limited resources—such as large memory buffers, database connections, or operating system handles—be released in the Finalize() method. Instead, Delphi developers should use the Disposable Pattern by overriding the destructor Destroy() of an object to free valuable resources. How to do this is discussed in Chapter 9, "Memory Management and Garbage Collection."

The Adam of Objects

You might be asking yourself how all these methods got into your little object. You certainly didn't declare them yourself, right? Right. The methods just discussed actually come from .NET's base System.Object class. Delphi's TObject class is an alias for System. Object. In .NET, all objects are always descendants of TObject regardless of whether they're declared as such. Therefore, the declaration

type
  TFoo = class
end;

is equivalent to the declaration

type
  TFoo = class(TObject)
end;

Fields

Adding fields to a class is accomplished with syntax very similar to declaring variables in a var block. For example, the following code adds an Integer, string, and Double to class TFoo:

type
  TFoo = class(TObject)
    I: Integer;
    S: string;
    D: Double;
end;

The Delphi language also supports static fields—that is, fields whose data is shared among all instances of a given class. This is accomplished by adding one or more class var blocks to a class declaration. To illustrate, the following code adds three static fields to the TFoo class:

type
  TFoo = class(TObject)
  I: Integer;
  S: string;
  D: Double;
  class var
    I_Static: Integer;
    S_Static: string;
    D_Static: Double;
end;

Note that it is also legal (although syntactically unnecessary) to have a var block in a class definition that defines normal fields.

The class var block is identical in function to the static keyword in C#. Note that a class var or var block is terminated by any of the following elements:

  • Another class var or var block

  • A property declaration

  • Any method declaration

  • A visibility specifier

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.

Methods

Methods are procedures and functions belonging to a given object: They give an object behavior rather than just data. Two important methods of the objects you create are the constructor and the destructor methods, which we just covered. You can also create custom methods in your objects to perform a variety of tasks.

Creating a method is a two-step process. You first must declare the method in the object type declaration, and then you must define the method in the code. The following code demonstrates the process of declaring and defining a method:

type
  TBoogieNights = class
    Dance: Boolean;
    procedure DoTheHustle;
  end;

procedure TBoogieNights.DoTheHustle;
begin
  Dance := True;
end;

Note that when defining the method body, the fully qualified name—consisting of the class and method names—must be used. It's important also to note that the object's Dance field can be accessed directly from within the method.

Method Types

A class type's methods can be declared as normal, static, virtual, class static, class virtual, dynamic, or message. Consider the following example object:

TFoo = class
  procedure IAmNormal;
  class procedure IAmAClassMethod;
  class procedure IAmAStatic; static;
  procedure IAmAVirtual; virtual;
  class procedure IAmAVirtualClassMethod; virtual;
  procedure IAmADynamic; dynamic;
  procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;

Regular Methods

IAmNormal() is a regular method. This is the default method type, and it works similarly to a regular procedure or function call. The compiler knows the address of these methods, so when you call a static method, it's capable of statically linking that information into the executable. Static methods execute the fastest; however, they don't have the capability to be overridden to provide polymorphism.

Class Methods

IAmAClassMethod() is a special, Delphi-specific type of static method. Class methods may be called without an instance, and the implementation of class methods are shared among all instances of the given class. However, class methods have a special implicit and hidden Self parameter that is passed by the compiler that allows for the calling of polymorphic (virtual) class methods. Class methods can be plain or virtual. Nonclass or nonstatic elements may not be accessed from within a class method.

Static Methods

IAMStatic() is a true, .NET-compatible static method. Like static fields, the implementation of static methods are shared among all instances of the given class. As such, nonstatic elements may not be accessed from within a static method. No Self parameter is passed for static methods, meaning that nonstatic methods may not be called from static methods.

Virtual Methods

IAmAVirtual() is a virtual method. Virtual methods are called in the same way as static methods, but because virtual methods can be overridden, the compiler doesn't know the address of a particular virtual function when you call it in your code. The .NET JIT compiler, therefore, builds a Virtual Method Table (VMT) that provides a means to look up function addresses at runtime. All virtual method calls are dispatched at runtime through the VMT. An object's VMT contains all its ancestor's virtual methods as well as the ones it declares.

Dynamic Methods

IAmADynamic() is a dynamic method. The .NET compiler maps dynamic methods to virtual methods, unlike the Win32 compiler, which provides for a separate dynamic method dispatching mechanism.

Message Methods

IAmAMessage() is a message-handling method. The message directive creates a method that can respond to dynamically dispatched messages. The value after the message keyword dictates what message the method will respond to. In VCL, message methods are used to create an automatic response to Windows messages, and you generally don't call them directly.

Overriding Methods

Overriding a method is the Delphi language's implementation of the OOP concept of polymorphism. It enables you to change the behavior of a method from descendant to descendant. Delphi methods can be overridden only if they're first declared as virtual, dynamic, or message. To override a virtual or dynamic method, just use the override directive instead of virtual or dynamic in your descendant object type. To override a message method, the message directive must be repeated and the same message ID used in the ancestor class. For example, you could override the IAmAVirtual(), IAmADynamic(), and IAmAMessage() methods as shown here:

TFooChild = class(TFoo)
  procedure IAmAVirtual; override;
  procedure IAmADynamic; override;
  procedure IAmAMessage(var M: TMessage); message WM_SOMEMESSAGE;
end;

The override directive replaces the original method's entry in the VMT with the new method. If you had redeclared IAmAVirtual and IAmADynamic with the virtual or dynamic keyword instead of override, you would have created new methods rather than overriding the ancestor methods. This will normally result in a compiler warning unless you suppress the warning by adding the reintroduce directive (described shortly) to the method declaration. Also, if you attempt to override a normal method in a descendant type, the static method in the new class hides the method from users of the descendent class.

Method Overloading

Like regular procedures and functions, methods can be overloaded so that a class can contain multiple methods of the same name with differing parameter lists. Overloaded methods must be marked with the overload directive, although the use of the directive on the first instance of a method name in a class hierarchy is optional. The following code example shows a class containing three overloaded methods:

type
  TSomeClass = class
    procedure AMethod(I: Integer); overload;
    procedure AMethod(S: string); overload;
    procedure AMethod(D: Double); overload;
  end;

Reintroducing Method Names

Occasionally, you might want to add a method to one of your classes to replace a virtual method of the same name in an ancestor of your class. In this case, you don't want to override the ancestor method but instead obscure and completely supplant the base class method. If you simply add the method and compile, you'll see that the compiler will produce a warning explaining that the new method hides a method of the same name in a base class. To suppress this error, use the reintroduce directive on the method declaration in the descendant class. The following code example demonstrates proper use of the reintroduce directive:

type
  TSomeBase = class
    procedure Cooper; virtual;
  end;

  TSomeClass = class
    procedure Cooper; reintroduce;
  end;

Self

An implicit variable called Self is available within all object methods. Self is a reference to the class instance that was used to call the method. Self is passed by the compiler as a hidden parameter to all methods. Self is analogous to this in C# and Me in Visual Basic .NET.

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.

Class References and Properties

Although normal class variables hold a reference to an object, class references provide a means to hold a reference to a class type. Using a class reference, you call class or static methods of a class or create instances of the class. The following code illustrates the syntax for declaring a new class called SomeClass and a class reference type for SomeClass:

type
  SomeClass = class
    constructor Create; virtual;
    class procedure Foo;
  end;

  SomeClassRef = class of SomeClass;

Using these types, you could call the SomeClass.Foo() class method through the SomeClassRef class reference type like this:

var
  SCRef: SomeClassRef; 
begin
  SCRef := SomeClass;
  SCRef.Foo;

Similarly, an instance of SomeClass can be created from the class reference:

var
  SCRef: SomeClassRef;
  SC: SomeClass;
begin
  SCRef := SomeClass;
  SC := SCRef.Create;

Note that creating a class via a class reference requires that the class have at least one virtual constructor. Virtual constructors are a feature somewhat unique to the Delphi language, allowing classes to be created by class references, where the specific type of class is not known at compile time.

Properties

It might help to think of properties as special accessor fields that enable you to modify data and execute code contained within your class. For components, properties are those things that show up in the Object Inspector window when published. The following example illustrates a simplified Object with a property:

TMyObject = class
private
  SomeValue: Integer;
  procedure SetSomeValue(AValue: Integer);
public
  property Value: Integer read SomeValue write SetSomeValue;
end;

procedure TMyObject.SetSomeValue(AValue: Integer);
begin
  if SomeValue <> AValue then
    SomeValue := AValue;
end;

TMyObject is an object that contains the following: one field (an integer called SomeValue), one method (a procedure called SetSomeValue), and one property called Value. The sole purpose of the SetSomeValue procedure is to set the value of the SomeValue field. The Value property doesn't actually contain any data. Value is an accessor for the SomeValue field; when you ask Value what number it contains, it reads the value from SomeValue. When you attempt to set the value of the Value property, Value calls SetSomeValue to modify the value of SomeValue. This is useful for a few reasons: First, it allows the developer to create natural side effects of getting or setting a property (such as recalculating an equation or repainting a control). Second, it allows you to present the users of the class with a simple variable without burdening them with the class's implementation details. Finally, you can allow the users to override accessor methods in descendant classes for polymorphic behavior.

Like static fields and methods, the Delphi language also supports static properties using the class keyword. The following code shows a class with a static property that accesses a static field:

TMyClass = class(TObject)
  class var FValue: Integer;
  class procedure SetValue(Value: Integer); static;
  class property Value: Integer read FValue write SetValue;
end;

Note that static properties can employ only static fields and methods as getter and setters.

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.

Events

The Delphi language supports two different kinds of events: singleton and multicast.

Singleton events have been in the Delphi language since the beginning. They are declared as a property whose type is a procedure type with read and write accessors. Singleton events may have zero or one event listeners. The assignment operator is used to hook a listener to the event, and nil is assigned to remove the listener from the event. Listing 5.3 provides a demonstration of the declaration and use of a singleton event.

Listing 5.3 Singleton Event Demonstration

1: program singleevent;
2:
3: {$APPTYPE CONSOLE}
4:
5: type
6: TMyEvent = procedure (Sender: TObject; Msg: string) of object;
7:
8: TClassWithEvent = class
9: private
10: FAnEvent: TMyEvent;
11: public
12: procedure FireEvent;
13: property AnEvent: TMyEvent read FAnEvent write FAnEvent;
14: end;
15:
16: TListener = class
17: procedure EventHandler(Sender: TObject; Msg: string);
18: end;
19:
20: { TClassWithEvent }
21:
22: procedure TClassWithEvent.FireEvent;
23: begin
24: if Assigned(FAnEvent) then
25: FAnEvent(Self, '*singleton event*');
26: end;
27:
28: { TListener }
29:
30: procedure TListener.EventHandler(Sender: TObject; Msg: string);
31: begin
32: WriteLn('Event was fired. Message is: ', Msg);
33: end;
34:
35: var
36: L: TListener;
37: CWE: TClassWithEvent;
38: begin
39: L := TListener.Create; // create objects
40: CWE := TClassWithEvent.Create;
41: CWE.AnEvent := L.EventHandler; // assign event handler
42: CWE.FireEvent; // cause event to fire
43: CWE.AnEvent := nil; // disconnect event handler
44: ReadLn;n
45: end.

The output of the program shown in Listing 5.3 is

Event was fired. Message is: *singleton event*

Multicast events have been added to the language to support .NET's capability of having multiple listeners for a given event. A multicast event is a property whose type is a procedure type and requires both add and remove accessors. Multicast events can have any number of listeners. The Include() and Exclude() procedures are used to add and remove listeners from a multicast event.

Listing 5.4 provides an example of declaring and using a multicast event.

Listing 5.4 Multicast Event Demonstration

1: program multievent;
2:
3: {$APPTYPE CONSOLE}
4:
5: uses
6: SysUtils;
7:
8: type
9: TMyEvent = procedure (Sender: TObject; Msg: string) of object;
10:
11: TClassWithEvent = class
12: private
13: FAnEvent: TMyEvent;
14: public
15: procedure FireEvent;
16: property AnEvent: TMyEvent add FAnEvent remove FAnEvent;
17: end;
18:
19: TListener = class
20: procedure EventHandler(Sender: TObject; Msg: string);
21: end;
22:
23: { TClassWithEvent }
24:
25: procedure TClassWithEvent.FireEvent;
26: begin
27: if Assigned(FAnEvent) then
28: FAnEvent(Self, '*multicast event*');
29: end;
30:
31: { TListener }
32:
33: procedure TListener.EventHandler(Sender: TObject; Msg: string);
34: begin
35: WriteLn('Event was fired. Message is: ', Msg);
36: end;
37:
38: var
39: L1, L2: TListener;
40: CWE: TClassWithEvent;
41: begin
42: L1 := TListener.Create; // create objects
43: L2 := TListener.Create;
44: CWE := TClassWithEvent.Create;
45: Include(CWE.AnEvent, L1.EventHandler); // assign event handler
46: Include(CWE.AnEvent, L2.EventHandler); // assign event handler
47: CWE.FireEvent; // cause event to fire
48: Exclude(CWE.AnEvent, L1.EventHandler); // disconnect event handler
49: Exclude(CWE.AnEvent, L2.EventHandler); // disconnect event handler
50: ReadLn;
51: end

The output of the program shown in Listing 5.4 is

Event was fired. Message is: *multicast event* Event was fired. Message is: *multicast event*

Note that attempts to use Include() to add the same method more than once to the listener list will result in the method being called multiple times.

In order to maintain compatibility with other .NET CLR languages, the Delphi compiler will implement multicast semantics even for singleton events, creating add and remove accessors for the singleton event. In this implementation, the add() method will result in an overwrite of the existing value.

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.

Visibility Specifiers, Friend Classes and Class Helpers

Visibility Specifiers

The Delphi language offers you further control over the behavior of your objects by enabling you to declare fields and methods with directives such as private, strict private, protected, strict protected, public, and published. The syntax for using these directives is as follows:

TSomeObject = class
private
  APrivateVariable: Integer;
  AnotherPrivateVariable: Boolean;
strict private
  procedure AStrictPrivateMethod;
protected
  procedure AProtectedProcedure;
  function ProtectMe: Byte;
strict protected
  procedure AStrictProtectedMethod;
public
  constructor APublicContructor;
  destructor Destroy; override; // a public destructor
published 
  property AProperty: Integer
  read APrivateVariable write APrivateVariable;
end;

You can place as many fields or methods as you want under each directive. Style dictates that you should indent the specifier the same as you indent the classname. The meanings of these directives follow:

  • private—These parts of your object are accessible only to code in the same unit as your object's implementation. Use this directive to hide implementation details of your objects from users and to prevent users from directly modifying sensitive members of your object.

  • strict private—Members are accessible within the declaring class and not within the same unit. Used for more strict data encapsulation than private.

  • protected—Your object's protected members can be accessed by descendants of your object. This capability enables you to hide the implementation details of your object from users while still providing maximum flexibility to descendants of your object.

  • strict protected—Members are accessible only within the declaring class and ancestors and not within declaring units. Used for more strict data encapsulation than protected.

  • public—These fields and methods are accessible anywhere in your program. Object constructors and destructors always should be public.

  • published—Identical to public from a visibility standpoint. Published has the additional benefit of adding the [Browsable(true)] attribute to properties contained within, which causes them to be displayed in the Object Inspector when used in the form designer. Attributes are discussed later in this chapter.


Note - The meaning of published represents a fairly substantial departure from the Win32 implementation of the Delphi language. In Win32, Runtime Type Information (RTTI) was generated for published properties. The .NET equivalent of RTTI is Reflection; however, Reflection is possible on all class elements, regardless of visibility specifier.


Here, then, is code for the TMyObject class that was introduced earlier, with directives added to improve the integrity of the object:

TMyObject = class
private
  SomeValue: Integer;
  procedure SetSomeValue(AValue: Integer);
published
  property Value: Integer read SomeValue write SetSomeValue;
end;

procedure TMyObject.SetSomeValue(AValue: Integer);
begin
  if SomeValue <> AValue then
    SomeValue := AValue;
end;

Now, users of your object will not be able to modify the value of SomeValue directly, and they will have to go through the interface provided by the property Value to modify the object's data.

"Friend" Classes

The C++ language has a concept of friend classes (that is, classes that are allowed access to the private data and functions in other classes). This is accomplished in C++ using the friend keyword. .NET languages, such as Delphi and C#, have a similar concept, although the implementation is different. Non- strict, private, and protected members of a class are visible and accessible to other classes and code declared within the same unit namespace.

Class Helpers

Class helpers provide a means to extend a class without modifying the actual class. Instead, a new class, the helper, is created and its methods effectively bolted on to the original class. This enables users of the original class to call methods of the helper class as if they were methods of the original class.

The following code example creates a simple class and class helper and demonstrates calling a method on the helper class:

program Helpers;

{$APPTYPE CONSOLE}

type
  TFoo = class
    procedure AProc;
  end;

  TFooHelper = class helper for TFoo
    procedure AHelperProc;
  end;

{ TFoo }

procedure TFoo.AProc; 
begin 
  WriteLn('TFoo.AProc'); 
end;

{ TFooHelper }

procedure TFooHelper.AHelperProc;
begin
  WriteLn('TFooHelper.AHelperProc');
  AProc;
end;

var
  Foo: TFoo;
begin
  Foo := TFoo.Create;
  Foo.AHelperProc;
end.


Caution - Class helpers are an interesting feature, but not a feature that generally lends itself to good software design. This feature is in the language mostly because Borland needed it to hide the differences between standard .NET classes and similar classes in Win32 Delphi. The occasion should be rare when a good design involves the use of class helpers.


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.

Nested Types, Operator Overloading and Attributes

The Delphi language allows for a type clause to appear with a class declaration, effectively nesting types within a class. Such nested types are referenced using a NestedType.OuterType syntax, as shown in the following code example:

type
  OutClass = class
    procedure SomeProc;

    type
      InClass = class
        procedure SomeOtherProc;
      end;

  end;

  var
    IC: OutClass.InClass;

Operator Overloading

The Delphi language supports the overloading of select operators for classes and records. The syntax for overloading an operator is as straightforward as declaring a class method with a specific name and signature. A complete list of overloadable operators can be found in the Delphi online help under the Operator Overloads topic; however, the following code example demonstrates how you might overload the + and operators of a class:

  OverloadsOps = class
  private
    FField: Integer;
  public
    class operator Add(a, b: OverloadsOps): OverloadsOps;
    class operator Subtract(a, b: OverloadsOps):   OverloadsOps;
  end;

class operator OverloadsOps.Add(a, b: OverloadsOps): OverloadsOps;
begin
  Result := OverloadsOps.Create;
  Result.FField := a.FField + b.FField;
end;

class operator OverloadsOps.Subtract(a, b: OverloadsOps): OverloadsOps;
begin
  Result := OverloadsOps.Create;
  Result.FField := a.FField - b.FField;
end;

Note that the overloaded operators are declared as class operator and take the declaring class as parameters. Because the + and – operators are binary, they also return the declaring class.

Once declared, the operators can be used in a manner similar to that shown here:

var
O1, O2, O3: OverloadsOps;
begin
  O1 := OverloadsOps.Create;
  O2 := OverloadsOps.Create;
  O3 := O1 + O2;
end;

Attributes

One of the more exciting features of .NET platform is the realization of attribute-based development, which had been on the drawing board of a few different programming languages for the past several years. Attributes provide a means to tie metadata to language elements such as classes, properties, methods, variables, and so on that provide extended information about those elements to their consumers.

Attributes are declared using square bracket notation before the element to be augmented. For example, the following code demonstrates the use of the DllImport attribute, which signals to .NET that the method should be imported from the specified DLL:

[DllImport('user32.dll')]
function MessageBeep(uType : LongWord) : Boolean; external;

Attributes are used liberally in .NET. For example, the Browsable attribute on a property determines whether it should be displayed in the Object Inspector:

[Browsable(True)]
property Foo: string read FFoo write FFoo;

.NET's attribute system is quite extensible because attributes are implemented as classes. This allows you to extend the attribute system without limit because you can create your own attributes either from scratch or by inheriting from existing attribute classes and use these new attributes in other classes.

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.

Interfaces

The Delphi language contains native support for interfaces, which, simply put, define a set of functions and procedures that can be used to interact with an object. The definition of a given interface is known to both the implementer and the client of the interface—acting as a contract of sorts for how an interface will be defined and used. A class can implement multiple interfaces, providing multiple known "faces" by which a client can control an object.

As its name implies, an interface defines only, well, an interface by which object and clients communicate. It's the job of a class that supports an interface to implement each of the interface's functions and procedures.


Note - Unlike Win32 Delphi, .NET interfaces to not implicitly descend from IInterface or IUnknown. As such, they no longer implement QueryInterface(), _AddRef, or _Release(). Typecasting is now used for type identity, and reference counting is built in to the .NET platform.


Defining Interfaces

The syntax for defining an interface is very similar to that of a class. The primary difference is that an interface can optionally be associated with a globally unique identifier (GUID), which is unique to the interface. The following code defines a new interface called IFoo, which implements one method called F1():

type
  IFoo = interface
    function F1: Integer;
  end;

Note that GUIDs are not required for .NET interface definitions, but they are required for Win32. Therefore, the use of GUID is only recommended when you need to maintain a multiplatform code base or want to engage in .NET COM Interop to interoperate between .NET and COM.


Tip - The Delphi IDE will manufacture new GUIDs for your interfaces when you use the Ctrl+Shift+G key combination.


The following code defines a new interface, IBar, which descends from IFoo:

type
  IBar = interface(IFoo)
    function F2: Integer; 
  end;

Implementing Interfaces

The following bit of code demonstrates how to implement IFoo and IBar in a class called TFooBar:

type
  TFooBar = class(TObject, IFoo, IBar)
    function F1: Integer;
    function F2: Integer;
  end;

function TFooBar.F1: Integer; 
begin
  Result := 0;
end;

function TFooBar.F2: Integer;
begin
  Result := 0;
end;

Note that multiple interfaces can be listed after the ancestor class in the first line of the class declaration to implement multiple interfaces. The binding of an interface function to a particular function in the class happens when the compiler matches a method signature in the interface with a matching signature in the class. A compiler error will occur if a class declares that it implements an interface but the class fails to implement one or more of the interface's methods.


Interface Methods Made Easy - Let's face it, interfaces are great, but all of that typing to implement interface methods in a class can be a bummer! Here's an IDE trick to implement all the interface methods in just a few keystrokes and mouse clicks:

  1. Add the interfaces you want to implement to the class declaration.

  2. Place the cursor somewhere in the class and press the Ctrl+Spacebar keystroke combination to invoke code completion. The yet unimplemented methods show in red in the code completion window.

  3. Select all the red-colored methods in the list by holding down the Shift key and using the keyboard arrow keys or mouse.

  4. Press the Enter key, and the interface methods will be added automatically to the class definition.

  5. Press the Ctrl+Shift+C keystroke combination to complete the implementation portion of the new methods.

  6. Now all that is left to do is fill in the implementation portion of each method!


If a class implements multiple interfaces that have methods of the same signature, you must alias the same-named methods as shown in the following short example:

type
  IFoo = interface
    function F1: Integer;
  end;

  IBar = interface 
    function F1: Integer;
  end;

  TFooBar = class(TObject, IFoo, IBar)
    // aliased methods
    function IFoo.F1 = FooF1;
    function IBar.F1 = BarF1;
    // interface methods
    function FooF1: Integer;
    function BarF1: Integer;
  end;

function TFooBar.FooF1: Integer; 
begin 
  Result := 0; 
end;

function TFooBar.BarF1: Integer; 
begin 
  Result := 0; 
end;


Note - The implements directive from Win32 Delphi is not available in the current version of the .NET Delphi compiler.


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.

Using Interfaces, SEH

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.

Exception Classes

Exceptions are merely special instances of objects. These objects are instantiated when an exception occurs and are destroyed when an exception is handled. The base exception object is .NET's System.Exception class.

One of the more important elements of the Exception object is the Message property, which is a string that provides more information or explanation on the exception. The information provided by Message depends on the type of exception that's raised.


Caution - If you define your own exception object, make sure that you derive it from a known exception object such as Exception or one of its descendants. This is so that generic exception handlers will be able to trap your exception.


When you handle a specific type of exception in an except block, that handler also will catch any exceptions that are descendants of the specified exception. For example, System.ArithmeticException is the ancestor object for a variety of math-related exceptions, such as DivideByZeroException, NotFiniteNumberException, and OverflowException. You can catch any of these exceptions by setting up a handler for the base ArithmeticException class, as shown here:

try
  Statements
except
  on EMathError do // will catch EMathError or any descendant
    HandleException
end;

Any exceptions that you don't explicitly handle in your program eventually will continue to unwind the stack until handled. In a .NET Winform or Webform application, a default exception handler performs some work to display the error to the user. In VCL applications, the default handler will put up a message dialog box informing the user that an exception occurred.

When handling an exception, you sometimes need to access the instance of the exception object in order to retrieve more information on the exception, such as that provided by its Message property. There are two ways to do this: The preferable method is to use an optional identifier with the on SomeException construct. You can also use the ExceptObject() function, but this technique is not recommended and has been deprecated.

You can insert an optional identifier in the on ESomeException portion of an except block and have the identifier map to an instance of the currently raised exception. The syntax for this is to preface the exception type with an identifier and a colon, as follows:

try
  Something 
except
  on E:ESomeException do
    ShowMessage(E.Message);
end;

The identifier (E in this case) receives a reference to the currently raised exception. This identifier is always of the same type as the exception it prefaces.

The syntax for raising an exception is similar to the syntax for creating an object instance. To raise a user-defined exception called EBadStuff, for example, you would use this syntax:

raise EBadStuff.Create('Some bad stuff happened.');

Flow of Execution

After an exception is raised, the flow of execution of your program propagates up to the next exception handler until the exception instance is finally handled and destroyed. This process is determined by the call stack and therefore works program-wide (not just within one procedure or unit). Listing 5.7 is a VCL unit that illustrates the flow of execution of a program when an exception is raised. This listing is the main unit of a Delphi application that consists of one form with one button. When the button is clicked, the Button1Click() method calls Proc1(), which calls Proc2(), which in turn calls Proc3(). An exception is raised in Proc3(), and you can witness the flow of execution propagating through each try..finally block until the exception is finally handled inside Button1Click().


Tip - When you run this program from the Delphi IDE, you'll be able to see the flow of execution better if you disable the integrated debugger's handling of exceptions by unchecking Tools, Options, Debugger Options, Borland .NET Debugger, Language Exceptions, Stop on Language Exceptions.


Listing 5.7 Main Unit for the Exception Propagation Project

1:  unit Main;
2:
3:  interface
4:
5:  uses
6:    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
7:    Dialogs;
8:
9:    type
10:      TForm1 = class(TForm)
11:        Button1: TButton;
12:        procedure Button1Click(Sender: TObject);
13:   end;
14:
15: var
16:   Form1: TForm1;
17:
18: implementation
19:
20: {$R *.nfm}
21:
22: type
23:   EBadStuff = class(Exception);
24:
25: procedure Proc3;
26: begin
27:   try
28:     raise EBadStuff.Create('Up the stack we go!');
29:   finally
30:     ShowMessage('Exception raised. Proc3 sees the exception');
31:   end;
32: end;
33:
34: procedure Proc2;
35: begin
36:   try
37:     Proc3;
38:   finally
39:     ShowMessage('Proc2 sees the exception');
40:   end;
41: end;
42:
43: procedure Proc1;
44: begin
45:   try
46:     Proc2;
47:   finally
48:     ShowMessage('Proc1 sees the exception');
49:   end;
50: end;
51:
5
2: procedure TForm1.Button1Click(Sender: TObject);
53: const
54:   ExceptMsg = 'Exception handled in calling procedure. The message is "%s"';
55: begin
56:   ShowMessage('This method calls Proc1 which calls Proc2 which calls Proc3');
57:   try
58:     Proc1;
59:   except
60:     on E:EBadStuff do
61:       ShowMessage(Format(ExceptMsg, [E.Message]));
62:   end;
63: end;
64:
65: end

Reraising an Exception

When you need to perform special exception handling for a statement inside an existing try..except block and still need to allow the exception to flow to the block's outer default handler, you can use a technique called reraising the exception. Listing 5.8 demonstrates an example of reraising an exception.

Listing 5.8 Reraising an Exception

1:  try // this is outer block
2:    { statements }
3:    { statements }
4:    ( statements }
5:    try // this is the special inner block
6:      { some statement that may require special handling }
7:    except
8:      on ESomeException do
9:      begin
10:       { special handling for the inner block statement }
11:       raise; // reraise the exception to the outer block
12:     end;
13:   end;
14: except
15:   // outer block will always perform default handling
16:   on ESomeException do Something;
17: 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.

blog comments powered by Disqus
.NET ARTICLES

- .Net 4.5 Brings Changes
- Understanding Events in VB.NET
- Objects, Properties, Events and Methods in V...
- Install Visual Web Developer Express 2010
- Microsoft Gadgeteer an Open Source Alternati...
- Best DotNetNuke Modules
- Facebook Image Viewer in Visual Basic
- Murach`s ADO.NET 4 Database Programming with...
- 5 Must Have Visual Studio 2010 Extensions
- Dynamic Web Applications with ASP.NET Mono u...
- PDFSharp: HTML to PDF in ASP.NET 3.5 using V...
- Using the PDFSharp Library in ASP.NET 3.5 wi...
- Sending Email in ASP.NET 3.5 using VB.NET wi...
- ASP.NET 3.5 Role Based Security and User Aut...
- Creating ASP.NET Login Web Pages and Basic C...

ASP Web Hosting ASP.Net Web Hosting Windows Web Hosting
 
 
 

ASP Free Forums 
 RSS  Tutorials RSS
 RSS  Forums RSS
 RSS  All Feeds
Site Map 
Request Media Kit
Write For Us Get Paid 
Weekly Newsletter
 
Developer Updates  
Free Website Content 
Privacy Policy 
Support 


© 2003-2012 by Developer Shed. All rights reserved. DS Cluster 4 - Follow our Sitemap
Most Popular Topics
All ASP.Net Tutorials