Is it possible to use records as a method parameter, and call it without implicitly declaring an instance of said record?
I would like to be able to write code like this.
type
TRRec = record
ident : string;
classtype : TClass;
end;
procedure Foo(AClasses : array of TRRec);
then calling the method like this or something similar.
Foo([('Button1', TButton), ('Lable1', TLabel)]);
I'm still stuck on Delphi 5 by the way.
Yes. Almost.
type
TRRec = record
ident : string;
classtype : TClass;
end;
function r(i: string; c: TClass): TRRec;
begin
result.ident := i;
result.classtype := c;
end;
procedure Foo(AClasses : array of TRRec);
begin
;
end;
// ...
Foo([r('Button1', TButton), r('Lable1', TLabel)]);
It is also possible to work with a const array, but it isn't so flexible as the solution given by "gangph":
(especially that you have to give the size ([0..1]) of the array in the array declaration. The records are anomymous, the array isn't).
type
TRRec = record
ident : string;
classtype : TClass;
end;
procedure Foo(AClasses : array of TRRec);
begin
end;
const tt: array [0..1] of TRRec = ((ident:'Button1'; classtype:TButton),
(ident:'Lable1'; classtype:TLabel));
Begin
Foo(tt);
end.
Related
How to store record to temporary variable and pass it through function?
If I have two records like:
TMyRec1 = packed record
SomeValue : Integer;
end;
TMyRec2 = packed record
ThisIsMessage : String;
end;
And now I want to be able to do something like this:
function GetRec(recId: Integer) : Variant;
begin
case (recId) of
1 : Result := TMyRec1.Create();
2 : Result := TMyRec2.Create();
//... many
else
end;
end;
And also to return it back to original type like:
function GetRec1(rec: Variant) : TMyRec1;
begin
Result := TMyRec1(rec);
// here I do lots of default things with this record type
end;
function GetRec2(rec: Variant) : TMyRec2;
begin
Result := TMyRec2(rec);
// here I do lots of default things with this record type
end;
Finally an complete function should be able to do the following:
procedure MainFunction();
var myRec : Variant; //I want to avoid to specify each T here
begin
myRec := GetRec(1);
PrintRec1(GetRec1(myRec));
myRec := GetRec(2);
PrintRec2(GetRec2(myRec));
end;
procedure PrintRec1(rec: TMyRec1);
begin
Print(IntToStr(rec.SomeValue));
end;
procedure PrintRec2(rec: TMyRec2);
begin
Print(rec.ThisIsMessage);
end;
I have tried with Variant, TObject, NativeUInt casting but nothing seem to work.
Thank you for any help.
EDIT
TMyRec = record
end;
TMyRec1 = TMyRec
SomeValue : Integer;
end;
TMyRec2 = TMyRec
ThisIsMessage : String;
end;
Can be done something like this?
I don't need safety checking and rising exceptions I will take care of that to make sure I pass correct one where required.
A record does not have a Create() constructor by default, like a class does, so TMyRec1.Create() and TMyRec2.Create() will not work as shown.
But, in Delphi 2006 and later, you can manually add a static Create() method that returns a new record instance (several of Delphi's own native RTL records do this, such as TFormatSettings, TRttiContext, etc), eg:
TMyRec1 = packed record
SomeValue : Integer;
class function Create: TMyRec1; static;
end;
TMyRec2 = packed record
ThisIsMessage : String;
class function Create: TMyRec2; static;
end;
...
class function TMyRec1.Create: TMyRec1;
begin
Result.SomeValue := ...;
end;
class function TMyRec2.Create: TMyRec2;
begin
Result.ThisIsMessage := ...;
end;
Otherwise, for earlier versions, you will have to use standalone functions, eg:
TMyRec1 = packed record
SomeValue : Integer;
end;
TMyRec2 = packed record
ThisIsMessage : String;
end;
function CreateRec1: TMyRec1;
function CreateRec2: TMyRec2;
...
function CreateRec1: TMyRec1;
begin
Result.SomeValue := ...;
end;
function CreateRec2: TMyRec2;
begin
Result.ThisIsMessage := ...;
end;
But, either way, know that by default you can't just store arbitrary record types in a Variant, it doesn't know how to store and retrieve them. You have to teach it how to do that. You do that by deriving a class from TCustomVariantType and override its various operational methods for casting, comparing, etc, and then register that class with the RTL so that the Variant infrastructure knows about it. See Defining Custom Variants in Delphi's documentation for more details about that. Only then will your GetRec(), GetRec1(), and GetRec2() functions be able to work exactly as you have coded them.
Otherwise, consider an alternative approach, for instance defining a custom tagged record, similar to how Variant works internally, eg:
PMyRec1 = ^TMyRec1;
TMyRec1 = packed record
SomeValue : Integer;
end;
PMyRec2 = ^TMyRec2;
TMyRec2 = packed record
ThisIsMessage : String;
end;
TMyRec = record
case Tag: Integer of
1 : (Rec1: PMyRec1);
2 : (Rec2: PMyRec2);
...
end;
function GetRec(recId: Integer) : TMyRec;
begin
Result.Tag := recId;
case recId of
1 : New(Result.Rec1);
2 : New(Result.Rec2);
...
else
raise ...;
end;
end;
function DisposeRec(var rec: TMyRec);
begin
case rec.Tag of
1 : Dispose(rec.Rec1);
2 : Dispose(rec.Rec2);
...
end;
rec.Tag := 0;
end;
function GetRec1(var rec: TMyRec) : TMyRec1;
begin
if rec.Tag <> 1 then raise ...;
Result := rec.Rec1^;
// here I do lots of default things with this record type
end;
function GetRec2(var rec: TMyRec) : TMyRec2;
begin
if rec.Tag <> 2 then raise ...;
Result := rec.Rec2^;
// here I do lots of default things with this record type
end;
procedure MainFunction;
var
myRec : TMyRec;
begin
myRec := GetRec(1);
try
PrintRec1(GetRec1(myRec));
finally
DisposeRec(myRec);
end;
myRec := GetRec(2);
try
PrintRec2(GetRec2(myRec));
finally
DisposeRec(myRec);
end;
end;
procedure PrintRec1(const rec: TMyRec1);
begin
Print(IntToStr(rec.SomeValue));
end;
procedure PrintRec2(const rec: TMyRec2);
begin
Print(rec.ThisIsMessage);
end;
I'm trying to convert a generic variable of type T into string.
TMyTest = class
class function GetAsString<T>(const AValue : T) : string; static;
end;
...
uses
System.Rtti;
class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
Result := TValue.From<T>(AValue).ToString();
end;
It works good using several types (like Integer, Double, Boolean...) but it "fails" using Variant variables.
procedure TForm1.FormCreate(Sender: TObject);
var
Tmp : Variant;
begin
Tmp := 123;
ShowMessage(TMyTest.GetAsString<Variant>(Tmp));
end;
It produces the following output:
(variant)
I was expecting the same output obtained by the VarToStr function (But I cannot use that function with generic variables):
123
You can check whether T is variant and then use VarToStr on AsVariant function.
You can easily extend that function to cater for other types where ToString will not give you expected result.
uses
System.TypInfo, System.Rtti;
class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
if PTypeInfo(TypeInfo(T)).Kind = tkVariant then
Result := VarToStr(TValue.From<T>(AValue).AsVariant)
else
Result := TValue.From<T>(AValue).ToString();
end;
You can check the type of T using RTTI, and then call VarToStr() when T is a Variant, eg:
class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
if TypeInfo(T) = TypeInfo(Variant) then begin
// yes, the following cast looks odd, but the compiler can't validate
// T is really a Variant in this context, as it syntax-checks the code
// *before* instantiating the Generic, so a runtime cast is needed.
// The TypeInfo check above will ensure the cast is safe...
Result := VarToStr({AValue}PVariant(#AValue)^);
end else begin
Result := TValue.From<T>(AValue).ToString;
end;
end;
Or, in XE7+, you can use the GetTypeKind() intrinsic instead:
class function TMyTest.GetAsString<T>(const AValue : T) : string;
begin
if GetTypeKind(T) = tkVariant then begin
Result := VarToStr({AValue}PVariant(#AValue)^);
end else begin
Result := TValue.From<T>(AValue).ToString;
end;
end;
Either approach will allow the compiler to optimize the code by dropping the unused branch from the executable, as both comparisons are treated as compile-time constants.
Note: other TTypeKind values types that TValue.ToString does not support are tkUnknown, tkArray, tkRecord, and tkDynArray.
I want to use SQL like condition in Delphi as
if VarI in SOMESET then
...
[SOMESET] to read from any text file.
Store few numbers in any text file like ini/txt and read it from file and replace it with set so that we can use in operator with if condition.
If I understand what you are asking correctly, the simple answer is yes. Your terminology may be a bit off - set means something quite specific in Delphi, but I don't think that is what you mean. Also, I don't think you are asking about the specifics of loading and saving so I have not included that. Instead I think you are asking how to allow 'in' to be defined in this specific case, and here is how
unit Unit1;
interface
uses
System.SysUtils;
type
TMySet = record
private
function GetEntry(const i: integer): integer;
public
Entries : array of integer;
// procedure LoadFromFile( const pFromFile : TFileName );
class operator in ( const pTest : integer; pMyset : TMyset ) : boolean;
property Entry[ const i : integer ] : integer
read GetEntry; default;
end;
implementation
{ TMySet }
function TMySet.GetEntry(const i: integer): integer;
begin
Result := Entries[ i ]; // allow default exceptions to occur
end;
class operator TMySet.in(const pTest: integer; pMyset: TMyset): boolean;
var
i: Integer;
begin
for i := Low(pMyset.Entries) to High(pMyset.Entries) do
begin
if i = pMyset[ i ] then
begin
Result := TRUE;
exit;
end;
end;
// else
Result := FALSE;
end;
end.
I hope I have understood your question, and that this helps.
Arrays can be indexed using user-defined enumerated types. For example:
type
TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);
var
MyArray: array[Low(TIndexValue) .. High(TIndexValue)] of String;
Elements from this array can then be referenced using TIndexValue values as an index:
MyArray[ZERO] := 'abc';
I am trying to obtain this same general functionality with a TStringList.
One simple solution is to cast every index value to an Integer type at the time of reference:
MyStringList[Integer(ZERO)] := 'abc';
Another solution (to hide all the casting) is to create a subclass of TStringList and defer all the casting to this subclass's subroutines that access the inherited Strings property:
type
TIndexValue = (ZERO = 0, ONE, TWO, THREE, FOUR);
type
TEIStringList = class(TStringList)
private
function GetString(ItemIndex: TIndexValue): String;
procedure SetString(ItemIndex: TIndexValue; ItemValue: String);
public
property Strings[ItemIndex: TIndexValue]: String
read GetString write SetString; default;
end;
function TEIStringList.GetString(ItemIndex: TIndexValue): String;
begin
Result := inherited Strings[Integer(ItemIndex)];
end;
procedure TEIStringList.SetString(ItemIndex: TIndexValue; ItemValue: String);
begin
inherited Strings[Integer(ItemIndex)] := ItemValue;
end;
This works fine for a single implementation that uses the enumerated type TIndexValue.
However, I would like to re-use this same logic or subclass for several different TStringList objects that are indexed by different enumerated types, without having to define TStringList subclasses for each possible enumerated type.
Is something like this possible? I suspect I may have to depend on Delphi's Generics, but I would be very interested to learn that there are simpler ways to achieve this.
I think that generics would be by far the most elegant solution. Using them would be as simple as rewriting your class above as:
TEIStringList<T> = class(TStringList)
and then replacing all TIndexValue references with T. Then you could create it just as any other generic:
var
SL: TEIStringList<TIndexValue>;
begin
SL:=TEIStringList<TIndexValue>.Create;
(...)
ShowMessage(SL[ZERO])
(...)
end;
If you insist on avoiding generics, maybe operator overloading would be of use. Something like the following should work:
type
TIndexValueHolder = record
Value : TIndexValue;
class operator Implicit(A: TMyRecord): integer;
end;
(...)
class operator TIndexValueHolder.Implicit(A: TMyRecord): integer;
begin
Result:=Integer(A);
end;
Then use with:
var
Inx : TIndexValueHolder;
begin
Inx.Value:=ZERO;
ShowMessage(SL[Inx]);
end
UPDATE:
You could adapt TIndexValueHolder for use in a for or while loop by adding Next, HasNext, etc. methods. This might end defeating the purpose, though. I'm still not sure what the purpose is, or why this would be useful, but here's some ideas for how to do it, anyways.
You probably can use a class helper and declare the default property index as Variant:
type
TEnum1 = (Zero = 0, One, Two, Three, Four);
TEnum2 = (Nul = 0, Een, Twee, Drie, Vier);
TEnum3 = (Gds = 0, Psajs, Oeroifd, Vsops, Wowid);
TStringListHelper = class helper for TStringList
private
function GetString(Index: Variant): String;
procedure SetString(Index: Variant; const Value: String);
public
property Strings[Index: Variant]: String read GetString write SetString;
default;
end;
function TStringListHelper.GetString(Index: Variant): String;
begin
Result := inherited Strings[Index];
end;
procedure TStringListHelper.SetString(Index: Variant; const Value: String);
begin
inherited Strings[Index] := Value;
end;
Testing code:
procedure TForm1.Button1Click(Sender: TObject);
var
Strings: TStringList;
begin
Strings := TStringList.Create;
try
Strings.Add('Line 1');
Strings.Add('Second line');
Strings[Zero] := 'First line';
Memo1.Lines.Assign(Strings);
Caption := Strings[Psajs];
finally
Strings.Free;
end;
end;
See edit history for a previous less successful attempt.
If I am trying to call a procedure which has a record type (not object) as a parameter, is it possible to somehow pass details of that parameter "inline" without having to declare a variable of that type first?
eg assume I have this simple record type:
type TMyRecord = record
AString: string;
AnInt: Integer;
end;
and this procedure declaration:
procedure MyProcedure(Rec: TMyRecord);
If I want to call MyProcedure do I have to declare a variable of type TMyRecord or can I do something like:
MyProcedure(TMyRecord("Test", 10));
That doesn't work (XE2) (get a compiler error about it expecting a ")").
So, can I do something like that? Or not possible.
Thanks
It is possible using the advanced record structure.
For more information about advanced records, see the Records (advanced) section in Delphi help.
This is a small prototype to see how it works in your case to preinitialize a record in a function/procedure call :
Type
TRecord = record
AString : String;
AnInt : Integer;
Constructor Create( Const s : String; i : Integer);
end;
constructor TRecord.Create(const s: String; i: Integer);
begin
AString := s;
AnInt := i;
end;
procedure DoSomething( theRec : TRecord);
begin
WriteLn(theRec.AString, ' ',theRec.AnInt);
end;
begin
DoSomeThing( TRecord.Create('S',1));
ReadLn;
end.
Looking at the Delphi RTL, see the definitions of the record types TPoint and TRect in unit system.types (XE2).
They define some overloaded Create constructors, which are used in lots of places to preinitialize the record structures in function/procedure calls.
The question you are asking relates to code readability and there is a solution that avoids having to create a variable. The VCL uses this solution with the records TPoint and TRect.
Consider the definition of TPoint:
type
TPoint = record
X,Y integer
end;
To pass a TPoint to a procedure you might do:
var
MyPoint : TPoint;
begin
MyPoint.X := 5;
MyPoint.Y := 7;
DoSomething( MyPoint );
end;
This is fine but takes 3 lines when one is also possible using the factory function Point:
begin
DoSomething( Point(5,7) );
end;
In Delphi, a function has been declared as follows:
function Point( X, Y : integer ) : TPoint;
begin
Result.X := X;
Result.Y := Y;
end;
You can then call this function 'inline' to create the record 'on the fly' to to quickly
You will see the same has been provided for TRect etc. I often put such a factory function together with the record declaration as follows, even if I don't plan to use them yet:
type
TMyRecord = record
A : integer;
B : string;
end;
function MyRecord( A : integer; const B : string ) : TMyRecord;
begin
Result.A := A;
Result.B := B;
end;
Use of this technique can improved the readability of code and also ensures that you don't accidently omit setting a record element.
Just having fun with John Easley's idea:
type TRec = record
X: string;
Y: Integer;
end;
procedure TestRec(const Rec: array of const);
var
R: TRec;
begin
R.X:= string(Rec[0].VUnicodeString);
R.Y:= Rec[1].VInteger;
ShowMessage(R.X + IntToStr(R.Y));
end;
procedure TForm1.Button7Click(Sender: TObject);
begin
TestRec(['Test', 22]);
end;
It is possible to pass record fields as array of const parameters and assign these parameters to local record variable.
It would be nice! But, no.
If passing things inline is really your objective, then perhaps Open Array Parameters would suit you.
Procedure MyProcedure(const Vars: Array of Variant);
begin
ShowMessage(VarToStr(Vars[0])+' '+VarToStr(Vars[1]));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
MyProcedure(['Test', 12]);
end;
You could also pass an Array of Const, which is basically an array of TVarRec which is a variant record that also includes type information as VType. This is fun stuff..
An excellent article can be found on Rudy's Delphi Corner here:
Rudy's Delphi Corner, Open Array Parameters