10

I'd like to define an array type which consists of different types, such as String, Integer, Boolean, Double, etc. but no objects, structures, or anything of that nature. Then I'd like to use this type as a function argument, for example...

type
  TMyArray = array of ...?...;

function GetSomething(const Input: TMyArray): String;
var
  X: Integer;
begin
  for X:= 0 to Length(Input) - 1 do begin
    //Identify type and handle accordingly...
    //Serialize data for the result...

  end;
end;

and use it like...

Variable:= GetSomething(['some string', 123, 'something else', 12.3, false]);

Then, how do I identify what type each element is when iterating through such an array?

I'm pretty sure this is possible, but have no idea even what terminology to search for. How do I do this?

Would I have to define this as an array of Variants? Or is there a way to define exactly which types the array accepts?

EDIT

Not to change the question any, but after the answer by RRUZ, I found an intriguing article about the performance when doing this exact thing different ways...

Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327

6 Answers6

13

If your Delphi version supports RTTI, you can use an array of TValue and the Kind property like so.

{$APPTYPE CONSOLE}


uses
  System.TypInfo,
  System.Rtti,
  System.SysUtils;


function GetSomething(const Input: array of TValue): String;
var
  X: Integer;
  LValue : TValue;
begin
  for LValue in Input  do begin
     case LValue.Kind of
       tkUnknown: Writeln('Unknown');
       tkInteger:  Writeln(Format('The Kind of the element is Integer and the value is %d',[LValue.AsInteger]));
       tkChar: Writeln('Char');
       tkEnumeration: if LValue.TypeInfo=TypeInfo(Boolean) then Writeln(Format('The Kind of the element is Boolean and the value is %s',[BoolToStr(LValue.AsBoolean, True)]));
       tkFloat: Writeln(Format('The Kind of the element is Float and the value is %n',[LValue.AsExtended]));
       tkString: Writeln('String');
       tkSet: Writeln('Set');
       tkClass: Writeln('Class');
       tkMethod:Writeln('method');
       tkWChar: Writeln('WChar');
       tkLString: Writeln('String');
       tkWString: Writeln('String');
       tkVariant: Writeln('Variant');
       tkArray: Writeln('Array');
       tkRecord: Writeln('Record');
       tkInterface: Writeln('Interface');
       tkInt64: Writeln('Int64');
       tkDynArray: Writeln('DynArray');
       tkUString:  Writeln(Format('The Kind of the element is String and the value is %s',[LValue.AsString]));
       tkClassRef:  Writeln('Class Ref');
       tkPointer: Writeln('Pointer');
       tkProcedure:  Writeln('procedure');
     end;
  end;
end;

begin
  try
    GetSomething(['some string', 123, 'something else', 12.3, false]);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.

Another option is use an array of const

{$APPTYPE CONSOLE}

uses
  SysUtils;


procedure GetSomething(const Input: array of const);
var
  LIndex: Integer;
