Reputation: 1205
The following code in Delphi 10.2.3:
uses
System.SysUtils;
type
TRec = record
strict private
FName: String;
FValue: Integer;
public
property Name: String read FName;
property Value: Integer read FValue;
constructor Create(const AName: String);
function WithValue(const AValue: Integer): TRec;
end;
constructor TRec.Create(const AName: String);
begin
FName := AName;
end;
function TRec.WithValue(const AValue: Integer): TRec;
begin
Result := Self;
Result.FValue := AValue;
end;
procedure Main;
var
x: TRec;
begin
x := TRec.Create('First').WithValue(666);
x := TRec.Create('Second');
Writeln('In stack: ', x.Value);
end;
var
x: TRec;
begin
x := TRec.Create('First').WithValue(666);
x := TRec.Create('Second');
Writeln('In global: ', x.Value);
Main;
Readln;
end.
Makes following output:
In global: 0
In stack: 666
Is this intended to be like this? When assignment is done to global variable in data segment then "call @CopyRecord" line is generated by the compiler, but when local variable from stack is used then this line is not added by the compiler...
For global:
Project17.dpr.47: x := TRec.Create('First').WithValue(666);
0041D56B 8D45E0 lea eax,[ebp-$20]
0041D56E BA44D64100 mov edx,$0041d644
0041D573 E87CDAFFFF call TRec.Create
0041D578 8D55E0 lea edx,[ebp-$20]
0041D57B B8C0584200 mov eax,$004258c0
0041D580 8B0D64AF4100 mov ecx,[$0041af64]
0041D586 E865B2FEFF call @CopyRecord
0041D58B B8C0584200 mov eax,$004258c0
0041D590 8D4DE8 lea ecx,[ebp-$18]
0041D593 BA9A020000 mov edx,$0000029a
0041D598 E877DAFFFF call TRec.WithValue
0041D59D 8D55E8 lea edx,[ebp-$18]
0041D5A0 B8B8584200 mov eax,$004258b8
0041D5A5 8B0D64AF4100 mov ecx,[$0041af64]
0041D5AB E840B2FEFF call @CopyRecord
Project17.dpr.48: x := TRec.Create('Second');
0041D5B0 8D45D8 lea eax,[ebp-$28]
0041D5B3 BA5CD64100 mov edx,$0041d65c
0041D5B8 E837DAFFFF call TRec.Create
0041D5BD 8D55D8 lea edx,[ebp-$28]
0041D5C0 B8B8584200 mov eax,$004258b8
0041D5C5 8B0D64AF4100 mov ecx,[$0041af64]
0041D5CB E820B2FEFF call @CopyRecord
For local:
Project17.dpr.39: x := TRec.Create('First').WithValue(666);
0041B074 8D45F0 lea eax,[ebp-$10]
0041B077 BAF8B04100 mov edx,$0041b0f8
0041B07C E873FFFFFF call TRec.Create
0041B081 8D45F0 lea eax,[ebp-$10]
0041B084 8D4DF8 lea ecx,[ebp-$08]
0041B087 BA9A020000 mov edx,$0000029a
0041B08C E883FFFFFF call TRec.WithValue
Project17.dpr.40: x := TRec.Create('Second');
0041B091 8D45F8 lea eax,[ebp-$08]
0041B094 BA10B14100 mov edx,$0041b110
0041B099 E856FFFFFF call TRec.Create
Project17.dpr.41: Writeln('In stack: ', x.Value);
0041B09E A1ACF54100 mov eax,[$0041f5ac]
0041B0A3 BA2CB14100 mov edx,$0041b12c
0041B0A8 E87BA8FEFF call @Write0UString
Should I always use line like this in every constructor of the record?
Self := Default(TRec);
Because if I add this line then the output is intuitive and returns 0 for both cases.
Upvotes: 4
Views: 322
Reputation: 612993
Is this intended to be like this?
Records are value types. When you allocate local variables of such types, they are not default initialized. So, yes, this is as designed.
Default initialization is performed for:
Should I always use line like this in every constructor of the record?
Self := Default(TRec);
Yes, if you wish for the constructor to initialize each field of the record.
Personally, I'm not a big fan of record constructors, in terms of readability. When I see:
foo := TBar.Create(...);
I expect foo
to be an instance of a class and therefore I would also expect to see a call to foo.Free
when the lifetime of the instance ends.
Myself I tend to use static class methods, always named New
, to construct newly minted value type instances.
I'm also not a great fan of your WithValue
instance method. I think it feels a little clunky to force the consumer of your class to populate one instance with a name, and then call WithValue
on that instance to finish the job off by providing a value. I'd write it like this:
type
TRec = record
strict private
FName: String;
FValue: Integer;
public
property Name: String read FName;
property Value: Integer read FValue;
public
class function New(const Name: String; const Value: Integer): TRec; static;
end;
class function TRec.New(const Name: String; const Value: Integer): TRec;
begin
Result.FName := Name;
Result.FValue := Value;
end;
Upvotes: 6