Delphi documentation says that it is possible to overload the Inc and Dec operators; I see no valid way to do it. Here are attempts to overload the Inc operator; some attempts lead to compile errors, some to runtime access violation (Delphi XE):
program OverloadInc;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyInt = record
FValue: Integer;
// class operator Inc(var A: TMyInt); DCC error E2023
class operator Inc(var A: TMyInt): TMyInt;
property Value: Integer read FValue write FValue;
end;
class operator TMyInt.Inc(var A: TMyInt): TMyInt;
begin
Inc(A.FValue);
Result:= A;
end;
type
TMyInt2 = record
FValue: Integer;
class operator Inc(A: TMyInt2): TMyInt2;
property Value: Integer read FValue write FValue;
end;
class operator TMyInt2.Inc(A: TMyInt2): TMyInt2;
begin
Result.FValue:= A.FValue + 1;
end;
procedure Test;
var
A: TMyInt;
begin
A.FValue:= 0;
Inc(A);
Writeln(A.FValue);
end;
procedure Test2;
var
A: TMyInt2;
I: Integer;
begin
A.FValue:= 0;
// A:= Inc(A); DCC error E2010
Writeln(A.FValue);
end;
begin
try
Test; // access violation
// Test2;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
The signature of the operator is wrong. It should be:
class operator Inc(const A: TMyInt): TMyInt;
or
class operator Inc(A: TMyInt): TMyInt;
You cannot use a var parameter.
This program
{$APPTYPE CONSOLE}
type
TMyInt = record
FValue: Integer;
class operator Inc(const A: TMyInt): TMyInt;
property Value: Integer read FValue write FValue;
end;
class operator TMyInt.Inc(const A: TMyInt): TMyInt;
begin
Result.FValue := A.FValue + 1;
end;
procedure Test;
var
A: TMyInt;
begin
A.FValue := 0;
Inc(A);
Writeln(A.FValue);
end;
begin
Test;
Readln;
end.
produces this output:
1
Discussion
This is a rather unusual operator when overloaded. In terms of usage the operator is an in-place mutation. However, when overloaded, it works like an addition operator with an implicit addend of one.
So, in the code above this line:
Inc(A);
is effectively transformed into
A := TMyInt.Inc(A);
and then compiled.
If you are wanting to maintain true in-place mutation semantics, and avoid the copying associated with this operator, then I believe that you need to use a method of the type.
procedure Inc; inline;
....
procedure TMyInt.Inc;
begin
inc(FValue);
end;
Related
I have a record in a method as a local variable. Then I add it to a TList that is a member of the class. Now my question is after the method is finished, is the record is still valid? (or its destroyed and I shouldn't use MyList.List[0]).
this is a sample code:
TTestClass = class
MyList: TList<TMyRec>;
procedure add;
end;
procedure TTestClass.add;
var
ARec: TMyRec;
begin
Arec.a:= 100;
ARec.b:= 'abc';
MyList.add(ARec);
end;
is the record is still valid?
No, but its value is.
Records are value types allocated on the stack, which means they are passed by value (copied on each assignment)
When you use them as you did, you are actually preforming an implicit copy from your local variable to the storage in the list.
So no, the record declared in the var block is not valid when the method finishes execution. But its value is already copied to the storage of the list and therefore it is a valid value.
Consider the following code for more illustration:
program Project20;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, system.generics.collections;
type
PMyRec = ^TMyRec;
TMyRec = record
a: Integer;
b: string;
end;
TTestClass = class
MyListOfPointers: TList<PMyRec>;
MyListOfValues: TList<TMyRec>;
constructor Create;
destructor Destroy; override;
procedure add;
procedure addP;
procedure ShowRecs;
end;
procedure TTestClass.add;
var
ARec: TMyRec;
begin
Arec.a:= 100;
ARec.b:= 'abc';
MyListOfValues.add(ARec);
end;
procedure TTestClass.addP;
var
ARec: TMyRec;
begin
Arec.a:= 100;
ARec.b:= 'abc';
MyListOfPointers.add(#ARec);
end;
constructor TTestClass.Create;
begin
MyListOfPointers := TList<PMyRec>.Create;
MyListOfValues := TList<TMyRec>.Create;
end;
destructor TTestClass.Destroy;
begin
MyListOfPointers.Free;
MyListOfValues.Free;
inherited;
end;
procedure TTestClass.ShowRecs;
begin
writeln(PMyRec(MyListOfPointers[0])^.b + ' ' + PMyRec(MyListOfPointers[0])^.a.ToString);
writeln(MyListOfValues[0].b + ' ' + MyListOfValues[0].a.ToString);
end;
var
MyClass: TTestClass;
begin
try
MyClass := TTestClass.Create;
try
MyClass.Add;
MyClass.AddP;
MyClass.ShowRecs;
finally
MyClass.Free;
end;
Readln;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
Readln;
end;
end;
end.
the output is
First attempt
39866256
abc 100
Second attempt
40390544
abc 100
You won't get an access violation but rather a unique behavior a will take any value on that address and b will always be empty (b = '') because it is a managed type.
I have a Delphi generic class that exposes a function with an argument of the generic type. Inside this function, I need to pass an instance of the generic type on to another object expecting a Variant type. Similar to this:
type
IMyInterface = interface
DoStuff(Value: Variant);
end;
TMyClass<T> = class
FMyIntf: IMyInterface
procedure DoStuff(SomeValue: T);
end;
[...]
procedure MyClass<T>.DoStuff(SomeValue: T);
begin
FMyIntf.DoStuff((*convert SomeValue to Variant here*));
end;
I tried using Rtti.TValue.From(SomeValue).AsVariant. This worked for integral types, but blew up for Booleans. I don't quite see why, since normally I'd be able to assign a Boolean value to a Variant...
Is there a better way to make this conversion? I only need it to work for simple built-in types (excluding enumerations and records)
I think there is no direct way to convert generic type to variant because variant cannot hold all the possible types. You must write your specific conversion routine. E.g.:
interface
//...
type
TDemo = class
public
class function GetAsVariant<T>(const AValue: T): Variant;
end;
//...
implementation
uses
Rtti,
TypInfo;
//...
{ TDemo}
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
bRes: Boolean;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkInteger: Result := val.AsInteger;
tkInt64: Result := val.AsInt64;
tkEnumeration:
begin
if val.TryAsType<Boolean>(bRes) then
Result := bRes
else
Result := val.AsOrdinal;
end;
tkFloat: Result := val.AsExtended;
tkString, tkChar, tkWChar, tkLString, tkWString, tkUString:
Result := val.AsString;
tkVariant: Result := val.AsVariant
else
begin
raise Exception.Create('Unsupported type');
end;
end;
end;
Because TValue.AsVariant handles most of the type conversions internally, this function can be simplified. I will handle enumerations in case you could need them later:
class function TDemo.GetAsVariant<T>(const AValue: T): Variant;
var
val: TValue;
begin
val := TValue.From<T>(AValue);
case val.Kind of
tkEnumeration:
begin
if val.TypeInfo = TypeInfo(Boolean) then
Result := val.AsBoolean
else
Result := val.AsOrdinal;
end
else
begin
Result := val.AsVariant;
end;
end;
Possible usage:
var
vValue: Variant;
begin
vValue := TDemo.GetAsVariant<Boolean>(True);
Assert(vValue = True); //now vValue is a correct Boolean
Looks like in my Delphi version 10.2 the Boolean problem is gone and TValue.From<T>(FValue).AsVariant is enough.
Here an example with some other helpful things like comparing the generic type:
TMyValue<T> = class(TPersistent)
private
FValue: T;
procedure SetValue(const AValue: T);
function GetAsVariant: Variant; override;
public
procedure Assign(Source: TPersistent); override;
property Value: T read FValue write SetValue;
property AsVariant: Variant read GetAsVariant;
end;
function TMyValue<T>.GetAsVariant: Variant;
begin
Result:= TValue.From<T>(FValue).AsVariant;
end;
procedure TMyValue<T>.SetValue(const AValue: T);
begin
if TEqualityComparer<T>.Default.Equals(AValue, FValue) then Exit;
FValue:= AValue;
//do something
end;
procedure TMyValue<T>.Assign(Source: TPersistent);
begin
if Source is TMyValue<T> then Value:= (Source as TMyValue<T>).Value
else inherited;
end;
Another way (tested XE10)
Var
old : variant;
val : TValue;
Begin
val := TValue.FromVariant(old);
End;
In Delphi XE2, I'm trying to overload the in operator on a record to allow me to check whether the value represented by the record is part of a set. My code looks like this:
type
MyEnum = (value1, value2, value3);
MySet = set of MyEnum;
MyRecord = record
Value: MyEnum;
class operator In(const A: MyRecord; B: MySet): Boolean;
end;
class operator MyRecord.In(const A: MyRecord; B: MySet): Boolean;
begin
Result := A.Value in B;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MySet;
begin
R.Value := value1;
S := [value1, value2];
Button1.Caption := BoolToStr(R in S);
end;
The code fails to compile. For the statement R in S the compiler says: Incompatible types MyRecord and MyEnum.
How can I overload the In operator on MyRecord so that R in S will evaluate to True in the above code?
For the in operator to work the right operand must be of the record type since it's a set operator and not a binary operator. In your case it is the left operand.
So the following will work:
type
MyRecord = record
Value: MyEnum;
class operator In(const A: MyRecord; const B: MySet): Boolean;
end;
MyRecord2 = record
Value: MySet;
class operator In(const A: MyRecord; const B: MyRecord2): Boolean;
class operator In(const A: MyEnum; const B: MyRecord2): Boolean;
end;
class operator MyRecord.In(const A: MyRecord; const B: MySet): Boolean;
begin
Result := A.Value in B;
end;
class operator MyRecord2.In(const A: MyRecord; const B: MyRecord2): Boolean;
begin
Result := A.Value in B.Value;
end;
class operator MyRecord2.In(const A: MyEnum; const B: MyRecord2): Boolean;
begin
Result := A in B.Value;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
R2: MyRecord2;
begin
R.Value := value1;
R2.Value := [value1, value2];
if R in R2 then;
if value1 in R2 then;
end;
Well, you can almost do this, but you may not want to. AFAIK, class operators only work on the class (or record) they are defined within, so both R and S in your code have to be TMyRecord. With some injudicious use of implicit casting, we get the following:
unit Unit2;
interface
type
MyEnum = (value1, value2, value3);
MySet = set of MyEnum;
MyRecord = record
Value: MyEnum;
ValueSet: MySet;
class operator Implicit(A: MyEnum): MyRecord;
class operator Implicit(A: MySet): MyRecord;
class operator In (Left,Right:MyRecord): Boolean;
end;
implementation
class operator MyRecord.Implicit(A: MyEnum): MyRecord;
begin
Result.Value := A;
end;
class operator MyRecord.Implicit(A: MySet): MyRecord;
begin
Result.ValueSet := A;
end;
class operator MyRecord.In(Left, Right: MyRecord): Boolean;
begin
Result:= left.Value in Right.ValueSet;
end;
end.
The following will now complile, and even work:
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MyRecord;
begin
R.Value := value1;
S := [value1,value2,value3];
Button1.Caption := BoolToStr(R In S,true);
end;
Which, I'm sure we will all agree, is much more elegant than 'BoolToStr(R.Value in S)'.
However the following will also compile, but give the wrong result:
procedure TForm1.Button1Click(Sender: TObject);
var
R: MyRecord;
S: MyRecord;
begin
R.Value := value1;
S := [value1,value2,value3];
Button1.Caption := BoolToStr(S In R,true);
end;
So, as Dorin commented, better to just have dull, staid old 'BoolToStr(R.Value in S)'. Unless of course you are being paid per line of code. And a bonus for bug-fixing.
The following code (constructed only to demonstrate the problem) compiles and works in Delphi 2010. In Delphi 2009, compiler fails with "E2035 Not enough actual parameters".
program Project50;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(param: integer);
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
end;
a := TProc(b); // <-- [DCC Error] Project50.dpr(19): E2035 Not enough actual parameters
end.
I have found only one very ugly hack to work around the problem (a: TProc absolute b). Does anybody knows of a nicer workaround for this compiler deficiency?
[TProc field is actually hidden inside a record that can store various 'executable' code - TProcedure, TMethod and TProc. Casting is used to store specific anonymous proc into this field.]
The trick is not to do
a := TProc(b);
but
TMyProc(a) := b;
That compiles and works in D2009. Sample project attached below.
program Project51;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(var param: integer);
TStorage = record
FDelegate: TProc;
end;
var
a : TMyProc;
b : TMyProc;
param: integer;
stg : TStorage;
begin
b := procedure (var param: integer)
begin
param := 2*param;
end;
// stg.FDelegate := TMyProc(b); // doesn't compile in Delphi 2009, compiles in Delphi 2010
TMyProc(stg.FDelegate) := b;
param := 21;
TMyProc(stg.FDelegate)(param);
Writeln(param);
Readln;
end.
However, this doesn't work if casting to a local variable.
var
p: TProc;
a: TMyProc;
TMyProc(p) := a; // this will not compile
Curiouser and curiouser.
I have found a hack #2:
program Project1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(param: integer);
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
Writeln('asdf');
end;
PPointer(#a)^ := PPointer(#b)^;
a;
readln;
end.
I am in doubt what are you trying to achieve by assigning TMyProc (with param argument) to TProc (without argument)?
Updated: A hack #3 (should increment ref counter, the idea is stolen from System._IntfCopy):
procedure AnonCopy(var Dest; const Source);
var
P: Pointer;
begin
P:= Pointer(Dest);
if Pointer(Source) <> nil
then IInterface(Source)._AddRef;
Pointer(Dest):= Pointer(Source);
if P <> nil then
IInterface(P)._Release;
end;
var
a: TProc;
b: TMyProc;
begin
b := procedure (param: integer)
begin
Writeln('asdf');
end;
AnonCopy(a, b);
// PPointer(#a)^ := PPointer(#b)^;
a;
readln;
end.
It appears that the best way would be to use generics to store the correct type of delegate in the record. No hacks required.
program Project51;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
TMyProc = reference to procedure(var param: integer);
TStorage<T> = record
FDelegate: T;
end;
var
a : TMyProc;
b : TMyProc;
p : TProc;
param: integer;
stg : TStorage<TMyProc>;
begin
b := procedure (var param: integer)
begin
param := 2*param;
end;
stg.FDelegate := b;
param := 21;
stg.FDelegate(param);
Writeln(param);
Readln;
end.
I currently use a record to pass several result parameters for a function and need to add some more data as it follows:
type
TItemType = (itFile, itRegistry);
TItemDetails = record
Success: Boolean;
ItemType: TItemType;
TotalCount: Integer;
TotalSize: Int64;
List: TStringList;
end;
function DoSomething: TItemDetails;
Is it possible/advisable to use a TStringList inside a record for this specific case?
I found on Embarcadero Developer Network a class that allows to declare StringList instead of TStringList and takes care of creating and freeing the list. Would this be an advisable solution?
http://cc.embarcadero.com/Item/25670
Also, if this does indeed works, will I have to manually free the TStringList?
Yes, by all means, just be aware that if the record goes out of scope, then it looses the reference to the object (unless you add code otherwise).
I've used that StringList example you are referring too, and that works great to have a record manage the lifetime of a TStringList. You can adapt that to your usage. The key is the embedded Interface which frees the object when it goes out of scope with the record.
You can also look at Allen Bauer's Nullable record example. I included the code, but you will want to read the article (and comments) too. It uses Generics in Delphi 2009 or newer, but you can adapt it to earlier versions of Delphi. Again the key is the interface, but he takes a different approach.
unit Foo;
interface
uses Generics.Defaults, SysUtils;
type
Nullable<T> = record
private
FValue: T;
FHasValue: IInterface;
function GetValue: T;
function GetHasValue: Boolean;
public
constructor Create(AValue: T);
function GetValueOrDefault: T; overload;
function GetValueOrDefault(Default: T): T; overload;
property HasValue: Boolean read GetHasValue;
property Value: T read GetValue;
class operator NotEqual(ALeft, ARight: Nullable<T>): Boolean;
class operator Equal(ALeft, ARight: Nullable<T>): Boolean;
class operator Implicit(Value: Nullable<T>): T;
class operator Implicit(Value: T): Nullable<T>;
class operator Explicit(Value: Nullable<T>): T;
end;
procedure SetFlagInterface(var Intf: IInterface);
implementation
function NopAddref(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopRelease(inst: Pointer): Integer; stdcall;
begin
Result := -1;
end;
function NopQueryInterface(inst: Pointer; const IID: TGUID; out Obj): HResult; stdcall;
begin
Result := E_NOINTERFACE;
end;
const
FlagInterfaceVTable: array[0..2] of Pointer =
(
#NopQueryInterface,
#NopAddref,
#NopRelease
);
FlagInterfaceInstance: Pointer = #FlagInterfaceVTable;
procedure SetFlatInterface(var Intf: IInterface);
begin
Intf := IInterface(#FlagInterfaceInstance);
end;
{ Nullable<T> }
constructor Nullable<T>.Create(AValue: T);
begin
FValue := AValue;
SetFlagInterface(FHasValue);
end;
class operator Nullable<T>.Equal(ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue = ARight.HasValue;
end;
class operator Nullable<T>.Explicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
function Nullable<T>.GetHasValue: Boolean;
begin
Result := FHasValue <> nil;
end;
function Nullable<T>.GetValue: T;
begin
if not HasValue then
raise Exception.Create('Invalid operation, Nullable type has no value');
Result := FValue;
end;
function Nullable<T>.GetValueOrDefault: T;
begin
if HasValue then
Result := FValue
else
Result := Default(T);
end;
function Nullable<T>.GetValueOrDefault(Default: T): T;
begin
if not HasValue then
Result := Default
else
Result := FValue;
end;
class operator Nullable<T>.Implicit(Value: Nullable<T>): T;
begin
Result := Value.Value;
end;
class operator Nullable<T>.Implicit(Value: T): Nullable<T>;
begin
Result := Nullable<T>.Create(Value);
end;
class operator Nullable<T>.NotEqual(const ALeft, ARight: Nullable<T>): Boolean;
var
Comparer: IEqualityComparer<T>;
begin
if ALeft.HasValue and ARight.HasValue then
begin
Comparer := TEqualityComparer<T>.Default;
Result := not Comparer.Equals(ALeft.Value, ARight.Value);
end else
Result := ALeft.HasValue <> ARight.HasValue;
end;
end.
It will work, but you'll have to free it manually. And since records clean themselves up automatically when they go out of scope, and don't have destructors, making sure you do it right can be a hassle. You're better off not using objects in records. If you need a data type that contains objects, why not make it an object too?
Any solution for a record correctly lifetime-managing a string list object will involve an interface in one way or another. So why not return an interface from your function in the first place? Add properties to the interface, and for the consuming code it will look like record fields. It will allow you to easily add more "record fields" later on, and you can put arbitrarily complex code in the getters that return the values.
Another issue to be aware of, if you use sizeof to determine the memory footprint of the record, it will only include the size of a pointer for the TStringList. If you attempt to stream it out, the pointer which is stored will NOT be available to later instances, so you would have to ignore the pointer on the load and have another method to load the Tstringlist.
For example:
Procedure SaveRecToStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
Stream.Write(Rec,SizeOf(Rec)-SizeOf(tSTringList));
Rec.List.saveToStream(Stream);
end;
Procedure LoadRecFromStream(Rec: TItemDetails ; Stream:tStream);
var
i : integer;
begin
FillMemory(#Rec,SizeOf(Rec),0);
i := Stream.Read(rec,SizeOf(Rec)-SizeOf(tStringList));
if i <> SizeOf(Rec)-SizeOf(tStringList) then
Raise Exception.create('Unable to load record');
Rec.List := tStringlist.create;
Rec.List.LoadFromStream(Stream);
end;
This assumes that each stream contains exactly one record, and that the record variable passed to LoadRecFromStream does not contain a live tStringlist (if it was previously used it must be freed prior to the call or a leak occurs).
Why not use something like
type PStringList = ^TStringList;
type TMyFreakyRecord = record
PointerToAStringList : PStringList;
// some more code here
end;
...
var x : TMyFreakyRecord;
stringlist : TStringList;
begin
stringList := TStringlist.create;
stringList.Add('any data you wish');
x.PointertoaStringList := #stringlist;
// some more code here
end;
and access the record's string list like
procedure ProcedureThatPasses(AFreakyRecord: TFreakyRecord);
var i : integer;
begin
for i := 0 to AFreakyRecord.PointerToAStringList.count -1 do
// something with AFreakyRecord.PointerToAStringList[i];
end;
in order to transparently free the memory allocated you can create a TList variable in which you add every variable of type TStringList that is used inside a record,
var frmMain : TfrmMain;
MyJunkList : TList;
...
implementation
...
procedure clearjunk;
var i : integer;
o : TObject;
begin
for i := MyJunkList.count -1 downto 0 do begin
o := MyJunkList[i];
FreeandNil(o);
end;
MyJunkList.clear;
end;
...
initialization
MyJunkList := TList.Create;
finalization
clearjunk;
FreeAndNil(MyJunkList );
end. // end of unit
if this helps, don't hesitate to visit http://delphigeist.blogspot.com/