begin
  for LIndex := Low(Input) to High(Input) do
  begin
    case Input[LIndex].VType of
      vtWideString: Writeln('WideString = ''', WideString(Input[LIndex].VWideChar), '''');
      vtInt64: Writeln('Int64 = ', Input[LIndex].VInt64^);
      vtCurrency: Writeln('Currency = ', CurrToStr(Input[LIndex].VCurrency^));
      vtInteger: Writeln('Integer = ', Input[LIndex].VInteger);
      vtBoolean: Writeln('Boolean = ', BoolToStr(Input[LIndex].VBoolean, True));
      vtChar: Writeln('Char = ''', Input[LIndex].VChar, '''');
      vtExtended: Writeln('Extended = ', FloatToStr(Input[LIndex].VExtended^));
      vtString: Writeln('ShortString = ''', Input[LIndex].VString^, '''');
      vtPChar: Writeln('PChar = ''', Input[LIndex].VPChar, '''');
      vtAnsiString: Writeln('AnsiString = ''', Ansistring(Input[LIndex].VAnsiString), '''');
      vtWideChar: Writeln('WideChar = ''', Input[LIndex].VWideChar, '''');
      vtPWideChar: Writeln('PWideChar = ''', Input[LIndex].VPWideChar, '''');
      vtUnicodeString : Writeln('UnicodeString = ''', string(Input[LIndex].VUnicodeString), '''');
    else
      Writeln('Unsupported');
    end;
  end;
end;

begin
  try
    GetSomething(['some string', 123, 'something else', 12.3, false]);
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
  Readln;
end.
Jerry Dodge
  • 26,858
  • 31
  • 155
  • 327
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • 1
    Now besides the fact that it's RTTI, I have to figure out what are the differences between using this or a Variant – Jerry Dodge Sep 18 '13 at 02:09
  • Speaking of which, here's a related reading: http://www.thedelphigeek.com/2010/03/speed-comparison-variant-tvalue-and.html – Jerry Dodge Sep 18 '13 at 02:10
  • +1. I was writing an answer with the VType alternative, and noticed you'd edited just before posting. Refreshed your answer and had to discard mine. I can't vote twice on this one for including both options, either. :-) – Ken White Sep 18 '13 at 02:32
  • I just have to make something clear - can I define these as their own types and use the types as an argument? Or must I define such arrays the way you did in your answer, in-line in the methods? – Jerry Dodge Sep 18 '13 at 02:45
  • There's also `VarArrayOf` (`array of Variant`), but as you want to be able to perform different options based on the type of thing held then `array of const` is probably your better bet (not been lucky(?) enough to work on a modern Delphi that has TValue so couldn't comment). Bear in mind, however that the values are not persistent (you will need to manage your own memory if you want to keep the array as array of const and it goes out of scope). A good article talking about this is http://rvelthuis.de/articles/articles-openarr.html – Matt Allwood Sep 18 '13 at 09:49
3

Oddly enough, nobody has yet mentioned variant records, which have been a feature of Pascal for decades:

type
  TVarRecType = (vrtInteger, vrtDouble {other types go here});
  TVarRec = record
    Field1: string; { can be omitted }
  case RecType: TVarRecType of
    vrtInteger:
      IntValue: integer;
    vrtDouble:
      DblValue: double;
    { other cases go here }
  end;

var
  VarRec: TVarRecType;
begin
  VarRec.Field1 := 'This is an example.';
  VarRec.RecType := vrtInteger;
  VarRec.IntValue := 4711;
  {...}
  VarRec.RecType := wrtDouble;
  VarRec.DblValue := Pi;
  {...}
end;

{ Oops, forgot the array part }
type
  TVarRecArr = array[1..15] of TVarRec;
var
  VarRecArr: TVarRecArr;
begin
  VarRecArr[1].Field1 := 'This is the first record';
  VarRecArr[1].RecType := wrtInteger;
  VarRecArr[1].IntValue := 1;
  {...}
end;
dummzeuch
  • 10,975
  • 4
  • 51
  • 158
  • @Rob: There is Nothing wrong with it. I had actually forgotten about it. It might support too many types for a specific use case, e.g. if you only want to store integers and singles, the custom variant record is only 4+SizeOf(enum) bytes in size. This might matter depending on the size of your array(s). – dummzeuch Sep 25 '13 at 12:19
1

Arrays are homogeneous. As the documentation says:

An array represents an indexed collection of elements of the same type (called the base type).

Therefore you can achieve your goal only by way of a base type that can hold differing types of data. Such a data type is known as a variant data type.

In Delphi there are a number of possibilities for variant data types. There is the venerable COM Variant type. There is the new kid on the block, TValue, which was added to support new style RTTI. And there are many third party options. Typically these third party options exist to support persistence frameworks.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
0

Since is for use as an parameter, you could use the array of const construct. Also known as variant open array parameters. More on my answer on this other question.

It works like your desired array of different types. Delphi docwiki documentation on that theme

Community
  • 1
  • 1
Fabricio Araujo
  • 3,810
  • 3
  • 28
  • 43
0

It's really easy and it's not different between Delphi's versions

first you need to a record of name and type such as this :

Customer = record
OINTCUSTID      : INTEGER ;
CUSTTYPE        : SmallInt;
NAME            : string[30];
end;

and now different your array like this :

Glb_CUSTOMER       :ARRAY [1..20] OF Customer;

Now you have an array with different type.

0

Assuming that in the end you want to get the output of the array as a string (with the procedure GetSomething). I think you can easily do this using Variants.
Define your array like this:

MyArray = array of variant;

The GetSomething procedure now is simple:

function TForm3.GetSomething(const Input: TMyArray): String;
var
  X: Integer;
begin
  for X:= 0 to Length(Input) - 1 do
    //Identify type and handle accordingly...
    //Serialize data for the result...
    Result := Result + VarToStrDef(Input[X], '?') + ' | ';
end;

And the result is the expected.
This line:

Variable:= GetSomething(['some string', 123, 'something else', 12.3, false]);

Return this result:

some string | 123 | something else | 12,3 | False |