I'm trying to create a generic class to which I can use a set of enums to initiate the values inside. For example:
constructor TManager<TEnum>.Create;
var
enum: TEnum;
enumObj: TMyObject;
begin
fMyObjectList:= TObjectDictionary<TEnum,TMyObject>.Create([doOwnsValues],10);
for enum:= Low(TEnum) to High(TEnum) do
begin
enumObj:= TMyObject.Create();
fMyObjectList.Add(enum, enumObj);
end;
end;
Additionally, later methods will fetch objects, via the enum value, for example:
function TManager<TEnum>.Fetch(enum: TEnum): TMyObject;
begin
fMyObjectList.TryGetValue(enum, Result);
end;
However, passing as a generic parameter, delphi doesn't know that TEnum is going to be an enum. Can I enforce that in some way?
As David mentioned the best you can do is at runtime with RTTI.
type
TRttiHelp = record
class procedure EnumIter<TEnum {:enum}>; static;
end;
class procedure TRttiHelp.EnumIter<TEnum {:enum}>;
var
typeInf: PTypeInfo;
typeData: PTypeData;
iterValue: Integer;
begin
typeInf := PTypeInfo(TypeInfo(TEnum));
if typeInf^.Kind <> tkEnumeration then
raise EInvalidCast.CreateRes(#SInvalidCast);
typeData := GetTypeData(typeInf);
for iterValue := typeData.MinValue to typeData.MaxValue do
WhateverYouWish;
end;
Although I don't know how the code behaves when your enum has defined values such as
(a=9, b=19, c=25)
Edit:
If you would like to return iterValue to the enum, you may use the following function, taken from a enum helper class by Jim Ferguson
class function TRttiHelp.EnumValue<TEnum {:enum}>(const aValue: Integer): TEnum;
var
typeInf: PTypeInfo;
begin
typeInf := PTypeInfo(TypeInfo(TEnum));
if typeInf^.Kind <> tkEnumeration then
raise EInvalidCast.CreateRes(#SInvalidCast);
case GetTypeData(typeInf)^.OrdType of
otUByte, otSByte:
PByte(#Result)^ := aValue;
otUWord, otSWord:
PWord(#Result)^ := aValue;
otULong, otSLong:
PInteger(#Result)^ := aValue;
else
raise EInvalidCast.CreateRes(#SInvalidCast);
end;
end;
You may then use the generically provided as the index to the dictionary in your constructor.
You cannot constrain a generic parameter such that low() and high() can be used in the generic class. The only constraints available are class or interface constraints.
To the very best of my knowledge, the language offers no generic way to enumerate over a generic enumerated type. Probably the best you can do is to use RTTI, sacrificing compile time type safety (as illustrated by Tobias).
Having read the comments to Tobias's answer, it seems likely that what you really want here is TObjectDictionary<TEnum,TMyObject>. That's because you want to be able to find a TMyObject instance given a TEnum key. And you want TObjectDictionary rather than TDictionary because the former takes over ownership of the TMyObject instances. You need somebody to free them all when you are done, and you may as well let TObjectDictionary do it for you.
For an example of the ownership side of this, see #RRUZ's answer here: Example for using Generics.Collections.TObjectDictionary
Related
I am having some trouble figuring out this case of inheritance.
In my class TBalans, I have a routine Initialiseer that takes a TBalPar object as parameter. TBalPar is the ancestor class of TNewBalPar that has additional fields. Now I would like to reach the additional fields from within my TBalans class. I still can feed a TNewBalPar object to the Initialiseer routine, but how do I get to the data of the descendant class?
What I tried is the following: I derived TBalans too into TNieuweBalans, gave it the new additional fields, and assign them in the routine:
type
TBalPar = class
//some vars
end;
TNewBalPar = class(TBalPar)
ExtraVar: TValue;
end;
TBalans = class
MyBalPar: TBalPar;
function Initialiseer(ABalPar: TBalPar): Boolean; virtual;
end;
TNieuweBalans = class(TBalans)
MyBalPar: TNewBalpar; //declared again so I don't need to cast it when using it
MyExtraVar: TValue;
function Initialiseer(ABalPar: TBalPar): Boolean; override;
end;
function TBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
MyBalPar := ABalPar;
end;
function TNieuweBalans.Initialiseer(ABalPar: TBalPar): Boolean;
begin
inherited;
MyBalPar := TNewBalPar(ABalPar);
MyExtraVar := MyBalPar.ExtraVar; //instead of casting TNewBalPar(MyBalPar).ExtraVar
end;
This code works, but it feels wrong: I declare the MyBalPar field twice. I would like to improve on it.
Note that I am not looking for a way how to expose ExtraVar to the outside world, but how to use it conveniently within TNieuweBalans.
How can I eliminate the double MyBalPar field but still prevent frequent typecasting?
Current design
The need for a convenient designated derived field type for an ancestral field is not forbidden, nor uncommon for that matter. But your implementation, like you sense already, has some problems:
the doubled fields require unnecessary memory,
you need to synchronize changes to TBalans.MyBalPar and TNieuweBalans.MyBalPar,
you need to synchronize changes to TNieuweBalans.MyBalPar.ExtraVar and TNieuweBalans.MyExtraVar,
you do not enforce the derived class type: feeding a TBalPar object to TNieuweBalans.Initialiseer results in an access violation because MyBalPar.ExtraVar does not exist.
There are multiple ways to overcome each of these problems.
The most elementary solution to prevent extra fields is to provide properties for them with getters that extract the values from the inherited class (I renamed some of your types and variables for comprehensibility):
type
TBalPar = class(TObject)
// some variables
end;
TBalParEx = class(TBalPar)
private
FExtra: TValue;
public
property Extra: TValue read FExtra write FExtra;
end;
TBalance = class(TObject)
private
FBalPar: TBalPar;
public
function Initialize(ABalPar: TBalPar): Boolean; virtual;
property BalPar: TBalPar read FBalPar;
end;
TBalanceEx = class(TBalance)
private
function GetExtra: TValue;
procedure SetExtra(Value: TValue);
public
function BalPar: TBalParEx;
function Initialize(ABalPar: TBalPar): Boolean; override;
property Extra: TValue read GetExtra write SetExtra;
end;
function TBalanceEx.BalPar: TBalParEx;
begin
Result := TBalParEx(inherited BalPar);
end;
function TBalanceEx.GetExtra: TValue;
begin
Result := BalPar.Extra;
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
BalPar.Extra := Value;
end;
With this approach, there is only one typecast needed and it does not require additional storage.
To enforce TBalanceEx.BalPar to be of type TBalParEx, you could raise an exception in the Initialize routine:
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := True;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
if ABalPar is TBalParEx then
Result := inherited Initialize(ABalPar)
else
raise Exception.Create('Wrong BalPar type');
end;
Of course, this places the sole responsibility of a correct class functioning on the requirement to always call the Initialize routine before any other usage of the other class members. Since that is what initialization obviously is intended for, you could ignore that, but protection against misuse could be added like:
TBalance = class(TObject)
protected
function HasBalPar: Boolean; virtual;
...
TBalanceEx = class(TBalance)
protected
function HasBalPar: Boolean; override;
...
function TBalance.HasBalPar: Boolean;
begin
Result := FBalPar is TBalPar;
end;
function TBalance.Initialize(ABalPar: TBalPar): Boolean;
begin
FBalPar := ABalPar;
Result := HasPalBar;
end;
function TBalanceEx.GetExtra: TValue;
begin
if HasBalPar then
Result := BalPar.Extra
else
Result := nil;
end;
function TBalanceEx.HasBalPar: Boolean;
begin
Result := BalPar is TBalParEx;
end;
function TBalanceEx.Initialize(ABalPar: TBalPar): Boolean;
begin
Result := inherited Initialize(ABalPar);
if Result = False then
raise Exception.Create('Initialization went wrong');
end;
procedure TBalanceEx.SetExtra(Value: TValue);
begin
if HasBalPar then
BalPar.Extra := Value;
end;
In turn, this requires not to forget to implement HasBalPar for each derived class. You could 'protect' against that with:
TBalance = class(TObject)
strict private
function HasBalPar: Boolean;
private
...
TBalanceEx = class(TBalance)
strict private
function HasBalPar: Boolean;
private
...
Design considerations
All in all, making this a robust design requires some work. And your current approach raises the question why you would want to have the Extra field in the TBalanceEx class too. Even why to have a TBalanceEx class at all.
From the naming of your classes, I assume you have the following equivalent: A structure which has structural parameters like build date, owner, location, and you have a specialized structure, say a castle, with additional parameters like the number of towers and whether it has a moat:
TStructureData: Location, BuildDate
TCastleData: Location, BuildDate, TowerCount, HasMoat
TStructure: StructureData
TCastle: StructureData, CastleData
The question you need to answer is whether a structure needs to know if it is a castle, or a palace, a warehouse, a biological or chemical structure. Assume your program evolves to being able to handle all different kinds of structures, then you are always bound to add two classes to your program, resulting in a more and more complex and improvised design which in the end will get you in trouble, if not already. The challenge is to make this a more generalized and abstract design.
For example:
must TStructureData and TStructure be separate classes?
could calculations, analysations, or presentational requests on the specific data be 'outsourced' to the specific class? E.g.: if you add a GetFeatures routine to the TStructureData class, then the TStructure class can request the features of a TCastle without knowing it being a Castle.
...
Think big.
I got strings in database like 'TGroupBox' or 'TEdit' ... now I need to check element against them... how do I enumerate string to type?
I mean something like this:
mystr := 'TGroupBox';
If (page.Controls[0] is mystr) then ...
Of course it won't work, as error appears:
E2015 Operator not applicable to this operand type
How do I do that correctly?
You can verify that
page.Controls[0].ClassName = mystr
using the ClassName property.
But notice that this doesn't do exactly the same thing as the is operator. To see the difference, suppose you have a class TFruit and a subclass TApple. If myFruit is an instance of a TApple, then both myFruit is TApple and myFruit is TFruit will yield true. But of course, the ClassName will still only be TApple.
If you need the full functionality of the is operator, you can make use of the ClassParent property, as suggested by hvd:
function IsDerivedFrom(AClass: TClass; const AClassName: string): boolean;
begin
if not Assigned(AClass) then Exit(false);
result := SameText(AClass.ClassName, AClassName) or
IsDerivedFrom(AClass.ClassParent, AClassName);
end;
To get the class of an object, use the ClassType property:
IsDerivedFrom(page.Controls[0].ClassType, mystr);
The function you are looking for is GetClass located in System.Classes. Be aware that the class has to be registered.
System.Classes.GetClass
For the specific scenario in the question body the answer by Andreas Rejbrand (with assistance from hvd) is a good one. However, for the broader problem implied by the question title - how to I convert a string containing a class name to a class reference? - you can utilise extended RTTI in a new(ish) version of Delphi:
unit ClassLookupUtils;
interface
uses
System.SysUtils, System.Generics.Collections, System.Rtti;
type
RttiClassLookup = record
strict private
class var FMap: TDictionary<string, TClass>;
class destructor Destroy;
public
class function Find(const ClassName: string): TClass; static;
end;
implementation
class destructor RttiClassLookup.Destroy;
begin
FMap.Free;
end;
class function RttiClassLookup.Find(const ClassName: string): TClass;
var
RttiType: TRttiType;
RttiContext: TRttiContext;
begin
if FMap = nil then
begin
FMap := TDictionary<string, TClass>.Create;
for RttiType in RttiContext.GetTypes do
if RttiType is TRttiInstanceType then
FMap.AddOrSetValue(RttiType.Name.ToLowerInvariant, (RttiType as TRttiInstanceType).MetaclassType);
end;
if not FMap.TryGetValue(ClassName.ToLowerInvariant, Result) then
Result := nil;
end;
end.
In use:
var
MyStr: string;
MyStrClass: TClass;
begin
//...
MyStrClass := RttiClassLookup.Find(MyStr);
if MyStrClass <> nil then
for I := 0 to Page.ControlCount - 1 do
if Page.Controls[I].InheritsFrom(MyStrClass) then
begin
//...
end;
The background here is that SomeObj is SomeClass is implemented as (SomeObj <> nil) and SomeObj.InheritsFrom(SomeClass).
You have a good answer from #UweRaabe usingRTTIto getClassName.
A simple (and not very robust) hack without using RTTI would be to use the TComponent.Name property, which is a string, like this - without the is operator:
If (pos('GroupBox', page.Controls[0].name)>0 ) then ...
By default, a control gets the same name as the instance variable, so GroupBox1.name='GroupBox1'. You can either change your database entries to use the substr 'groupbox' or extract 'groupbox' from the type name string in your database.
That being said, if you've inherited this design approach of persisting type names as strings in a database and then using them at runtime to check the types of different components, then you're stuck with it, and so be it. But Delphi is a strongly typed, compiled language, so persisting type names as strings in a database and reading them at runtime and decoding them into Delphi types just doesn't "smell right" IMO. I would re-think this design if possible. Consider doing it all in Delphi using classOf type, enumerations, etc.
in the previous ( remove empty strings from list ) question I asked about the removal of empty strings from a stringlist
....
// Clear out the items that are empty
for I := mylist.count - 1 downto 0 do
begin
if Trim(mylist[I]) = '' then
mylist.Delete(I);
end;
....
From the aspect of code design and reuse I would now prefer a solution being more flexible as :
MyExtendedStringlist = Class(TStringlist)
procedure RemoveEmptyStrings;
end;
Q : Can I use a class helper in this case ? How would this look like in contrast to designing a new class as above ?
A class helper is an excellent idea here. To make it more widely applicable you should choose to associate the helper with the least derived class to which the helper can apply. In this case that means TStrings.
The huge advantage over deriving a new class is that your helper methods are available for instances of TStrings that are not created by you. Obvious examples include the TStrings properties that expose the contents of memos, list boxes etc.
I personally would write a helper that offers a more general removal functionality using a predicate. For instance:
type
TStringsHelper = class helper for TStrings
public
procedure RemoveIf(const Predicate: TPredicate<string>);
procedure RemoveEmptyStrings;
end;
procedure TStringsHelper.RemoveIf(const Predicate: TPredicate<string>);
var
Index: Integer;
begin
for Index := Count-1 downto 0 do
if Predicate(Self[Index]) then
Delete(Index);
end;
procedure TStringsHelper.RemoveEmptyStrings;
begin
RemoveIf(
function(Item: string): Boolean
begin
Result := Item.IsEmpty;
end;
);
end;
More generally, TStrings is an excellent candidate for a class helper. It is missing quite a deal of useful functionality. My helper includes:
An AddFmt method that formats and adds in one go.
An AddStrings method that adds multiple items in one call.
A Contains method that wraps up IndexOf(...)<>-1 and presents a more semantically meaningful method to future readers of code.
A Data[] property, of type NativeInt, and matching AddData method, that wraps the Objects[] property. This hides the casts between TObject and NativeInt.
I'm sure there is more useful functionality that could be added.
You can use a HelperClass, but you should base on TStrings, which would offer more flexibility.
An Example could be:
type
TMyStringsClassHelper = class helper for TStrings
Procedure RemoveEmptyItems;
end;
{ TMyStringsClassHelper }
procedure TMyStringsClassHelper.RemoveEmptyItems;
var
i:Integer;
begin
for i := Count - 1 downto 0 do
if Self[i]='' then Delete(i);
end;
procedure TForm5.Button1Click(Sender: TObject);
var
sl:TStringList;
begin
sl:=TStringList.Create;
sl.Add('AAA');
sl.Add('');
sl.Add('BBB');
sl.RemoveEmptyItems;
Showmessage(sl.Text);
Listbox1.Items.RemoveEmptyItems;
Memo1.Lines.RemoveEmptyItems;
sl.Free;
end;
I have a record that looks like:
TBigint = record
PtrDigits: Pointer; <-- The data is somewhere else.
Size: Byte;
MSB: Byte;
Sign: Shortint;
...
class operator Implicit(a: TBigint): TBigint; <<-- is this allowed?
....
The code is pre-class operator legacy code, but I want to add operators.
I know the data should really be stored in a dynamic array of byte, but I do not want to change the code, because all the meat is in x86-assembly.
I want to following code to trigger the class operator at the bottom:
procedure test(a: TBignum);
var b: TBignum;
begin
b:= a; <<-- naive copy will tangle up the `PtrDigit` pointers.
....
If I add the implicit typecast to itself, will the following code be executed?
class operator TBigint.Implicit(a: TBigint): TBigint;
begin
sdpBigint.CreateBigint(Result, a.Size);
sdpBigint.CopyBigint(a, Result);
end;
(Will test and add the answer if it works as I expect).
My first answer attempts to dissuade against the idea of overriding the assignment operator. I still stand by that answer, because many of the problems to be encountered are better solved with objects.
However, David quite rightly pointed out that TBigInt is implemented as a record to leverage operator overloads. I.e. a := b + c;. This is a very good reason to stick with a record based implementation.
Hence, I propose this alternative solution that kills two birds with one stone:
It removes the memory management risks explained in my other answer.
And provides a simple mechanism to implement Copy-on-Write semantics.
(I do still recommend that unless there's a very good reason to retain a record based solution, consider switching to an object based solution.)
The general idea is as follows:
Define an interface to represent the BigInt data. (This can initially be minimalist and support only control of the pointer - as in my example. This would make the initial conversion of existing code easier.)
Define an implementation of the above interface which will be used by the TBigInt record.
The interface solves the first problem, because interfaces are a managed type; and Delphi will dereference the interface when a record goes out of scope. Hence, the underlying object will destroy itself when no longer needed.
The interface also provides the opportunity to solve the second problem, because we can check the RefCount to know whether we should Copy-On-Write.
Note that long term it might prove beneficial to move some of the BigInt implementation from the record to the class & interface.
The following code is trimmed-down "big int" implementation purely to illustrate the concepts. (I.e. The "big" integer is limited to a regular 32-bit number, and only addition has been implemented.)
type
IBigInt = interface
['{1628BA6F-FA21-41B5-81C7-71C336B80A6B}']
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
end;
type
TBigIntImpl = class(TInterfacedObject, IBigInt)
private
FData: Pointer;
FSize: Integer;
protected
{IBigInt}
function GetData: Pointer;
function GetSize: Integer;
procedure Realloc(ASize: Integer);
function RefCount: Integer;
public
constructor CreateCopy(ASource: IBigInt);
destructor Destroy; override;
end;
type
TBigInt = record
PtrDigits: IBigInt;
constructor CreateFromInt(AValue: Integer);
class operator Implicit(AValue: TBigInt): Integer;
class operator Add(AValue1, AValue2: TBigInt): TBigInt;
procedure Add(AValue: Integer);
strict private
procedure CopyOnWriteSharedData;
end;
{ TBigIntImpl }
constructor TBigIntImpl.CreateCopy(ASource: IBigInt);
begin
Realloc(ASource.GetSize);
Move(ASource.GetData^, FData^, FSize);
end;
destructor TBigIntImpl.Destroy;
begin
FreeMem(FData);
inherited;
end;
function TBigIntImpl.GetData: Pointer;
begin
Result := FData;
end;
function TBigIntImpl.GetSize: Integer;
begin
Result := FSize;
end;
procedure TBigIntImpl.Realloc(ASize: Integer);
begin
ReallocMem(FData, ASize);
FSize := ASize;
end;
function TBigIntImpl.RefCount: Integer;
begin
Result := FRefCount;
end;
{ TBigInt }
class operator TBigInt.Add(AValue1, AValue2: TBigInt): TBigInt;
var
LSum: Integer;
begin
LSum := Integer(AValue1) + Integer(AValue2);
Result.CreateFromInt(LSum);
end;
procedure TBigInt.Add(AValue: Integer);
begin
CopyOnWriteSharedData;
PInteger(PtrDigits.GetData)^ := PInteger(PtrDigits.GetData)^ + AValue;
end;
procedure TBigInt.CopyOnWriteSharedData;
begin
if PtrDigits.RefCount > 1 then
begin
PtrDigits := TBigIntImpl.CreateCopy(PtrDigits);
end;
end;
constructor TBigInt.CreateFromInt(AValue: Integer);
begin
PtrDigits := TBigIntImpl.Create;
PtrDigits.Realloc(SizeOf(Integer));
PInteger(PtrDigits.GetData)^ := AValue;
end;
class operator TBigInt.Implicit(AValue: TBigInt): Integer;
begin
Result := PInteger(AValue.PtrDigits.GetData)^;
end;
The following tests were written as I built up the proposed solution. They prove: some basic functionality, that the copy-on-write works as expected, and that there are no memory leaks.
procedure TTestCopyOnWrite.TestCreateFromInt;
var
LBigInt: TBigInt;
begin
LBigInt.CreateFromInt(123);
CheckEquals(123, LBigInt);
//Dispose(PInteger(LBigInt.PtrDigits)); //I only needed this until I
//started using the interface
end;
procedure TTestCopyOnWrite.TestAssignment;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
CheckEquals(123, LValue2);
end;
procedure TTestCopyOnWrite.TestAddMethod;
var
LValue1: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue1.Add(111);
CheckEquals(234, LValue1);
end;
procedure TTestCopyOnWrite.TestOperatorAdd;
var
LValue1: TBigInt;
LValue2: TBigInt;
LActualResult: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2.CreateFromInt(111);
LActualResult := LValue1 + LValue2;
CheckEquals(234, LActualResult);
end;
procedure TTestCopyOnWrite.TestCopyOnWrite;
var
LValue1: TBigInt;
LValue2: TBigInt;
begin
LValue1.CreateFromInt(123);
LValue2 := LValue1;
LValue1.Add(111); { If CopyOnWrite, then LValue2 should not change }
CheckEquals(234, LValue1);
CheckEquals(123, LValue2);
end;
Edit
Added a test demonstrating use of TBigInt as value parameter to a procedure.
procedure TTestCopyOnWrite.TestValueParameter;
procedure CheckValueParameter(ABigInt: TBigInt);
begin
CheckEquals(2, ABigInt.PtrDigits.RefCount);
CheckEquals(123, ABigInt);
ABigInt.Add(111);
CheckEquals(234, ABigInt);
CheckEquals(1, ABigInt.PtrDigits.RefCount);
end;
var
LValue: TBigInt;
begin
LValue.CreateFromInt(123);
CheckValueParameter(LValue);
end;
There is nothing in Delphi that allows you to hook into the assignment process. Delphi has nothing like C++ copy constructors.
Your requirements, are that:
You need a reference to the data, since it is of variable length.
You also have a need for value semantics.
The only types that meet both of those requirements are the native Delphi string types. They are implemented as a reference. But the copy-on-write behaviour that they have gives them value semantics. Since you want an array of bytes, AnsiString is the string type that meets your needs.
Another option would be to simply make your type be immutable. That would let you stop worrying about copying references since the referenced data could never be modified.
It seems to me your TBigInt should be a class rather than a record. Because you're concerned about PtrDigits being tangled up, it sounds like you're needing extra memory management for what the pointer references. Since records don't support destructors there's no automatic management of that memory. Also if you simply declare a variable of TBigInt, but don't call the CreatBigInt constructor, the memory is not correctly initialised. Again, this is because you cannot override a record's default parameterless constructor.
Basically you have to always remember what has been allocated for the record and remember to manually deallocate. Sure you can have a deallocate procedure on the record to help in this regard, but you still have to remember to call it in the correct places.
However that said, you could implement an explicit Copy function, and add an item to your code-review checklist that TBitInt has been copied correctly. Unfortunately you'll have to be very careful with the implied copies such as passing the record via a value parameter to another routine.
The following code illustrates an example conceptually similar to your needs and demonstrates how the CreateCopy function "untangles" the pointer. It also highlights some of the memory management problems that crop up, which is why records are probably not a good way to go.
type
TMyRec = record
A: PInteger;
function CreateCopy: TMyRec;
end;
function TMyRec.CreateCopy: TMyRec;
begin
New(Result.A);
Result.A^ := A^;
end;
var
R1, R2: TMyRec;
begin
New(R1.A); { I have to manually allocate memory for the pointer
before I can use the reocrd properly.
Even if I implement a record constructor to assist, I
still have to remember to call it. }
R1.A^ := 1;
R2 := R1;
R2.A^ := 2; //also changes R1.A^ because pointer is the same (or "tangled")
Writeln(R1.A^);
R2 := R1.CreateCopy;
R2.A^ := 3; //Now R1.A is different pointer so R1.A^ is unchanged
Writeln(R1.A^);
Dispose(R1.A);
Dispose(R2.A); { <-- Note that I have to remember to Dispose the additional
pointer that was allocated in CreateCopy }
end;
In a nutshell, it seems you're trying to sledgehammer records into doing things they're not really suited to doing.
They are great at making exact copies. They have simple memory management: Declare a record variable, and all memory is allocated. Variable goes out of scope and all memory is deallocated.
Edit
An example of how overriding the assignment operator can cause a memory leak.
var
LBigInt: TBigInt;
begin
LBigInt.SetValue(123);
WriteBigInt(LBigInt); { Passing the value by reference or by value depends
on how WriteBigInt is declared. }
end;
procedure WriteBigInt(ABigInt: TBigInt);
//ABigInt is a value parameter.
//This means it will be copied.
//It must use the overridden assignment operator,
// otherwise the point of the override is defeated.
begin
Writeln('The value is: ', ABigInt.ToString);
end;
//If the assignment overload allocated memory, this is the only place where an
//appropriate reference exists to deallocate.
//However, the very last thing you want to do is have method like this calling
//a cleanup routine to deallocate the memory....
//Not only would this litter your code with extra calls to accommodate a
//problematic design, would also create a risk that a simple change to taking
//ABigInt as a const parameter could suddenly lead to Access Violations.
Is there a way to explore a interface's properties with Rtti?
The following code does not work:
procedure ExploreProps;
var
Ctx: TRttiContext;
RttiType: TRttiType;
RttiProp: TRttiProp;
begin
RttiType := Ctx.GetType(TypeInfo(IMyInterface));
for RttiProp in RttiType.GetProperties do
Writeln(RttiProp.ToString);
end;
Has anyone a solution how to do this correctly?
Interfaces are collections of functions. They don't really have properties the way objects do; that's just a bit of syntactic sugar that the compiler adds for you to make it easier to write code for them. The difference is that on objects, properties allow controlled access to private and protected members, whereas on interfaces, all members are public so there's no need for the properties.
As I known, there is no support for normal interfaces. You could add {$M+} and then try again.
Add this function in your interface
function GetObject: TObject;
and implement it in the class.
the use the GetObject function with RTTI routines
var
obj: IPerson;
begin
obj := TPerson.Create;
Count := GetPropList(obj.GetObject.ClassInfo, tkAny, #List);
end;
Please note that your class should be inherited from TInterfacedPersistent not TInterfacedObject
TPerson = class(TInterfacedPersistent, IPerson)
late answer, but you could typecast your interfae to TObject, e.g.
RttiType := Ctx.GetType(TObject(IMyInterface).ClassInfo);