• No results found

Object fields and Free

Using Free rather than Destroy directly is particularly important in the case of object fields that are themselves object

references. Imagine, for example, a class called TConvertor that has a TFileStream field called FOutputStream. This sub-object

is then created in TConvertor’s constructor, and destroyed in its destructor: type

TConvertor = class strict private

FOutputStream: TFileStream; public

constructor Create(const AOutputFileName: string); destructor Destroy; override;

//other methods... end;

constructor TConvertor.Create(const AOutputFileName: string); begin

inherited Create;

FOutputStream := TFileStream.Create(AOutputFileName, fmCreate); end;

destructor TConvertor.Destroy; begin

FOutputStream.Destroy; //!!!should use FOutputStream.Free inherited Destroy;

end;

Say the TFileStream constructor raises an exception — for example, maybe the output file is currently open by another

application that has blocked writes by anything else. In such a case, • TFileStream.Create will abort and call TFileStream.Destroy automatically.

• FOutputStream won’t be assigned, remaining nil (the fields of a class instance are automatically zero-initialised).

• Given the exception hasn’t been caught by TConvertor.Create, the construction of the TConvertor object will itself be

aborted, causing TConvertor.Destroy to be called automatically...

• ... at which point FOutputStream.Destroy will raise another exception, because FOutputStream remains nil.

Simply replacing the call to FOutputStream.Destroy with FOutputStream.Free will avoid this eventuality.

FreeAndNil

One small step beyond Free is FreeAndNil. Unlike Free, this isn’t a method, but a freestanding procedure declared by the System.SysUtils unit. In use, you pass it an object reference. Internally, the procedure then assigns the reference to a

temporary local variable, nil’s the parameter, and calls Free on the temporary (the indirection is to cover the case of a

destructor that raises an exception). Because of some technicalities sounding parameters, the compiler will allow you to pass any sort of variable to FreeAndNil. Nonetheless, be careful to only ever pass an object reference:

uses System.SysUtils; var Int: Integer; Obj: TStringBuilder; begin Obj := TStringBuilder.Create; FreeAndNil(Obj); //OK

FreeAndNil(Int); //this unfortunately compiles too! end.

For such a simple routine, FreeAndNil has caused quite some controversy amongst experienced Delphi users. The gist of

the case against is that actually needing to use it implies you’re reusing the same object reference for different objects, and doing such a thing strongly suggests bad design. On the other hand, nil’ing old object references does, by definition,

make it easy to check when objects have been destroyed — just test for nil. This can be useful for debugging purposes

regardless of whether you intend to reuse object references or not. Quite simply, ‘access violations’ caused by nil object references are easy to identify, whereas the potentially random and possibly intermittent errors caused by a stale object reference are not.

Events

An ‘event’ in a Delphi context usually means an event property. This acts as a hook for assigning a method of another object, which then gets invoked when the event occurs. For example, the TButton class has an OnClick event, which allows

arbitrary code to be run when the button is clicked.

What makes a property an event property is that it is typed to either a method pointer or a method reference type; conventionally, event properties also have an On- prefix, and are read/write. If you are writing a custom component to

be installed into the IDE, they should also be given published visibility so that the user can assign them at design time via

the ‘Events’ tab of the Object Inspector. Method pointers are similar to regular procedural pointers, which we looked at in chapter 1, only with their instances being assignable to methods rather than standalone routines (hence their name), and their declarations having an of object suffix. For example, to declare an event type for events that take parameterless procedures for their handlers, you can use the following: type

TMyEvent = procedure of object;

The identifier used — here, TMyEvent — is arbitrary. Usually events have procedural method types; if there is a value to

be returned, it is declared as a var or out parameter: type

TMyCallback = procedure (var Cancel: Boolean) of object;

Nonetheless, functions are possible too. For instance, here’s a type for an event that takes a method with a string

parameter and a Boolean result: type

TMyOtherEvent = function (const S: string): Boolean of object;

A common convention is to have a Sender parameter that denotes the object whose event it is. Since most events don’t

have any special parameters beyond that, this leads to the TNotifyEvent type (declared in System.Classes) being used a lot: type

TNotifyEvent = procedure (Sender: TObject) of object;

The point of making the Sender parameter loosely typed is so the same handler can be used for different types of object.

For example, both a menu item and a button might share the same OnClick handler.

Once you have your event type, you can define the event property itself: type TMyComp = class(TComponent) strict private FOnChange: TNotifyEvent; //... published

property OnChange: TNotifyEvent read FOnChange write FOnChange; end;

In the case of objects only ever created at runtime, public visibility can be used; in the case of a custom component, published makes the event appear in the Object Inspector at design-time.

Once a class has declared an event property, it then needs to ‘raise’ the event at an appropriate point. This is done by checking whether the property (or its backing field) is assigned, before invoking the value as if it were a method itself:

if Assigned(FOnChange) then FOnChange(Self);

In the consuming code, ‘handling’ the event means assigning the event property with a method of the right signature (both the method and parameter names are immaterial). For a component being set up in the IDE, this can be done very easily via the Object Inspector. Nonetheless, it is hardly any more difficult in code: type TMyForm = class(TForm) //... strict private FMyComp: TMyComp;

procedure MyCompChanged(Sender: TObject); end;

procedure TMyForm.MyCompChanged(Sender: TObject); begin

ShowMessage('MyComp has changed!'); end;

procedure TMyForm.FormCreate(Sender: TObject); begin

FMyComp := TMyComp.Create(Self); //assign the OnChange event... FMyComp.OnChange := MyCompChanged; end; Frequently, events are assigned to ordinary ‘instance methods’ as above, in which the instance in question is either of a class or a record. However, they may also be assigned to ‘class’ methods, in which the ‘instance’ is a class type: type TConsumer = class

class procedure CompChange(Sender: TObject); end; var Comp: TMyComp; begin Comp := TMyComp.Create(nil); Comp.OnChange := TConsumer.CompChange; This only works with true class methods though; their ‘static’ brethren (which is the only sort records support) cannot be used as event handlers: type TConsumer2 = class

class procedure CompChange(Sender: TObject); static; end;

TConsumerRec = record

class procedure CompChange(Sender: TObject); static; end;

var

Comp: TMyComp; begin

Comp := TMyComp.Create(nil);

Comp.OnChange := TConsumer2.CompChange; //compiler error Comp.OnChange := TConsumerRec.CompChange; //ditto

The reason is that static methods aren’t really ‘methods’ at all internally, just standalone routines that are scoped to a certain type.

Related documents