- 08, May 2020
- #1
Custom Managed Records Coming to Delphi 10.4
Marco Cantu 8 May 2020
Marco Cantu 8 May 2020
Скрытая информация :: Авторизуйтесь для просмотра »
)))));
end;
class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
Log('destroyed' + IntToHex (Integer(Pointer(Скрытая информация :: Авторизуйтесь для просмотра »
)))));
end;
The huge difference between this construction mechanism and what was previously available for records is the automatic invocation.
If you write something like the code below, you can invoke both the initializer and the finalizer, and end up with a try-finally block generated by the compiler for your managed record instance.
procedure LocalVarTest;
var
my1: TMyRecord;
begin
Log (my1.Value.ToString);
end;
With this code you’ll get a log like:
created 0019F2A8
10
destroyed 0019F2A8
Another scenario is the use of inline variables, like in:
begin
var t: TMyRecord;
Log(t.Value.ToString);
which gets you the same sequence in the log.
The Assign Operator
The := assignment flatly copies all of the data of the record fields.
While this is a reasonable default, when you have custom data fields and custom initialization you might want to change this behavior.
This is why for Custom Managed Records you can also define an assignment operator.
The new operator is invoked with the := syntax, but defined as Assign:
class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);
The operator definition must follow very precise rules, including having the first parameter as a reference parameter, and the second as a const passed by reference. If you fail to do so, the compiler issues error messages like the following:
[dcc32 Error] E2617 First parameter of Assign operator must be a var parameter of the container type
[dcc32 Hint] H2618 Second parameter of Assign operator must be a const[Ref] or var parameter of the container type
There is a sample case invoking the Assign operator:
var
my1, my2: TMyRecord;
begin
my1.Value := 22;
my2 := my1;
produces this log (in which we also add a sequence number to the record):
created 5 0019F2A0
created 6 0019F298
5 copied to 6
destroyed 6 0019F298
destroyed 50019F2A0
Notice that the sequence of destruction is reversed from the sequence of construction.
Passing Managed Records as Parameters
Managed records can work differently from regular records also when passed as parameters or returned by a function. Here are several routines showing the various scenarios:
procedure ParByValue (rec: TMyRecord);
procedure ParByConstValue (const rec: TMyRecord);
procedure ParByRef (var rec: TMyRecord);
procedure ParByConstRef (const [ref] rec: TMyRecord);
function ParReturned: TMyRecord;
Now without going over each log one by one, this is the summary of the information:
Exceptions and Managed Records
When an exception is raised, records in general are cleared even when no explicit try, finally block is present, unlike objects. This is a fundamental difference and key to the real usefulness of managed records.
procedure ExceptionTest;
begin
var a: TMRE;
var b: TMRE;
raise Exception.Create('Error Message');
end;
Within this procedure, there are two constructor calls and two destructor calls. Again, this is a fundamental difference and a key feature of managed records. See the later section on a simple smart pointer based on managed records.
Arrays of Managed Records
If you define a static array of managed records, there are initialized calling the Initialize operator at the point declaration:
var
a1: array [1..5] of TMyRecord; // call here
begin
Log ('ArrOfRec');
They are all destroyed when they get out of scope. If you define dynamic array of managed records, the initialization code is called with the array is sized (with SetLength):
var
a2: array of TMyRecord;
begin
Log ('ArrOfDyn');
SetLength(a2, 5); // call here
Conclusion
This is just a relatively short introduction of a great new feature Embarcadero is adding to the Delphi language for the coming 10.4 release.
Managed records work for generic records for example and in many other scenarios not covered here.
And while this is the top new language feature, there are others coming like the new unified memory management across all platforms.
Stay tuned!
If you have update subscription, one of the perks is accessing beta builds of upcoming releases. There’s still time to join our beta program for 10.4!
This is a preview of an upcoming release of RAD Studio. There can always be last-minute bugs or changes. Nothing here is final until the release is officially made available.
Marco Cantu 8 May 2020
Скрытая информация :: Авторизуйтесь для просмотра »
)))));
end;
class operator TMyRecord.Finalize(var Dest: TMyRecord);
begin
Log('destroyed' + IntToHex (Integer(Pointer(Скрытая информация :: Авторизуйтесь для просмотра »
)))));
end;
The huge difference between this construction mechanism and what was previously available for records is the automatic invocation.
If you write something like the code below, you can invoke both the initializer and the finalizer, and end up with a try-finally block generated by the compiler for your managed record instance.
procedure LocalVarTest;
var
my1: TMyRecord;
begin
Log (my1.Value.ToString);
end;
With this code you’ll get a log like:
created 0019F2A8
10
destroyed 0019F2A8
Another scenario is the use of inline variables, like in:
begin
var t: TMyRecord;
Log(t.Value.ToString);
which gets you the same sequence in the log.
The Assign Operator
The := assignment flatly copies all of the data of the record fields.
While this is a reasonable default, when you have custom data fields and custom initialization you might want to change this behavior.
This is why for Custom Managed Records you can also define an assignment operator.
The new operator is invoked with the := syntax, but defined as Assign:
class operator Assign (var Dest: TMyRecord; const [ref] Src: TMyRecord);
The operator definition must follow very precise rules, including having the first parameter as a reference parameter, and the second as a const passed by reference. If you fail to do so, the compiler issues error messages like the following:
[dcc32 Error] E2617 First parameter of Assign operator must be a var parameter of the container type
[dcc32 Hint] H2618 Second parameter of Assign operator must be a const[Ref] or var parameter of the container type
There is a sample case invoking the Assign operator:
var
my1, my2: TMyRecord;
begin
my1.Value := 22;
my2 := my1;
produces this log (in which we also add a sequence number to the record):
created 5 0019F2A0
created 6 0019F298
5 copied to 6
destroyed 6 0019F298
destroyed 50019F2A0
Notice that the sequence of destruction is reversed from the sequence of construction.
Passing Managed Records as Parameters
Managed records can work differently from regular records also when passed as parameters or returned by a function. Here are several routines showing the various scenarios:
procedure ParByValue (rec: TMyRecord);
procedure ParByConstValue (const rec: TMyRecord);
procedure ParByRef (var rec: TMyRecord);
procedure ParByConstRef (const [ref] rec: TMyRecord);
function ParReturned: TMyRecord;
Now without going over each log one by one, this is the summary of the information:
- ParByValue creates a new record and calls the assignment operator (if available) to copy the data, destroying the temporary copy when exiting the procedure
- ParByConstValue makes no copy, and no call at all
- ParByRef makes no copy, no call
- ParByConstRef makes no copy, no call
- ParReturned creates a new record (via Initialize) and on return it calls the Assign operator, if the call is like the following, and deletes the temporary record once assigned back like in: my1 := ParReturned;
Exceptions and Managed Records
When an exception is raised, records in general are cleared even when no explicit try, finally block is present, unlike objects. This is a fundamental difference and key to the real usefulness of managed records.
procedure ExceptionTest;
begin
var a: TMRE;
var b: TMRE;
raise Exception.Create('Error Message');
end;
Within this procedure, there are two constructor calls and two destructor calls. Again, this is a fundamental difference and a key feature of managed records. See the later section on a simple smart pointer based on managed records.
Arrays of Managed Records
If you define a static array of managed records, there are initialized calling the Initialize operator at the point declaration:
var
a1: array [1..5] of TMyRecord; // call here
begin
Log ('ArrOfRec');
They are all destroyed when they get out of scope. If you define dynamic array of managed records, the initialization code is called with the array is sized (with SetLength):
var
a2: array of TMyRecord;
begin
Log ('ArrOfDyn');
SetLength(a2, 5); // call here
Conclusion
This is just a relatively short introduction of a great new feature Embarcadero is adding to the Delphi language for the coming 10.4 release.
Managed records work for generic records for example and in many other scenarios not covered here.
And while this is the top new language feature, there are others coming like the new unified memory management across all platforms.
Stay tuned!
If you have update subscription, one of the perks is accessing beta builds of upcoming releases. There’s still time to join our beta program for 10.4!
This is a preview of an upcoming release of RAD Studio. There can always be last-minute bugs or changes. Nothing here is final until the release is officially made available.