Following the yesterday’s hunch, I did some research and I came up with a way to detect when a record is copied (simple, supported, working everywhere) and even to access both records (source and target) during that operation (unsafe, unsupported and currently working only for Win32).
Now I can write such code:
program RecordAssignment;
{$APPTYPE CONSOLE}
{$R *.res}
uses
FastMM4,
System.SysUtils,
GpRecAssignOverload in 'GpRecAssignOverload.pas';
type
TRec = record
private
FData : integer;
FAssignOp: IInterface; //should be last field in the record!
public
constructor Create(data: integer);
end;
{ TRec }
constructor TRec.Create(data: integer);
begin
FData := data;
Writeln('Record [', FData, '] created');
FAssignOp := TAssignRec<TRec>.Create(
procedure(var rLeft, rRight: TRec)
begin
Writeln('Record assignment');
rLeft.FData := rLeft.FData + 1;
end
);
end;
procedure Test;
var
rec1, rec2: TRec;
begin
rec1 := TRec.Create(42);
rec2 := rec1;
Writeln('rec1.FData = ', rec1.FData);
Writeln('rec2.FData = ', rec2.FData);
end;
begin
try
Test;
Write('>'); Readln;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
And the output is:
Just to make something clear – yes, technically I can write this but I’m pretty sure that I don’t want to. Tomorrow I’ll show the implementation for TAssignRec and you’ll see why.
Could this be solved with operator overloading?
ReplyDeleteLike this example: https://plus.google.com/+Asbj%C3%B8rnHeid/posts/KnR9pUPTy7P
Or will Implicit/Explicit only be triggered when there's a typecast involved?
Explicit is only triggered when typecast is involved.
DeleteImplicit is a good idea, but it doesn't work.
class operator Implicit(const a: TRec): TRec;
[DCC Error] CatchAssignment.dpr(39): E2521 Operator 'Implicit' must take one 'TRec' type in parameter or result type
Ah. Just figured that the operator overloading is intended for different datatypes and not similar datatypes. I had the impression that it was possible to override default behaviour as well...
Delete@Jorn: no, assignment can be overloaded in C++ but not in Delphi.
ReplyDelete@Gabr: instead of using a dummy interface to intercept an assignment why not to move a record data into an instance implementing interface? this is essentially a basic idea of my TForge project. You get solid automatic memory management with clear and safe "user" code, and very interesting and highly effective "engine" code.
Serge, do you have any blog post on that? I'm collecting various alternative (and better) solutions for the next post on that topic.
DeleteI believe this one is the most appropriate - http://sergworks.wordpress.com/2013/12/17/goodbye-tobject/
DeleteThere's not really much on the topic of records here.
DeleteIf you go through pointers, you can have something like this.
ReplyDelete(You need to add @ in front of the records you want to run through the custom assignment)
type
TRec = record
private
FData : integer;
public
constructor Create(iData: integer);
class operator Implicit(aSource: Pointer): TRec;
end;
//-----
class operator TRec.Implicit(aSource: Pointer): TRec;
type
TTempRec = type TRec;
begin
Result.FData := TTempRec(aSource^).FData + 1;
end;
//-----
procedure Test;
var
rec1, rec2, rec3: TRec;
begin
rec1 := TRec.Create(42);
rec2 := rec1; //Plain assignment
rec3 := @rec1; //Custom assignment
Writeln('rec1.FData = ', rec1.FData);
Writeln('rec2.FData = ', rec2.FData);
Writeln('rec3.FData = ', rec3.FData);
end;
//-----
Gives you:
rec1.FData = 42
rec2.FData = 42
rec3.FData = 43
Obviously a source for lots for debugging :-P
DeleteDirty hack, but works :D
ReplyDeleteprogram project1;
type
Tfoo=record
a: string;
end;
Tbar = record
a: string;
end;
// I use two equivalent types
// and two assigment op's: bar->foo and foo->bar:
operator := (foo: TFoo): TBar;
begin
result.a:=foo.a;
end;
operator := (bar: TBar): TFoo;
begin
result.a := bar.a;
end;
var
a,b:Tfoo;
begin
a.a:='a';
b.a:='b';
a := b;
writeln(a.a);
readln;
end.
'operator :=' is not a syntax supported by Delphi.
Delete