Want to load name value pairs into a TStringList.
have a text file with data:
a=ant
a=animal
b=bob
d=darn
Function to load it:
LoadData(argPath: string): TStringList;
var
data: TStringList;
begin
data := TStringList.Create(true);
data.Delimiter := '=';
try
data.LoadFromFile(argPath);
except on E : Exception do
begin
Application.MessageBox(PWideChar(E.Message),
'Unable to Load Data', MB_OK or MB_ICONERROR);
Application.Terminate;
end;
end;
Result := data;
end;
Result is currently:
a=ant
a=animal
b=bob
d=darn
want the Result's strings to be:
ant
animal
bob
darn
and the Result's TObjects to be
a
a
b
d
After this I want to be able to display this in a TComboBox.
You'll have to use the built-in functions in the TStringList. I've broken this down into variables just for ease of reading / understanding, but could be compacted:
var
X: Integer;
Name, Value: String;
begin
for X:= 0 to MyList.Count - 1 do begin
Name:= MyList.Names[X];
Value:= MyList.ValueFromIndex[X];
MyCombo.Items.Add(Value);
end;
end;
With that, I'm sure you can figure out the rest (since I'm not sure exactly what you mean by result strings and objects).
It's preferable to pass the string list as a parameter, to ease lifetime management. It's also better to let the function raise exceptions and deal with them later. That allows more flexibility and the possibility of reuse. Like this:
procedure LoadValues(const FileName: string: Values: TStringList);
var
i: Integer;
begin
Values.LoadFromFile(FileName);
for i := 0 to Values.Count - 1 do
if Pos(Values.NameValueSeparator, Values[i]) <> 0 then
Values[i] := Values.ValueFromIndex[i];
end;
You cannot store strings directly in the Objects[] property of a TStrings because strings are managed types. You should probably store the names in a separate string list, or another more capable data structure. It's easy to extend the above code to populate two lists instead of one using the Names[] property.
It sounds like you are trying to stuff all of your data into a visual control. Don't do that. Treat visual controls as things to display data and not to hold and manage it.
Gave up on trying to load the combo box with LoadFromFile. Ended up creating two separate lists.
In FormCreate handler:
LoadData(...)
myComboBox.Items := displayList;
LoadData
procedure MyForm.LoadData(
const path: string; data: TStringList);
var index : Integer;
begin
data.LoadFromFile(path);
for index := 0 to data.Count - 1 do
begin
displayList.Add(data.ValueFromIndex[index]);
valueList.Add(data.Names[index]);
end;
end;
Whenever you need to get the selected value:
valueList[myComboBox.ItemIndex]
Related
I have a StringList with 2 rows
aOldPriceTerms[0] = 'FuelAddition=336643160'
aOldPriceTerms[1] = 'Freight=336643155'
So it works fine to use
aOldPriceTerms.Values['Freight'] -> '336643155'
I want to have a list of ID's from the list.
So simply
'FuelAddition','Freight'
Currently I use this code where aOldPriceTerms is the actual StringList.
function GetList(aOldPriceTerms: TStringList): String;
var
vOldTerm : string;
vOldTermsList : TStringList;
begin
vOldTermsList := TStringList.Create;
try
for i := aOldPriceTerms.Count - 1 downto 0 do
begin
vOldTerm := Copy(aOldPriceTerms[i], 1, (Pos('=', aOldPriceTerms[i])-1));
vOldTermsList.Add(vOldTerm);
end;
Result := vOldTermsList.CommaText;
finally
FreeAndNil(vOldTermsList);
end;
end;
My question is there a cleaner way to get the ids ?
Example is from the Delphi Basics, but TStringList.Names is also described in the Delphi documentation
// Now display all name and age pair values
for i := 0 to names.Count-1 do
begin
ShowMessage(names.Names[i]+' is '+names.ValueFromIndex[i]);
end;
You can use the TALNVStringList (NV for nameValue) from alcinoe (https://github.com/Zeus64/alcinoe) to handle Name and Value without all the time splitting the string
TALNVStringList is exactly the same as TStringList (nothing to change in the code except replacing TstringList by TALNVStringList) except that it's more efficient in speed because it's store the name in one dedicated field and the value in another dedicated field (no need to do all the time pos('=') and copy() to retrieve the name and the value of the row)
for i := 0 to aOldPriceTerms.Count-1 do
begin
ShowMessage(aOldPriceTerms.Names[i]+' is '+aOldPriceTerms.ValueFromIndex[i]);
end;
Exe demo showing the speed penalty of classic TstringList: https://svn.code.sf.net/p/alcinoe/code/demos/ALSortedListBenchmark/win32/ALSortedListBenchmark.exe
To get all the id's
for name in names.Names do
begin
i := names.IndexOf[name];
end;
or
To get all the Values
for name in vOldTermsList.Names do
begin
Value := vOldTermsList.Value[name];
end;
I need move the data stored in a array of bytes to a set of records located in a TList, but i'm getting this error
E2197 Constant object cannot be passed as var parameter
This code reproduce the issue.
uses
System.Generics.Collections,
System.SysUtils;
type
TData = record
Age : Byte;
Id : Integer;
end;
//this code is only to show the issue, for simplicity i'm filling only the first
//element of the TList but the real code needs fill N elements from a very big array.
var
List : TList<TData>;
P : array [0..1023] of byte;
begin
try
List:=TList<TData>.Create;
try
List.Count:=1;
//here i want to move the content of the P variable to the element 0
Move(P[0],List[0], SizeOf(TData));
finally
List.Free;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
How i can copy the contents of a buffer to a TList Element
In XE2, the internal storage for TList<T> is opaque and hidden. You cannot gain access to it by normal means. All access to elements of the list are copied – references to the underlying storage are not available. So you cannot blit to it using Move. If you want a structure that you can blit to, you should consider a dynamic array, TArray<T>.
You can always use the trick of implementing a class helper for TList<TData> that would expose the private variable FItems. That's pretty hacky but will do what you ask.
type
__TListTData = TList<TData>;
//defeat E2086 Type 'TList<T>' is not yet completely defined
type
TListTDataHelper = class helper for TList<TData>
procedure Blit(const Source; Count: Integer);
end;
procedure TListTDataHelper.Blit(const Source; Count: Integer);
begin
System.Move(Source, Pointer(FItems)^, Count*SizeOf(Self[0]));
end;
I guess you might want to put some parameter checking in TListTDataHelper.Blit, but I'll leave that to you.
If you were using XE3, you could access the private storage of TList<T> by using the List property.
Move(P, Pointer(List.List)^, N*SizeOf(List[0]));
If you don't need to blit and can use a for loop then do it like this:
type
PData = ^TData;
var
i: Integer;
Ptr: PData;
....
List.Count := N;
Ptr := PData(#P);
for i := 0 to List.Count-1 do
begin
List[i] := Ptr^;
inc(Ptr);
end;
But I interpret your question that you wish to avoid this option.
Instead of using Move(), try using the TList<T>.Items[] property setter instead and let the compiler and RTL handle the copying for you:
type
PData = ^TData;
...
List[0] := PData(#P[0])^;
I have a procedure for sorting nodes in a node tree (VirtualTreeView)
All memory leaks, extracted from FMM4 report, are stored in objects of a class TMemoryLeakList(these are the list I want to sort), which are stored in a list of lists called TGroupedMemoryLeakList, and both TMLL and TGMLL extend TObjectList. If I want to keep the functionality of being able to chose between ascending and descending sort order and choosing between sorting by one of four different data types, I 'have to' implement EIGHT different comparison methods (4 sort types * 2 sort directions) which I pass on to the main sorting method, because my TMLL list extends TObjectList. The main sorting method look like this.
The values for the fields fSortType and fSortDirection are acquired from the GUI comboboxes.
One of those eight generic comparison functions looks like this.
The seven remaining are copy/pasted variations of this one.
Is there any rational way to refactor this huge amount of copy paste code and still keep the functionality of choosing a specific sort type and direction?
Nice question about refactoring, but I dislike the answer you presumably are looking for. There is nothing wrong with a few extra lines of code, or a few extra routines. Especially the latter in which case naming actively assist in more readability.
My advice would be: leave the design as you have, but shorten the code:
function CompareSizeAsc(Item1, Item2: Pointer): Integer;
begin
Result := TMemoryLeak(Item2).Size - TMemoryLeak(Item1).Size;
end;
function CompareSizeDesc(Item1, Item2: Pointer): Integer;
begin
Result := TMemoryLeak(Item1).Size - TMemoryLeak(Item2).Size;
end;
function CompareClassNameAsc(Item1, Item2: Pointer): Integer;
begin
Result := CompareStr(TMemoryLeak(Item1).ClassName,
TMemoryLeak(Item2).ClassName);
end;
procedure TMemoryLeakList.Sort;
begin
case FSortDirection of
sdAsc:
case FSortType of
stSize: inherited Sort(CompareSizeAsc);
stClassName: inherited Sort(CompareClassNameAsc);
stCallStackSize: inherited Sort(CompareCallStackSizeAsc);
stId: inherited Sort(CompareIdAsc);
end;
sdDesc:
case FSortType of
stSize: inherited Sort(CompareSizeDesc);
stClassName: inherited Sort(CompareClassNameDesc);
stCallStackSize: inherited Sort(CompareCallStackSizeDesc);
stId: inherited Sort(CompareIdDesc);
end;
end;
end;
You can't get it much smaller then this ánd preserve the same level of readability.
Of course, you could rewrite the Sort routine as suggested by Arioch 'The:
procedure TMemoryLeakList.Sort;
const
Compares: array[TSortDirection, TSortType] of TListSortCompare =
((CompareSizeAsc, CompareClassNameAsc, CompareCallStackSizeAsc,
CompareIdAsc), (CompareSizeDesc, CompareClassNameDesc,
CompareCallStackSizeDesc, CompareIdDesc));
begin
inherited Sort(Compares[FSortDirection, FSortType]);
end;
But then: why not rewrite the QuickSort routine to eliminate the need for separate compare routines?
Alternatively, you could add ownership to TMemoryLeak in which case you have a reference to the owning list and its sort direction and sort type, for use within óne single compare routine.
Use function pointers.
var comparator1, comparator2: function (Item1, Item2: Pointer): Integer;
function sortComplex (Item1, Item2: Pointer): Integer;
begin
Result := comparator1(Item1, Item2);
if 0 = Result then Result := comparator2(Item1, Item2);
end;
Then you GUI elements should behave like
case ListSortType.ItemIndex of
itemBySzie : comparator1 := sortBySizeProcAsc;
....
end;
DoNewSort;
PS: make sure that you correctly specify those pointers even before user 1st click any GUI element;
PPS: you can rearrange this even further like
type t_criteria = (bySize, byName,...);
t_comparators = array[t_criteria] of array [boolean {Descending?}]
of function (Item1, Item2: Pointer): Integer;
const comparator1table: t_comparators =
( {bySize} ( {false} sortBySizeProcAsc, {true} sortBySizeProcDesc),
{byName} ( {false} sortByNameProcAsc, ...
Then you would fill working pointers from that array constants
This is my solution. Apart from completely rewriting the two procedures I also added two 'static' variables to my TMemoryLeakList class, and removed the former instance variables of the same name. This way, they are globally accessible to the Sort function.
TMemoryLeakList=class(TObjectList)
class var fSortType :TMlSortType;
class var fSortDirection :TMLSortDirection;
...
end
procedure TMemoryLeakList.Sort;
begin
inherited sort(sortBySomethingSomething);
end;
function sortBySomethingSomething(Item1, Item2: Pointer): Integer;
var
a, b : string;
ret : Integer;
begin
ret := 1;
if(TMemoryLeakList.fSortDirection = sdAsc) then
ret := -1;
case TMemoryLeakList.fSortType of stSize:
begin
a := IntToStr(TMemoryLeak(Item1).Size);
b := IntToStr(TmemoryLeak(Item2).Size);
end;
end;
case TMemoryLeakList.fSortType of stClassName:
begin
a := TMemoryLeak(Item1).ClassName;
b := TMemoryLeak(Item2).ClassName;
end;
end;
case TMemoryLeakList.fSortType of stID:
begin
a := IntToStr(TMemoryLeak(Item1).ID);
b := IntToStr(TMemoryLeak(Item2).ID);
end;
end;
case TMemoryLeakList.fSortType of stCallStackSize:
begin
a := IntToStr(TMemoryLeak(Item1).CallStack.Count);
b := IntToStr(TMemoryLeak(Item2).CallStack.Count);
end;
end;
//...jos tu
if a=b then
Result:=0
else if a>b then
Result:=-1*ret
else if a<b then
Result:=1*ret;
end;
I would like to rewrite this solution so as to use instance bounded variables fSortType,fSortDirection in TMemoryLeakList, but it seems impossible to pass a member function to an inherited sort function (from TObjectList), or is it?
I have defined a record which has lots of fields with different types (integer, real , string, ... plus dynamic arrays in terms of "array of ...").
I want to save it as a whole to a file and then be able to load it back to my program. I don't want to go through saving each field's value individually.
The file type (binary or ascii or ...) is not important as long Delphi could read it back to a record.
Do you have any suggestions?
You can load and save the memory of a record directly to and from a stream, as long as you don't use dynamic arrays. So if you use strings, you need to make them fixed:
type TTestRecord = record
FMyString : string[20];
end;
var
rTestRecord: TTestRecord;
strm : TMemoryStream;
strm.Write(rTestRecord, Sizeof(TTestRecord) );
You can even load or save an array of record at once!
type TRecordArray = array of TTestRecord;
var ra : TRecordArray;
strm.Write(ra[0], SizeOf(TTestRecord) * Length(ra));
In case you want to write dynamic content:
iCount := Length(aArray);
strm.Write(iCount, Sizeof(iCount) ); //first write our length
strm.Write(aArray[0], SizeOf * iCount); //then write content
After that, you can read it back:
strm.Read(iCount, Sizeof(iCount) ); //first read the length
SetLength(aArray, iCount); //then alloc mem
strm.Read(aArray[0], SizeOf * iCount); //then read content
As promised here it is: https://github.com/KrystianBigaj/kblib
When you defined for example record as:
TTestRecord = record
I: Integer;
D: Double;
U: UnicodeString;
W: WideString;
A: AnsiString;
Options: TKBDynamicOptions;
IA: array[0..2] of Integer;
AI: TIntegerDynArray;
AD: TDoubleDynArray;
AU: array of UnicodeString;
AW: TWideStringDynArray;
AA: array of AnsiString;
R: array of TTestRecord; // record contain dynamic array of itself (D2009+)
end;
You can save whole dynamic record to stream (as binary data) by :
TKBDynamic.WriteTo(lStream, lTestRecord, TypeInfo(TTestRecord));
To load it back:
TKBDynamic.ReadFrom(lStream, lTestRecord, TypeInfo(TTestRecord));
It not need to be a record, you can do same for any dynamic type like:
TKBDynamic.WriteTo(lStream, lStr, TypeInfo(UnicodeString));
TKBDynamic.WriteTo(lStream, lInts, TypeInfo(TIntegerDynArray));
TKBDynamic.WriteTo(lStream, lArrayOfTestRecord, TypeInfo(TArrayOfTestRecord)); // TArrayOfTestRecord = array of TTestRecord;
Tested on Delphi 2006/2009/XE. License: MPL 1.1/GPL 2.0/LGPL 3.0
See readme for information.
Another option which works very well for records (Delphi 2010+) is to use the SuperObject library. For example:
type
TData = record
str: string;
int: Integer;
bool: Boolean;
flt: Double;
end;
var
ctx: TSuperRttiContext;
data: TData;
obj: ISuperObject;
sValue : string;
begin
ctx := TSuperRttiContext.Create;
try
sValue := '{str: "foo", int: 123, bool: true, flt: 1.23}';
data := ctx.AsType<TData>(SO(sValue));
obj := ctx.AsJson<TData>(data);
sValue := Obj.AsJson;
finally
ctx.Free;
end;
end;
I also tested this briefly with a simple TArray<Integer> dynamic array and it did not have a problem storing and loading the array elements.
In addition to the answers that indicate how you do this, please also be aware of these:
You must be aware that writing records out to a file will be Delphi version specific (usually: specific to a series of Delphi versions that share the same memory layout for the underlying data types).
You can only do that if your record does not contain fields of a managed type. Which means that fields cannot be of these managed types: strings, dynamic arrays, variants, and reference types (like pointers, procedural types, method references, interfaces or classes) and file types, or types that contain those manages types. Which basically limits to to these unmanaged types:
A: Simple types (including bytes, integers, floats, enumerations, chars and such)
B: Short strings
C: Sets
D: Static arrays of A, B, C, D and E
E: Records of A, B, C, D and E
In stead of writing out records to/from a file, it might be better to go with class instances and convert them to/from JSON, and them write the JSON string equivalent to a file and read it back in.
You can use this unit to do the JSON conversion for you (should work with Delphi 2010 and up; works for sure with Delphi XE and up) from this location this location.
unit BaseObject;
interface
uses DBXJSON, DBXJSONReflect;
type
TBaseObject = class
public
{ public declarations }
class function ObjectToJSON<T : class>(myObject: T): TJSONValue;
class function JSONToObject<T : class>(json: TJSONValue): T;
end;
implementation
{ TBaseObject }
class function TBaseObject.JSONToObject<T>(json: TJSONValue): T;
var
unm: TJSONUnMarshal;
begin
if json is TJSONNull then
exit(nil);
unm := TJSONUnMarshal.Create;
try
exit(T(unm.Unmarshal(json)))
finally
unm.Free;
end;
end;
class function TBaseObject.ObjectToJSON<T>(myObject: T): TJSONValue;
var
m: TJSONMarshal;
begin
if Assigned(myObject) then
begin
m := TJSONMarshal.Create(TJSONConverter.Create);
try
exit(m.Marshal(myObject));
finally
m.Free;
end;
end
else
exit(TJSONNull.Create);
end;
end.
I hope this helps you getting an overview of things.
--jeroen
You could also define an object instead of a record, so you can use RTTI to save your object to XML or whatever. If you have D2010 or XE, you can use DeHL to serialize it:
Delphi 2010 DeHL Serialization XML and custom attribute : how it work?
But if you "google" you can find other libs with RTTI and serialization (with D2007 etc)
Another solution, working from Delphi 5 up to XE, is available as an OpenSource unit.
In fact, it implements:
some low-level RTTI functions for handling record types: RecordEquals, RecordSave, RecordSaveLength, RecordLoad;
a dedicated TDynArray object, which is a wrapper around any dynamic array, able to expose TList-like methods around any dynamic array, even containing records, strings, or other dynamic arrays. It's able to serialize any dynamic array.
Serialization uses an optimized binary format, and is able to save and load any record or dynamic array as RawByteString.
We use this in our ORM, to store high-level types like dynamic array properties into a database back-end. First step to a DB-Sharding architecture.
If you have dynamic strings or array you can't write the record "as a whole". Instead of using old style-25 characters max strings, I would add methods to the record to be able to "stream" itself to a stream, or better using a TFiler descendant:
TMyRec = record
A: string;
B: Integer;
procedure Read(AReader: TReader);
procedure Writer(AWriter: TWriter);
end;
procedure TMyrec.Read(AReader: TReader);
begin
A := AReader.ReadString;
B := AReader.ReadInteger;
end;
Codes from delphibasics :
type
TCustomer = Record
name : string[20];
age : Integer;
male : Boolean;
end;
var
myFile : File of TCustomer; // A file of customer records
customer : TCustomer; // A customer record variable
begin
// Try to open the Test.cus binary file for writing to
AssignFile(myFile, 'Test.cus');
ReWrite(myFile);
// Write a couple of customer records to the file
customer.name := 'Fred Bloggs';
customer.age := 21;
customer.male := true;
Write(myFile, customer);
customer.name := 'Jane Turner';
customer.age := 45;
customer.male := false;
Write(myFile, customer);
// Close the file
CloseFile(myFile);
// Reopen the file in read only mode
FileMode := fmOpenRead;
Reset(myFile);
// Display the file contents
while not Eof(myFile) do
begin
Read(myFile, customer);
if customer.male
then ShowMessage('Man with name '+customer.name+
' is '+IntToStr(customer.age))
else ShowMessage('Lady with name '+customer.name+
' is '+IntToStr(customer.age));
end;
// Close the file for the last time
CloseFile(myFile);
end;
The problem with saving a record containing dynamic array or real strings (or other "managed" types for that matter) is, it's not an big blob of memory containing everything, it's more like a tree. Someone or something needs to go over everything and save it to storage, somehow. Other languages (Python for example) include all sorts of facilities to transform most objects to text (serialize it), save it to disk and reload it (deserialize it).
Even though a Embarcadero-provided solution doesn't exist for Delphi, one can be implemented using the extended RTTI available in Delphi 2010. A ready-made implementation is available in the DeHL library (here's a blog post about it) - but I can't say much about the implementation, I never used DeHL.
An other option is the one you want to avoid: manually serialize the record to an TStream; It's actually not half difficult. Here's the kind of code I usually use to read/write objects to a file stream:
procedure SaveToFile(FileName:string);
var F:TFileStream;
W:TWriter;
i:Integer;
begin
F := TFileStream.Create(FileName, fmCreate);
try
W := TWriter.Create(F, 128);
try
// For every field that needs saving:
W.WriteString(SomeStr);
W.WriteInteger(TheNumber);
// Dynamic arrays? Save the length first, then save
// every item. The length is needed when reading.
W.WriteInteger(Length(DArray));
for i:=0 to High(DArray) do
W.WriteString(DArray[i]);
finally W.Free;
end;
finally F.Free;
end;
end;
procedure ReadFromFile(FileName:string);
var F:TFileStream;
R:TReader;
i,n:Integer;
begin
F := TFileStream.Create(FileName, fmOpenRead);
try
R := TReader.Create(F, 128);
try
SomeStr := R.ReadString;
TheNumber := R.ReadInteger;
// Reading the dynamic-array. We first get the length:
n := R.ReadInteger;
SetLength(DArray, n);
// And item-by-item
for i:=0 to n-1 do
DArray[i] := R.ReadString;
finally R.Free;
end;
finally F.Free;
end;
end;
I have the following scenario:
procedure SomeMethod(SomeList: array of string);
I have to call this method with some IDs from a DataSet, I know I can do it this way:
var
MyArray: array of string;
I: Integer;
begin
SetLength(MyArray, MyDataSet.RecordCount);
I := 0;
MyDataSet.First;
while not MyDataSet.Eof do
begin
MyArray[I] := MyDataSetID.Value;
Inc(I);
MyDataSet.Next;
end;
SomeMethod(MyArray);
end;
I'm lazy as hell and this is too much work for my liking... I want an easier way for doing this, any ideas?
That's about the only way I can think of to do it. However, why can't you just take what you already have and turn it into a function? You'd just have to call it when you need it.
If you need to use it with different datasets, simply pass the dataset and the field to use as parameters:
procedure FieldToArray(const DS: TDataSet; const FieldName: string;
const Arr: TStringArray);
var
i: Integer;
begin
SetLength(Arr, DS.RecordCount);
i := 0;
DS.First;
while not DS.Eof do
begin
Arr[i] := DS.FieldByName(FieldName).AsString;
Inc(i);
DS.Next;
end;
end;
Somewhere visible (maybe in the interface section of the unit that declares this function), declare the TStringArray type:
type
TStringArray = array of string;
Now in your code, anywhere you need that list of field values:
var
MyArray: TStringArray;
FieldToArray(MyDataSet, 'ID', MyArray);
SomeProc(MyArray);
FieldToArray(AnotherDataSet, 'LastName', MyArray);
SomeOtherProc(MyArray);
MyArray := nil;
As far as I know there is no easier way built into Delphi, but there are two things which could make your life easier:
Write your own DataSetToArray function. (As proposed by the other answers)
If you are using Delphi 2007 or newer you can add a ToArray function to the dataset using a helper class:
TDataSetHelper = class helper for TDataSet
function ToArray: TStringArray;
end;
That way all your datasets would have this function and you could just use:
SomeMethod(MyDataset.ToArray);
In addition to the answer of Ken White. Repeatedly calling FieldByName can be a serious performance drain. So you can use a variable to store the field (which is updated with the dataset).
procedure FieldToArray(const DS: TDataSet; const FieldName: string;
const Arr: TStringArray);
var
i: Integer;
field : TField;
begin
SetLength(Arr, DS.RecordCount);
i := 0;
DS.First;
field := DS.FieldByName(FieldName);
while not DS.Eof do
begin
Arr[i] := field.AsString;
DS.Next;
Inc(i);
end;
end;
I would advise to never use TDataSet.RecordCount unless you know exactly how the data may change at any given time, and what data access you will use in the future. First getting the number of records and then loop through them has the following problems:
If this is not executed in one transaction it may be possible that the number of records changes while you are executing your loop. This can lead to bad crashes - the array may actually be too short.
If a SQL server is behind the TDataSet then you may end up fetching the result set twice, once for getting the number of records, then for fetching the data.
For that reason I don't see the benefit of using an array of strings over a TStrings object. And calling TStrings.Add() in a loop is about the shortest code you can have.
I am also a lazy programmer. I used to be employed as a VB6 programmer and I sort of liked all their CInt, CStr, CSomethingOrOther functions and I set up some similar ones in my own common unit. One of the first ones I did was the CArray function and I made several versions of it using the OVERRIDE declaration. I have one for use with StringLists, one for delimited strings, and also one for datasets. So I might do something like
while not DS.Eof do
begin
MyValue := CArray(DS)[4];
DS.Next;
end;
I cheated on this a bit because I used the index of the field. So my solution sort of sucks in that regard, but it's kind of nice being able to use CArray for a lot of different scenarios and it turns things like StringLists and datasets into things you can put a watch on and check for when you get to a specific value.
Does it absolutely have to be "an array of strings"? Just about anything you can do with a dynamic array, you can use a List for and do it more easily. Here's how I'd do it
procedure SomeMethod(SomeList: TStringList);
...
var
MyList: TStringList;
begin
MyList := TStringList.Create;
MyDataSet.First;
while not MyDataSet.Eof do
begin
MyList.add(MyDataSetID.Value);
MyDataSet.Next;
end;
SomeMethod(MyList);
end;
Make sure you free your TStringList when you're done with it, of course.