AFAIK we cannot assign directly values to record members if the said record is in a generic structure.
For example, having:
type
TMyRec = record
Width: integer;
Height: integer;
end;
var
myList: TList<TMyRec>;
...
myList[888].Width:=1000; //ERROR here: Left side cannot be assigned.
...
Till now, I used a temporary variable in order to overcome this:
var
...
temp: TMyRec;
...
begin
...
temp:=myList[999];
temp.Width:=1000;
myList[999]:=temp;
...
end;
Ugly, slow, but works. But now I want to add to TMyRec a dynamic array:
type
TMyRec = record
Width: integer;
Height: integer;
Points: array or TPoint;
end;
...or any other data structure which can became big so copying back and forth in a temporary variable isn't a feasible option.
The question is: How to change a member of a record when this record is in a generic structure without needing to copy it in a temporary var?
TIA for your feedback
A dynamic array variable is just a reference to the array. It's stored in the record as a single pointer. So you can continue with your current approach without any excessive copying. Copying an element to a temporary variable only copies a reference to the array and does not copy the array's contents. And even better, if you are assigning to the array's items then you don't need the copy at all. You can write:
myList[666].Points[0] := ...
If you do have a record that really is big then you would be better using a class rather than a record. Because an instance of a class is a reference, the same argument as above applies. For this approach you may prefer TObjectList<> to TList<>. The advantage of TObjectList<> is that you can set the OwnsObjects property to True and let the list be in charge of destroying its members.
You could then write
var
myList: TObjectList<TMyClass>
....
myList[666].SomeProp := NewValue;
Related
How can I use record in TDictionary?
TMyRec = record
a: Integer;
b: Integer;
end;
...
dictionary = TDictionary<String, TMyRec>.create();
...
dictionary[key].a := 30;<<<
Here the compiler gives an error: "Left side cannot be assigned to". How can I solve this problem without creating a separate function for writing myFunc(a, b: Integer): TMyRec?
dictionary[key] returns a copy of the record held by the dictionary. The compiler prevents you from modifying that because it would serve no purpose.
As an aside, older versions of the program would accept your code and it was very confusing that the modification to the record would be lost. You'd make an assignment but nothing visible changed because what you assigned was a nameless local variable.
Clearly you intend to modify the record held in the collection. In order to do that you need to assign the entire record. Read the record from the collection into a local variable. Modify the local variable. Write the updated value back to the collection. Like so:
var
rec: TMyRec;
...
rec := dictionary[key];
rec.a := 30;
dictionary[key] := rec;
One of the frustrating aspects of this is that the code needs to perform two dictionary lookups, even though we know that the second one will find the same record as the first one. Not even the mighty Spring4d dictionary can do this with a single lookup.
David Heffernans answer is what you're after, but I would like to offer an additional warning. Records can have properties just like classes, with getters and setters, and if your record has such properties your code will compile, but it will still not change the actual record value.
TMyRec = record
private
FA : integer;
procedure SetA(const Value: integer);
function GetA : integer;
public
{ Warning: When used on result from dictionary lookup, only the COPY will be
altered, not the actual record in the dictionary! }
property A : integer read GetA write SetA;
end;
A very simple workaround is to use the List property of the record.
You can say:
dictionary.list[key].a := 30;
This will access the dynamic array that backs up the TList via the List property. The compiler already supports direct access to a dynamic array.
If you can login to quality.embarcadero.com, you can see the full discussion of this issue raised as: RSP-23136: We should be able to assign a value to one element in a list of records - posted Dec 18, 2018 and resolved Nov 21, 2019.
The issue was closed with the comment:
"This works as expected. Alternative coding style was provided."
I want to use one keys for two values in Delphi some thing like this
TDictionary<tkey, tfirstvalue,tsecondvalue>;
Put your values into a compound structure like a record. Then use that record type as your dictionary value type.
Delphi has not Tuple type.
I don't know your purpose but may dynamic array of record type help.
Type
Tdict_ = reocord
tkey:integer;
tfirstvalue,Tsecondvalue :string;
end;
var
Tdict:array of tdict_
...
procedure adddata(Tkey:integer;tfirstvalue:string;Tsecondvalue :string);
begin
setlength(tdict,length(tdict)+1);
tdict[length(tdict)-1].tkey:=tkey;
tdict[length(tdict)-1].tfirstvalue:=tfirstvalue;
tdict[length(tdict)-1].tsecondtvalue:=tsecondvalue;
end;
but you must write your own "find" function for return index of array .
for example
Function find(tkey:integer):integer;
var i:Integer;
begin
for i:=0 to length(Tdict)-1 do
if tdict[i].tkey=i then
begin
result:=i;
break;
end;
end;
Function deletecalue(tkey:integer):integer;
var i,j:Integer;
begin
i:=find(tkey)
for j:=i to length(Tdict)-2 do
tdict[j]:=tdict[j+1];
setlength(tdict,length(tdict)-1);
end;
if keys are strings type must be changed, but it will be slow for huge date .
Also read This:
https://github.com/malcolmgroves/generics.tuples
TDictionary<TKey, TPair<TFirstValue, TSecondValue>>, as #RudyVelthuis commented, is possible and works. However, TPair (found in System.Generics.Collections) is not meant for that - the two values are named Key and Value, which doesn't make sense here. We should make a copy of TPair, which could be named TTuple.
You can then do MyDictionary.Add('key', TTuple<string, string>.Create('firstvalue', 'secondvalue'));
Since it is implemented as a record (value type), there is no need to free it.
type
TSomeRecord = Record
field1: integer;
field2: string;
field3: boolean;
End;
var
SomeRecord: TSomeRecord;
SomeRecAr: array of TSomeRecord;
This is the most basic example of what I have and since I want to reuse SomeRecord (with certain fields remaining empty, without freeing everything some fields would be carried over when I'm reusing SomeRecord, which is obviously undesired) I am looking for a way to free all of the fields at once. I've started out with string[255] and used ZeroMemory(), which was fine until it started leaking memory, that was because I switched to string. I still lack the knowledge to get why, but it appears to be related to it being dynamic. I am using dynamic arrays as well, so I assume that trying ZeroMemory() on anything dynamic would result in leaks. One day wasted figuring that out. I think I solved this by using Finalize() on SomeRecord or SomeRecAr before ZeroMemory(), but I'm not sure if this is the proper approach or just me being stupid.
So the question is: how to free everything at once? does some single procedure exist at all for this that I'm not aware of?
On a different note, alternatively I would be open to suggestions how to implement these records differently to begin with, so I don't need to make complicated attempts at freeing stuff. I've looked into creating records with New() and then getting rid of it Dispose(), but I have no idea what it means when a variable after a call to Dispose() is undefined, instead of nil. In addition, I don't know what's the difference between a variable of a certain type (SomeRecord: TSomeRecord) versus a variable pointing to a type (SomeRecord: ^TSomeRecord). I'm looking into the above issues at the moment, unless someone can explain it quickly, it might take some time.
Assuming you have a Delphi version that supports implementing methods on a record, you could clear a record like this:
type
TSomeRecord = record
field1: integer;
field2: string;
field3: boolean;
procedure Clear;
end;
procedure TSomeRecord.Clear;
begin
Self := Default(TSomeRecord);
end;
If your compiler doesn't support Default then you can do the same quite simply like this:
procedure TSomeRecord.Clear;
const
Default: TSomeRecord=();
begin
Self := Default;
end;
You might prefer to avoid mutating a value type in a method. In which case create a function that returns an empty record value, and use it with the assignment operator:
type
TSomeRecord = record
// fields go here
class function Empty: TSomeRecord; static;
end;
class function TSomeRecord.Empty: TSomeRecord;
begin
Result := Default(TSomeRecord);
end;
....
Value := TSomeRecord.Empty;
As an aside, I cannot find any documentation reference for Default(TypeIdentifier). Does anyone know where it can be found?
As for the second part of your question, I see no reason not to continue using records, and allocating them using dynamic arrays. Attempting to manage the lifetime yourself is much more error prone.
Don't make thinks overcomplicated!
Assigning a "default" record is just a loss of CPU power and memory.
When a record is declared within a TClass, it is filled with zero, so initialized. When it is allocated on stack, only reference counted variables are initialized: others kind of variable (like integer or double or booleans or enumerations) are in a random state (probably non zero). When it will be allocated on the heap, getmem will not initialize anything, allocmem will fill all content with zero, and new will initialize only reference-counted members (like on the stack initialization): in all cases, you should use either dispose, either finalize+freemem to release a heap-allocated record.
So about your exact question, your own assumption was right: to reset a record content after use, never use "fillchar" (or "zeromemory") without a previous "finalize". Here is the correct and fastest way:
Finalize(aRecord);
FillChar(aRecord,sizeof(aRecord),0);
Once again, it will be faster than assigning a default record. And in all case, if you use Finalize, even multiple times, it won't leak any memory - 100% money back warranty!
Edit: After looking at the code generated by aRecord := default(TRecordType), the code is well optimized: it is in fact a Finalize + bunch of stosd to emulate FillChar. So even if the syntax is a copy / assignement (:=), it is not implemented as a copy / assignment. My mistake here.
But I still do not like the fact that a := has to be used, where Embarcadero should have better used a record method like aRecord.Clear as syntax, just like DelphiWebScript's dynamic arrays. In fact, this := syntax is the same exact used by C#. Sounds like if Embacardero just mimics the C# syntax everywhere, without finding out that this is weird. What is the point if Delphi is just a follower, and not implement thinks "its way"? People will always prefer the original C# to its ancestor (Delphi has the same father).
The most simply solution I think of will be:
const
EmptySomeRecord: TSomeRecord = ();
begin
SomeRecord := EmptySomeRecord;
But to address all the remaining parts of your question, take these definitions:
type
PSomeRecord = ^TSomeRecord;
TSomeRecord = record
Field1: Integer;
Field2: String;
Field3: Boolean;
end;
TSomeRecords = array of TSomeRecord;
PSomeRecordList = ^TSomeRecordList;
TSomeRecordList = array[0..MaxListSize] of TSomeRecord;
const
EmptySomeRecord: TSomeRecord = ();
Count = 10;
var
SomeRecord: TSomeRecord;
SomeRecords: TSomeRecords;
I: Integer;
P: PSomeRecord;
List: PSomeRecordList;
procedure ClearSomeRecord(var ASomeRecord: TSomeRecord);
begin
ASomeRecord.Field1 := 0;
ASomeRecord.Field2 := '';
ASomeRecord.Field3 := False;
end;
function NewSomeRecord: PSomeRecord;
begin
New(Result);
Result^.Field1 := 0;
Result^.Field2 := '';
Result^.Field3 := False;
end;
And then here some multiple examples on how to operate on them:
begin
// Clearing a typed variable (1):
SomeRecord := EmptySomeRecord;
// Clearing a typed variable (2):
ClearSomeRecord(SomeRecord);
// Initializing and clearing a typed array variabele:
SetLength(SomeRecords, Count);
// Creating a pointer variable:
New(P);
// Clearing a pointer variable:
P^.Field1 := 0;
P^.Field2 := '';
P^.Field3 := False;
// Creating and clearing a pointer variable:
P := NewSomeRecord;
// Releasing a pointer variable:
Dispose(P);
// Creating a pointer array variable:
ReallocMem(List, Count * SizeOf(TSomeRecord));
// Clearing a pointer array variable:
for I := 0 to Count - 1 do
begin
Pointer(List^[I].Field2) := nil;
List^[I].Field1 := 0;
List^[I].Field2 := '';
List^[I].Field3 := False;
end;
// Releasing a pointer array variable:
Finalize(List^[0], Count);
Choose and/or combine as you like.
With SomeRecord: TSomeRecord, SomeRecord will be an instance/variable of type TSomeRecord. With SomeRecord: ^TSomeRecord, SomeRecord will be a pointer to a instance or variable of type TSomeRecord. In the last case, SomeRecord will be a typed pointer. If your application transfer a lot of data between routines or interact with external API, typed pointer are recommended.
new() and dispose() are only used with typed pointers. With typed pointers the compiler doesn't have control/knowlegde of the memory your application is using with this kind of vars. It's up to you to free the memory used by typed pointers.
In the other hand, when you use a normal variables, depending on the use and declaration, the compiler will free memory used by them when it considers they are not necessary anymore. For example:
function SomeStaff();
var
NativeVariable: TSomeRecord;
TypedPointer: ^TSomeRecord;
begin
NaviveVariable.Field1 := 'Hello World';
// With typed pointers, we need to manually
// create the variable before we can use it.
new(TypedPointer);
TypedPointer^.Field1 := 'Hello Word';
// Do your stuff here ...
// ... at end, we need to manually "free"
// the typed pointer variable. Field1 within
// TSomerecord is also released
Dispose(TypedPointer);
// You don't need to do the above for NativeVariable
// as the compiler will free it after this function
// ends. This apply also for native arrays of TSomeRecord.
end;
In the above example, the variable NativeVariable is only used within the SomeStaff function, so the compiler automatically free it when the function ends. This appy for almost most native variables, including arrays and records "fields". Objects are treated differently, but that's for another post.
I have this record (structure):
type
THexData = record
Address : Cardinal;
DataLen : Cardinal;
Data : string;
end;
And I've declared this list:
HexDataList: TList<THexData>;
I've filled the list with some data. Now I'd like scan ListHexData and sometimes update a element of a record inside HexDataList.
Is it possible? How can I do?
var
Item: THexData;
...
for i := 0 to HexDataList.Count-1 do begin
Item := HexDataList[i];
//update Item
HexDataList[i] := Item;
end;
The bind is that you would like to modify HexDataList[i] in place, but you can't. When I'm working with a TList<T> that holds records I actually sub-class TList<T> and replace the Items property with one that returns a pointer to the item, rather than a copy of the item. That allows for inplace modification.
EDIT
Looking at my code again I realise that I don't actually sub-class TList<T> because it the class is too private to extract pointers to the underlying data. That's probably a good decision. What I actually do is implement my own generic list class and that allows me the freedom to return pointers to records if needed.
I want to create a bunch of records (RWell) and to store them in an array in a certain order. Then I want to create a new array (different layout) and rearange the records in it.
Of course, I don't want to duplicate data in RAM so I though that in the second array I should put pointers to the records in the first array. However, I can't do that. Anybody can tell what's wrong with the code below?
Thanks
Type
RWell= record
x: string;
i: integer;
end;
PWell= ^RWell;
RWellArray= Array[0..12, 0..8] of RWell;
procedure TClass1.CreateWells
var
WellMX: RWellArray;
begin
{ should I initialize the WellXM here? }
{ note: WellXM is a static array! }
other stuff
end;
var Wells: array of PWell;
procedure TClass2.AddWell(aWell: RWell);
begin
aWell.Stuff:= stuff; {aWell cannot be readonly because I need to change it here}
SetLength(Wells, Length(Wells)+ 1); { reserve memory }
Wells[High(Wells)]:= #aWell;
end;
procedure TClass3.DisplayWell;
var CurWell: RWell;
begin
CurWell:= CurPrimer.Wells[iCurWell]^; <--- AV here (but in debugger the address is correct)
end;
Solved by Rob K.
In your AddWell function, you're passing the record by value. That means the function gets a copy of the actual parameter. You're storing a pointer to the formal parameter, which is probably just a location on the local stack of the function.
If you want a pointer to a well, then pass a pointer to a well:
procedure AddWell(AWell: PWell);
begin
SetLength(Wells, Length(Wells) + 1);
Wells[High(Wells)] := AWell;
end;
Another option is to pass the record by const value. For records, this means the actual parameter is passed as a reference. A pointer to the formal parameter is also a pointer to the actual parameter:
procedure AddWell(const AWell: RWell);
begin
SetLength(Wells, Length(Wells) + 1);
Wells[High(Wells)] := #AWell;
end;
I wouldn't really rely on that, though. When you want pointers, pass pointers. Some people try to avoid pointers in their code, but they're nothing to be afraid of.