Marco Cantù and I want to share with you that we are once again taking our Delphi Developer Days tour on the road this spring. This year, our two-day live Delphi event visits six cities with stops in Baltimore/Washington DC and Chicago in the United States, and London, Amsterdam, Frankfurt, and Rome in Europe. We start in London on March 26th and wrap up in Rome on May 18th.
This is our fourth year that we've toured together, and in case you have not attended a Delphi Developer Days event, I wanted to share with you what we are doing in hope that you will consider attending.
Marco and I are both long time trainers and authors. Combined we have nearly 40 years of experience training professional developers, and have published close to 40 books. In putting together Delphi Developer Days, we strive to create a perfect balance of formal training, sharing, and networking.
To begin with, we limit the number of attendees in each city to a maximum of 35 developers. By doing so we retain the intimacy of a small group while generating the necessary synergy for the sharing of ideas and perspective. Personally, it gives us the opportunity to interact with each attendee without getting lost in the crowd.
There is also a little bit of conference flavor to Delphi Developer Days. In each city we have a keynote address from an Embarcadero representative, and also have a guest speaker who presents on an area of their own specialization. Our guest speakers this year include Stephen Ball (London), Bob Swart (Amsterdam), Ray Konopka (Chicago), Jim McKeeth (Washington DC/Baltimore), Bruno Fierens (Frankfurt), and Daniele Teti (Rome).
But make no mistake about it, the main goal is covering Delphi in depth. Over the course of the two days, there are more than fifteen technical sessions covering everything from the latest features in Delphi XE2 to classic topics such as Unicode migration and RTTI (runtime type information). There are also talks on Internet-related development, mobile applications, cross platform development, and traditional client/server topics. In some of the talks Marco and I present together, offering our individual insights on the topic, while for other sessions, we break out into separate rooms.
For the combined sessions, we have chosen topics that should be of interest to all Delphi developers. This year's combined sessions include a comparison of Delphi database development strategies, an overview of options for Internet development, and the always popular Tips, Tricks, and Techniques session. For the break out sessions, we have worked to create attractive options for creating your own agenda. For example, when a Delphi XE2-specific topic is being presented in one room, a classic topic that applies to many versions of Delphi is being covered in the other room.
Of course, this means that you might have to choose between two equally attractive topics. But we have that covered. Both Marco and I are prolific writers, and we commit to writing a paper for every talk we present. This means that if you have to skip a particular talk you will be able to read the associated paper. Over the past three years, the course book that each attendee to Delphi Developer Days receives has been several hundred pages in length, containing as much material as a book, and this year will be no different.
Delphi Developer Days takes a lot of planning, but it wouldn't be possible without our many sponsors. Both of last year's platinum sponsors, Embarcadero Technologies and Sybase (an SAP Company) have returned this year, as have all of last year's gold sponsors: /n software, DevArt, Gnostice Technologies, Programmer's Paradise, RemObjects Software, and TMS Software. In addition, this year we welcome two new gold sponsors: Fast Reports and Sisulizer. And of course, we value our more than 20 regular sponsors and Delphi user groups for their strong and continued support.
We are currently offering a ten percent discount early bird special if you register and make payment the deadline listed online. We also offer additional discounts for previous attendees of the Delphi Developer Days 2009 through 2011 tours. There is also a group discount when 3 or more employees of the same company register together. But don't wait too long to register. Our early registrations are on a record pace. Make sure you sign up now to guarantee your spot at Delphi Developer Days 2012.
For more information about Delphi Developer Days, including dates, topics, locations, and pricing, please visit http://www.delphideveloperdays.com/.
Technical discussions related to software development. Particular attention is paid to Delphi development. Also expect a healthy dose of database-related content, including SQL, data modeling, and general database design.
Monday, January 23, 2012
Thursday, January 12, 2012
What’s Your Favorite LiveBindings Example?
LiveBindings, which were introduced in Delphi XE2, provide developers with new options for associating objects. And they are only one of a wealth of new features introduced in this groundbreaking version of Delphi. They are also a source of some confusion.
One of the problems is that most of the demonstrations of LiveBindings are simple, in part because LiveBindings are so new. Another way to put this is that it's hard to think differently about object binding when we are so familiar with Delphi's existing mechanisms. As a result, most examples that I've seen so far duplicate much of what we already achieve in Delphi.
But this is bound to change. I believe that once we start to see creative applications of LiveBindings, we, the collective Delphi community, will begin to think about them differently.
I hope to jump start this process by collecting examples of LiveBindings that represent the way that we'll be using them in the future, and I'll publish these here. Of course, I'll give credit if you contribute so that you can bask in the gratitude of your fellow Delphi developers.
So, here is my question. Do you have examples of LiveBindings that go beyond the obvious? Alternatively, have you seen an example that breaks the mold? Is so, please share.
And, in case you haven't given much thought to LiveBindings, here is a short introduction.
At its core, LiveBindings is a mechanism for creating associations between objects and expressions. Expressions are strings that are evaluated by Delphi's new expression engine, and in the case of LiveBindings, they define what effect the expression will have on an object.
While expressions are strings, they are evaluated by the expression engine at runtime, which is quite a bit different than your Delphi code, which is compiled by Delphi's compiler at compile time. As a result, expressions are different from other string types you normally encounter in Delphi code. For one thing, expression strings can define literal values using either single quotes or double quotes. In addition, the expression engine recognizes special methods that have been registered with it through Delphi's Open Tools API (OTA), and can employ custom output converters to transform data from one type to another.
Another concept critical to LiveBindings is scope. In LiveBindings terminology, scope defines what is visible to the expression engine. Most LiveBindings require a control component (the object to which the expression will be applied), and in most cases a source component as well. In the case of these LiveBindings components, the control and source components are both in scope, making their properties available to the expression engine. Similarly, those custom methods that have been registered with Delphi from a design time package are also in scope, making those methods callable from your expressions.
It's worth noting that while LiveBindings use expressions, expressions can be used without LiveBindings. Specifically, you can create a scope programmatically, adding to it the objects and methods you want the expression engine to evaluate, and then ask the expression engine to perform the evaluation, returning a value based on the expression string. It's an important point, as far as the expression engine is concerned, but not something that you necessarily need to think about when you are using the LiveBindings components.
I like the analogy a lot, because it actually highlights why LiveBindings are a positive thing. Let's take the fishing pole example. A recent television show on The History Channel called "101 Gadgets that Have Changed the World," the publishers of the magazine Popular Mechanics list the top 101 devices that have had a dramatic impact on our daily lives. And, guess what, fiberglass fishing poles made the list (at 100), beating out duct tape and being edged out by the stapler.
In any case, the point is that while cane poles and fiberglass fishing rods perform the same task, they work differently, and fiberglass rods are functionally better on every level.
I think we are going to be saying the same thing about LiveBindings, once we get our heads around them. Yes, you can do many things with LiveBindings that can be achieved without them, but as we get more familiar with their capabilities, I believe we will discover a whole range of features that are enabled only through LiveBindings.
So, let me hear from you. Post a link to your example, or an example that you find on the Web, as a comment to this posting.
One of the problems is that most of the demonstrations of LiveBindings are simple, in part because LiveBindings are so new. Another way to put this is that it's hard to think differently about object binding when we are so familiar with Delphi's existing mechanisms. As a result, most examples that I've seen so far duplicate much of what we already achieve in Delphi.
But this is bound to change. I believe that once we start to see creative applications of LiveBindings, we, the collective Delphi community, will begin to think about them differently.
I hope to jump start this process by collecting examples of LiveBindings that represent the way that we'll be using them in the future, and I'll publish these here. Of course, I'll give credit if you contribute so that you can bask in the gratitude of your fellow Delphi developers.
So, here is my question. Do you have examples of LiveBindings that go beyond the obvious? Alternatively, have you seen an example that breaks the mold? Is so, please share.
And, in case you haven't given much thought to LiveBindings, here is a short introduction.
LiveBindings
LiveBindings is a general term for Delphi's new object/property binding mechanism first introduced in RAD Studio XE2. It is the only binding mechanism available to the new FireMonkey cross-platform component library (FMX), and is also available for traditional visual component library (VCL) components.At its core, LiveBindings is a mechanism for creating associations between objects and expressions. Expressions are strings that are evaluated by Delphi's new expression engine, and in the case of LiveBindings, they define what effect the expression will have on an object.
While expressions are strings, they are evaluated by the expression engine at runtime, which is quite a bit different than your Delphi code, which is compiled by Delphi's compiler at compile time. As a result, expressions are different from other string types you normally encounter in Delphi code. For one thing, expression strings can define literal values using either single quotes or double quotes. In addition, the expression engine recognizes special methods that have been registered with it through Delphi's Open Tools API (OTA), and can employ custom output converters to transform data from one type to another.
Another concept critical to LiveBindings is scope. In LiveBindings terminology, scope defines what is visible to the expression engine. Most LiveBindings require a control component (the object to which the expression will be applied), and in most cases a source component as well. In the case of these LiveBindings components, the control and source components are both in scope, making their properties available to the expression engine. Similarly, those custom methods that have been registered with Delphi from a design time package are also in scope, making those methods callable from your expressions.
It's worth noting that while LiveBindings use expressions, expressions can be used without LiveBindings. Specifically, you can create a scope programmatically, adding to it the objects and methods you want the expression engine to evaluate, and then ask the expression engine to perform the evaluation, returning a value based on the expression string. It's an important point, as far as the expression engine is concerned, but not something that you necessarily need to think about when you are using the LiveBindings components.
Do We Need LiveBindings?
I recently spoke about LiveBindings during the "24 Hours of Delphi" broadcast with David Intersimone. One of the listeners asked a question about LiveBindings that I hear pretty often, though he gave a somewhat new twist. "Why do we need LiveBindings?" he asked. "After all, it appears that LiveBindings is just another way of doing what we already do using event handlers. It kind of seems like fishing poles. In older days we had cane fishing poles, and they worked just fine. The new fiberglass and graphite rods are nice, but they don't really do more than the old rods."I like the analogy a lot, because it actually highlights why LiveBindings are a positive thing. Let's take the fishing pole example. A recent television show on The History Channel called "101 Gadgets that Have Changed the World," the publishers of the magazine Popular Mechanics list the top 101 devices that have had a dramatic impact on our daily lives. And, guess what, fiberglass fishing poles made the list (at 100), beating out duct tape and being edged out by the stapler.
In any case, the point is that while cane poles and fiberglass fishing rods perform the same task, they work differently, and fiberglass rods are functionally better on every level.
I think we are going to be saying the same thing about LiveBindings, once we get our heads around them. Yes, you can do many things with LiveBindings that can be achieved without them, but as we get more familiar with their capabilities, I believe we will discover a whole range of features that are enabled only through LiveBindings.
So, let me hear from you. Post a link to your example, or an example that you find on the Web, as a comment to this posting.
Monday, January 9, 2012
For The Record
Records are data structures that hold one or more pieces of information. While records have been around since the earliest days of Pascal, they have taken on a much larger role in the most recent versions of Delphi. Today's records possess many features previously only found in objects.
This post begins with a brief overview of records. It continues with a discussion of features that have been added to records since Delphi 2005. This post concludes with a look at several of the important new record types that have been introduced to Delphi in recent versions.
In an array the various values are identified using an ordinal index. Records, by comparison, make use of named fields. Furthermore, arrays consist of a collection of elements that are all of the same data type (even if all elements of the array are variants). In records, the individual fields can be almost any valid Delphi type, including integers, Booleans, real numbers, object references, arrays, interfaces, and enumerations.
Actually, it's not necessary to declare a record type in order to use a record. Instead, a variable can be defined as a record in its declaration. This might look something like the following:
In the TDog record type, Breed was declared as a field of type TBreed. An alternative would be to declare the Breed field to be an enumeration directly, without the TBreed declaration. Here is another record type declaration that uses this syntax:
In the TAppointment record type, the DayOfWeek field is an enumeration, which is defined in the record type declaration directly.
Another interesting feature of traditional records is the optional variant part. A variant part is a conditional portion of the record structure that can hold variable pieces of information. The variant part, if present, always appear at the end of a record declaration.
For example, consider the following declaration of Delphi's TRect, which appears in the Types unit.
Using this syntax, you can use one or the other variant record structures (four Longints or two TPoints, though you cannot mix them in an individual instance of a record). For example, the following code declares and assigns data to two TRect instances (which are identical):
There is a second syntax, in which the value of one of the record's fields determines which of the alternative structures the record uses. For example, consider the following record type declaration:
In this record, when the Boolean field MilitaryService is assigned the value True the WhichService, StartDate, and EndDate fields are available. If MilitaryService is False, a single enumerated field, Reason, can be used.
But Delphi 2006 is the version in which the really big changes were introduced, giving records many features previously reserved for classes, and greatly improving their utility in your Delphi applications. These features include methods, properties, constructors, class constants, class variables, class static methods, class properties, nested types, overloaded operators, and record helpers. Each of these features are consider in the following sections.
Record methods also permit overloading. When overloading a record's method, you follow the same rules that you follow when overloading the methods of a class. Specifically, the compiler must be able to distinguish between each overloaded version based on the signature of the method (including whether the method is a function or procedure).
Record properties are also similar to their class counterparts. A record's properties can be implemented with read and/or write parts, and can use either direct access (reading and writing from fields of the record) or accessor methods (implementing reading and writing through specified methods of the record. Accessor methods permit a property to perform side effects).
The following is a simple example of a record with two fields (variables), a method, and three properties.
The methods declared in a record must be implemented (records do not permit abstract methods). Furthermore, after declaring a method in a record you can use class completion (Ctrl-Shift-C) to generate the necessary implementation stubs for your record's methods. The following is the implementation of the GetArea method of the TRectangle record.
The following shows the TRectangle record with an overloaded constructor (note that the overload directive was not required, but makes the code clearer). The implementation of the constructor follows in this code segment.
A class variable is a field that is shared by all instances of a particular record type. Changing the value of a class variable has the effect of changing the one memory location shared by the record type and all instances of it.
Class constants appear within a const clause of a record declaration, and class variables appear within a class var clause. These clauses conclude at the end of the record declaration, or when a method, type, variable, property, constructor, visibility directive, or variant part is encountered.
The following record type includes two class constants and two class variables:
Class properties are properties that can be referenced using an instance of a record type, or the record type itself. While class properties support both direct access and accessor methods (like normal properties), direct access can only employ class variables, and accessor methods must use only class static methods.
The following is a portion of a record type declaration that includes two class variables, a class static method, and two class properties. The implementation of the class static method follows.
The following is another record class segment, which includes a simple nested type declaration.
There are almost 30 operators that can be overloaded. These operators are shown in Listing 1.
Listing 1 Record operators that can be overloaded
To overload one of these operators, you implement the specified method name using the class operator syntax (instead of function) in your record type definition. You must also define the type of value that the operator will return. For those operators that are declared to return a Boolean value, the operator must return a Boolean value. All other operators can return whatever type is appropriate for that operator. In other words, a non-Boolean operator can return a value other than the record type.
The following code segment includes a portion of the TRectangle class with an overloaded addition operator, the implementation of this operator overload, and an event handle that uses the operator.
While the use of class helpers is largely discouraged, record helpers can play a role that cannot be provided through any other means. Specifically, record helpers provide the only mechanism for extending an existing record type. Classes, on the other hand, support inheritance. As a result, it is often better to descend a new class from an existing class, and introduce new methods and properties in the descendant than to use a class helper.
Record helpers have the same limitations as class helpers. A given record can be supported by only one record helper. If there is more than one record helper for a given record, the record will employ the record helper that is closer in scope.
The following code shows the declaration of a record helper for the TRectangle record, along with the implementation of the single method declared within the record helper. Notice in the GetMaxArea implementation that Self refers to the record instance.
First of all, records are allocated on the stack, as long as they are not declared globally or managed using New and Dispose. Class instances, by comparison, are allocated on the heap.
This has a significant impact on memory management. Specifically, while classes that do not have an owner must be explicitly freed, memory allocated for record instances is automatically freed when the record goes out of scope merely by its data being removed from the stack. Of course, if your record creates class instances during its lifecycle, that memory must be managed by you or your record, or a memory leak may exist. This is made a bit more difficult since records do not support destructors.
Another significant difference between classes and records is that you do not need to call a record's constructor, while a class instance must be explicitly created. You can declare a variable of a record type and then immediately use it. In this case, Delphi will call the zero parameter constructor for you.
You are still free to call a record's constructor if you want. In fact, if you've declared one or more additional constructors, it is probably because you want to create your record in a particular fashion, and you can do so by calling one of those overloaded constructors.
Another major difference between records and classes is that records do not support inheritance. A record never descends from an existing record. Similarly, records cannot implement interfaces (though they may have fields or properties of interface type, but that is different). The bottom line here is that records do not support polymorphism. A corollary of this is that record methods are necessarily static. Records do not support virtual, dynamic, or abstract methods.
As you learned earlier, records can also include a variant part. A variant part permits a record to hold data that is specific to its nature, and may vary from record instance to record instance. All instances of a class have the same set of members, regardless of their data.
Finally, records are value types, as opposed to reference types. Consider the following code:
The TRectangle named b in this code segment is a completely new record that contains a copy of the data held within record a. By comparison, if TRectangle was a class type, following the assignment of variable a to variable b, variable b would point to the same memory address as a.
Finally, records support operator overloading, while classes do not. Operator overloading provides you with a rich tool for managing exactly what happens when records are used in expressions, and the results can get very interesting.
Delphi 2009 saw the introduction of the System.TMonitor type. TMonitor is a record that can be used to acquire and manage a lock on any TObject. It provides a convenient and powerful synchronization mechanism for multithreaded applications.
A major new layer of runtime type information (RTTI) was introduced in Delphi 2010. The principle type for working with the new RTTI, TRTTIContext, is a record type.
Additional records types introduced in Delphi 2010, and further enhanced in Delphi XE, include TFile, TPath, and TDirectory. These types, which are declared in the IOUtils unit, are record types.
Authors Note: I originally published this article in the SDN Magazine (issue 110), published by the Software Development Network on August 26, 2011. For information on the Software Development Network, visit http://www.sdn.nl/.
This post begins with a brief overview of records. It continues with a discussion of features that have been added to records since Delphi 2005. This post concludes with a look at several of the important new record types that have been introduced to Delphi in recent versions.
Record Basics
The traditional style of record definition is somewhat similar to an array, in that it can hold more than one data value. But there are a number of features that distinguish records from arrays.In an array the various values are identified using an ordinal index. Records, by comparison, make use of named fields. Furthermore, arrays consist of a collection of elements that are all of the same data type (even if all elements of the array are variants). In records, the individual fields can be almost any valid Delphi type, including integers, Booleans, real numbers, object references, arrays, interfaces, and enumerations.
Declaring a Record
Consider the following type declarations. Together, these types define a record type, TDog, that includes four fields: A double, an enumeration, a TDateTime, and a TObject reference.type TPerson = class(TObject) private FName: String; public property Name: String read FName write FName; constructor Create(Name: String); end; TBreed = (Akita, Beagle, Chihuahua, Dachshund); TDog = record Weight: Double; Breed: TBreed; Born: TDatetime; Owner: TPerson; end;
Using a Record
Once you have defined a record type, it is easy to create an instance of that record. Simply declare a variable of that record type and use it. It is not even necessary to allocate memory for the record — Delphi does that for you. For example, the following code demonstrates how to declare and use a record of type TDog.procedure TForm1.Button2Click(Sender: TObject); var Dog: TDog; begin Dog.Name := 'Skippy'; Dog.Breed := Beagle; Dog.Born := StrToDateTime('2010-10-15'); Dog.Owner := TPerson.Create('Trevor'); ...
Actually, it's not necessary to declare a record type in order to use a record. Instead, a variable can be defined as a record in its declaration. This might look something like the following:
procedure TForm1.Button3Click(Sender: TObject); var Dog: record Name: String; Breed: TBreed; Born: TDatetime; Owner: TPerson; end; begin Dog.Name := 'Skippy'; // ...
More Variations
There are a couple of additional variations that are somewhat unique to records, and these are associated with enumerations and variant parts. Let's consider enumerations first.In the TDog record type, Breed was declared as a field of type TBreed. An alternative would be to declare the Breed field to be an enumeration directly, without the TBreed declaration. Here is another record type declaration that uses this syntax:
type TAppointment = record Time: TDateTime; DayOfWeek: (Mon, Tue, Wed, Thu, Fri, Sat, Sun); MeetingWith: TPerson; MeedtingBy: TPerson; end;
In the TAppointment record type, the DayOfWeek field is an enumeration, which is defined in the record type declaration directly.
Another interesting feature of traditional records is the optional variant part. A variant part is a conditional portion of the record structure that can hold variable pieces of information. The variant part, if present, always appear at the end of a record declaration.
For example, consider the following declaration of Delphi's TRect, which appears in the Types unit.
TRect = record case Integer of 0: (Left, Top, Right, Bottom: Longint); 1: (TopLeft, BottomRight: TPoint); end;
Using this syntax, you can use one or the other variant record structures (four Longints or two TPoints, though you cannot mix them in an individual instance of a record). For example, the following code declares and assigns data to two TRect instances (which are identical):
var r1, r2: TRect; begin r1.TopLeft := Point(5,5); r1.BottomRight := Point(100, 200); r2.Top := 5; r2.Left := 5; r2.Bottom := 100; r2.Right := 200;
There is a second syntax, in which the value of one of the record's fields determines which of the alternative structures the record uses. For example, consider the following record type declaration:
type TAdult = record Name: String; DateOfBirth: TDate; case MilitaryService: Boolean of True: ( WhichService: (Army, Navy, Marines, AirForce); StartDate: TDate; EndDate: TDate; ); False: ( Reason: (NoDraft, UnFit, Objection); ); end;
In this record, when the Boolean field MilitaryService is assigned the value True the WhichService, StartDate, and EndDate fields are available. If MilitaryService is False, a single enumerated field, Reason, can be used.
Class-Like Records
Beginning with Delphi 2005, records began to receive a major overhaul with the introduction of member visibility. Until this time, all members of a record were public. Since Delphi 2005, the same visibility directives that you can use in class declarations can now also be used in record declarations.But Delphi 2006 is the version in which the really big changes were introduced, giving records many features previously reserved for classes, and greatly improving their utility in your Delphi applications. These features include methods, properties, constructors, class constants, class variables, class static methods, class properties, nested types, overloaded operators, and record helpers. Each of these features are consider in the following sections.
Methods and Properties
A record's methods are functions and procedures associated with the record. Like their class cousins, a record's methods can access all of the other members of the record, including fields, properties, and other methods. Similar to the methods of a class, a record's methods have a reference to the record's instance through a variable named Self.Record methods also permit overloading. When overloading a record's method, you follow the same rules that you follow when overloading the methods of a class. Specifically, the compiler must be able to distinguish between each overloaded version based on the signature of the method (including whether the method is a function or procedure).
Record properties are also similar to their class counterparts. A record's properties can be implemented with read and/or write parts, and can use either direct access (reading and writing from fields of the record) or accessor methods (implementing reading and writing through specified methods of the record. Accessor methods permit a property to perform side effects).
The following is a simple example of a record with two fields (variables), a method, and three properties.
type TRectangle = record strict private FWidth: Double; FDepth: Double; function GetArea: Double; public property Area: Double read GetArea; property Width: Double read FWidth write FWidth; property Depth: Double read FWidth write FDepth; end;
The methods declared in a record must be implemented (records do not permit abstract methods). Furthermore, after declaring a method in a record you can use class completion (Ctrl-Shift-C) to generate the necessary implementation stubs for your record's methods. The following is the implementation of the GetArea method of the TRectangle record.
function TRectangle.GetArea: Double; begin Result := FWidth * FDepth; end;
Record Constructors
All records have an implicit, zero parameter constructor that is called when you attempt to use a record for the first time. If you want, you can add additional constructors. These constructors, however, must have at least one parameter, and must be distinguishable from any other overloaded constructor based on their signatures.The following shows the TRectangle record with an overloaded constructor (note that the overload directive was not required, but makes the code clearer). The implementation of the constructor follows in this code segment.
type TRectangle = record strict private FWidth: Double; FDepth: Double; function GetArea: Double; public property Area: Double read GetArea; property Width: Double read FWidth write FWidth; property Depth: Double read FWidth write FDepth; constructor Create(Width, Depth: Double); overload; end; constructor TRectangle.Create(Width, Depth: Double); begin FWidth := Width; FDepth := Depth; end;
Class Constants and Class Variables
While each instance of a record has one memory location for each of its fields, class constants and class variables are shared between all instances of a given record type. A class constant is a constant value, and its declaration looks a lot like a regular declaration of a constant. The difference is that the class constant is a characteristic of the specific record type. The class constant can be read using a reference to an instance of the record type, or through the record type itself.A class variable is a field that is shared by all instances of a particular record type. Changing the value of a class variable has the effect of changing the one memory location shared by the record type and all instances of it.
Class constants appear within a const clause of a record declaration, and class variables appear within a class var clause. These clauses conclude at the end of the record declaration, or when a method, type, variable, property, constructor, visibility directive, or variant part is encountered.
The following record type includes two class constants and two class variables:
TRectangle = record strict private FWidth: Double; FDepth: Double; function GetArea: Double; const MaxWidth = 100.0; MaxDepth = 200.0; public class var PreferredMinWidth: Double; PreferredMinDepth: Double; property Area: Double read GetArea; property Width: Double read FWidth write FWidth; property Depth: Double read FWidth write FDepth; constructor Create(Width, Depth: Double); overload; end;
Class Static Methods and Class Properties
Unlike classes, which have both class methods and class static methods, records support only class static methods (as far as class methods go). A class static method is one that can be called on an instance of a record or through a reference to the record type itself. Like the class static methods in a class declaration, a record's class static methods have no reference to Self, and therefore cannot access any other members of the class, other than other class static methods or class variables.Class properties are properties that can be referenced using an instance of a record type, or the record type itself. While class properties support both direct access and accessor methods (like normal properties), direct access can only employ class variables, and accessor methods must use only class static methods.
The following is a portion of a record type declaration that includes two class variables, a class static method, and two class properties. The implementation of the class static method follows.
type TRectangle = record strict private //... class var FPreferredMinWidth: Double; FPreferredMinDepth: Double; //... public class function PreferredMinArea: Double; static; class property PreferredMinWidth: Double read FPreferredMinWidth write FPreferredMinWidth; class property PreferredMinDepth: Double read FPreferredMinDepth write FPreferredMinDepth; //... end; class function TRectangle.PreferredMinArea: Double; begin Result := FPreferredMinWidth * FPreferredMinDepth; end;
Nested Types
A nested type in a record is identical to a nested type in a class declaration. A nested type is a type declaration internal to a record. The nested type is often a class type that is designed to act as a helper class. Specifically, it is designed to perform tasks related to the record in which it is declared.The following is another record class segment, which includes a simple nested type declaration.
type TRectangle = record strict private FWidth: Double; FDepth: Double; public //... function GetArea: Double; type WorkerClass = class(TObject) private FName: String; public function SayMyName: String; property Name: String read FName write FName; end; property Area: Double read GetArea; property Width: Double read FWidth write FWidth; property Depth: Double read FWidth write FDepth; end;
Operator Overloading
One additional feature added to records that is not shared by classes is operator overloading. In short, you can explicitly define what happens when an operator is applied to a particular record (unary operators) are to two records (binary operators). For example, you can define what happens when two of your records are added together using the addition (+) operator, or what happens if you implicitly cast your record to another type.There are almost 30 operators that can be overloaded. These operators are shown in Listing 1.
Method Name | Operator | Use Example | Returns |
Implicit | return type | ||
Explicit | TRecord(r) | return type | |
Negative | - | - r | return type |
Positive | + | + r | return type |
Dec | dec | dec(r) | return type |
LogicalNot | not | not r | return type |
Trunc | trunc | trunc(r) | return type |
Round | round | round(r) | return type |
In | in | in [a, b, r] | Boolean |
Equal | = | a = r | Boolean |
NotEqual | <> | a <> r | Boolean |
GreaterThan | > | a > r | Boolean |
GreaterThanOrEqual | >= | a <= r | Boolean |
LessThan | < | a < r | Boolean |
LessThanOrEqual | <= | a <= r | Boolean |
Add | + | a + r | return type |
Subtract | - | a - r | return type |
Multiply | * | a * r | return type |
Divide | / | a / r | return type |
IntDivide | div | a div r | return type |
Modulus | mod | a mod r | return type |
LeftShift | shl | a shl r | return type |
RightShift | shr | a shr r | return type |
LogicalAnd | and | a and r | return type |
LogicalOr | or | a or r | return type |
LogicalXor | xor | a xor r | return type |
BitwiseAnd | and | a and r | return type |
BitwiseOr | or | a or r | return type |
BitwiseXor | xor | a xor r | return type |
Listing 1 Record operators that can be overloaded
To overload one of these operators, you implement the specified method name using the class operator syntax (instead of function) in your record type definition. You must also define the type of value that the operator will return. For those operators that are declared to return a Boolean value, the operator must return a Boolean value. All other operators can return whatever type is appropriate for that operator. In other words, a non-Boolean operator can return a value other than the record type.
The following code segment includes a portion of the TRectangle class with an overloaded addition operator, the implementation of this operator overload, and an event handle that uses the operator.
type TRectangle = record strict private FWidth: Double; //... public property Width: Double read FWidth write FWidth; //... class operator Add(a, b: TRectangle): TRectangle; end; class operator TRectangle.Add(a, b: TRectangle): TRectangle; begin Result.Width := a.Width + b.Width; end; procedure TForm1.Button7Click(Sender: TObject); var a, b, c: TRectangle; begin a.Width := 5; b.Width := 20; c := a + b; end;
Record Helpers
Like classes in Delphi 2005, records can also have helpers. Record helpers serve essentially the same purpose as class helpers, though they only apply to records. Specifically, record helpers permit you to attach additional methods, properties, and class variables to an existing record. Record helpers can also replace existing methods of a class, if they use the same name and signature, since methods of a record helper supercede those of the record they are helping.While the use of class helpers is largely discouraged, record helpers can play a role that cannot be provided through any other means. Specifically, record helpers provide the only mechanism for extending an existing record type. Classes, on the other hand, support inheritance. As a result, it is often better to descend a new class from an existing class, and introduce new methods and properties in the descendant than to use a class helper.
Record helpers have the same limitations as class helpers. A given record can be supported by only one record helper. If there is more than one record helper for a given record, the record will employ the record helper that is closer in scope.
The following code shows the declaration of a record helper for the TRectangle record, along with the implementation of the single method declared within the record helper. Notice in the GetMaxArea implementation that Self refers to the record instance.
type TRectHelper = record helper for TRectangle function GetMaxArea: Double; end; function TRectHelper.GetMaxArea: Double; begin Result := Self.MaxWidth * Self.MaxDepth; end; procedure TForm1.Button8Click(Sender: TObject); var a: TRectangle; begin ShowMessage(FloatToStr(a.GetMaxArea)); end;
Records Versus Classes
At this point you might be inclined to think that the enhanced record syntax makes records nearly identical to classes, making it difficult to choose between them. The truth is that there are very significant differences between records and classes, and these differences can help you decide which to use in a particular situation.First of all, records are allocated on the stack, as long as they are not declared globally or managed using New and Dispose. Class instances, by comparison, are allocated on the heap.
This has a significant impact on memory management. Specifically, while classes that do not have an owner must be explicitly freed, memory allocated for record instances is automatically freed when the record goes out of scope merely by its data being removed from the stack. Of course, if your record creates class instances during its lifecycle, that memory must be managed by you or your record, or a memory leak may exist. This is made a bit more difficult since records do not support destructors.
Another significant difference between classes and records is that you do not need to call a record's constructor, while a class instance must be explicitly created. You can declare a variable of a record type and then immediately use it. In this case, Delphi will call the zero parameter constructor for you.
You are still free to call a record's constructor if you want. In fact, if you've declared one or more additional constructors, it is probably because you want to create your record in a particular fashion, and you can do so by calling one of those overloaded constructors.
Another major difference between records and classes is that records do not support inheritance. A record never descends from an existing record. Similarly, records cannot implement interfaces (though they may have fields or properties of interface type, but that is different). The bottom line here is that records do not support polymorphism. A corollary of this is that record methods are necessarily static. Records do not support virtual, dynamic, or abstract methods.
As you learned earlier, records can also include a variant part. A variant part permits a record to hold data that is specific to its nature, and may vary from record instance to record instance. All instances of a class have the same set of members, regardless of their data.
Finally, records are value types, as opposed to reference types. Consider the following code:
var a, b: TRectangle; begin a.Width := 5; a.Depth := 5; b := a;
The TRectangle named b in this code segment is a completely new record that contains a copy of the data held within record a. By comparison, if TRectangle was a class type, following the assignment of variable a to variable b, variable b would point to the same memory address as a.
Finally, records support operator overloading, while classes do not. Operator overloading provides you with a rich tool for managing exactly what happens when records are used in expressions, and the results can get very interesting.
Examples of Useful Records in Delphi
Record types are playing an increasingly important role in the latest versions of Delphi, and this is due in large part to the differences between record types and class types described in the preceding section. Let's consider a few of the more significant record types.Delphi 2009 saw the introduction of the System.TMonitor type. TMonitor is a record that can be used to acquire and manage a lock on any TObject. It provides a convenient and powerful synchronization mechanism for multithreaded applications.
A major new layer of runtime type information (RTTI) was introduced in Delphi 2010. The principle type for working with the new RTTI, TRTTIContext, is a record type.
Additional records types introduced in Delphi 2010, and further enhanced in Delphi XE, include TFile, TPath, and TDirectory. These types, which are declared in the IOUtils unit, are record types.
Summary
It is no longer safe to assume that the entity you are calling a method on, or are reading a property from, is an object. It might be a type of record. Indeed, records types posses a unique blend of features that make them better suited for a variety of situations in which you would have previously used a class type.Authors Note: I originally published this article in the SDN Magazine (issue 110), published by the Software Development Network on August 26, 2011. For information on the Software Development Network, visit http://www.sdn.nl/.