Why can some arrays be published but not others? - delphi

type
TStaticArray = array[1..10] of integer;
TDynamicArray = array of integer;
TMyClass = class(TObject)
private
FStaticArray: TStaticArray;
FDynamicArray: TDynamicArray;
published
property staticArray: TStaticArray read FStaticArray write FStaticArray; //compiler chokes on this
property dynamicArray: TDynamicArray read FDynamicArray write FDynamicArray; //compiler accepts this one just fine
end;
What's going on here? A static array gives the error, "published property 'staticArray' cannot be of type ARRAY" but dynamic arrays are just fine? I'm confused. Anyone know the reasoning behind this, and how I can work around it? (And no, I don't want to redeclare all my static arrays as dynamic. They're the size they are for a reason.)

Published declaration tells the compiler to store information in the virtual method table. Only certain kinds of information can be stored.
The type of a published property cannot be a pointer, record, or array. If it is a set type, it must be small enough to be stored in an integer.
(O'REILLY, DELPHİ IN A NUTSHELL)

You have to have getters and setters. Under D2009 (didn't check other versions), the parameters to the getters/setters can't, for some reason, be const. ?
This works fine under D2009:
type
TMyArray = array[0..20] of string;
type
TMyClass=class(TObject)
private
FMyArray: TMyArray;
function GetItem(Index: Integer): String;
procedure SetItem(Index: Integer; Value: string);
public
property Items[Index: Integer]: string read GetItem write SetItem;
end;
implementation
function TMyClass.GetItem(Index: Integer): string;
begin
Result := '';
if (Index > -1) and (Index < Length(FMyArray)) then
Result := FMyArray[Index];
end;
procedure TMyClass.SetItem(Index: Integer; Value: string);
begin
if (Index > -1) and (Index < Length(FMyArray)) then
FMyArray[Index] := Value;
end;
NOTE: I would not typically just ignore Index values out of range, obviously. This was a quick example of how to make static array properties in a class definition; IOW, it's a compilable example only.

The reason why you can publish a dynamic array property, is that dynamic arrays is implemented as references, or 'implicitly pointer'. They work more like strings, really.
The reason why you can't publish a static array, I don't know. It's just the way it's made, I guess..
For more details on how dynamic arrays work, take a look at DrBobs site

TMyClass = class(TObject)
private
FStaticArray: TStaticArray;
FIdx: Integer;
function GetFoo(Index: Integer): Integer;
procedure SetFoo(Index: Integer; Value: Integer);
public
property Foo[Index: Integer] : Integer read GetFoo write SetFoo;
published
property Idx: Integer read fidx write fidx;
property AFoo: Integer read GetAFoo writte SetAFoo;
end;
implementation
function TMyClass.GetAFoo: Integer;
begin
result := FStaticArray[FIdx];
end;
procedure TMyClass.SetAFoo(Value: Integer);
begin
FStaticArray[FIdx] := Value;
end;

Array properties cannot be published.
So the following code does not work. The work around probably is to make it public.
TMyClass = class(TObject)
private
FStaticArray: TStaticArray;
function GetFoo(Index: Integer): Integer;
procedure SetFoo(Index: Integer; Value: Integer);
public
property Foo[Index: Integer] : Integer read GetFoo write SetFoo;
end;

Related

I get an error when using the dynamic byte array in delphide

I'm doing a project with delphi. The project has c # codes. I'm using the byte array in the project. But I get an error. C # codes:
public class DataTransmitEventArgs : EventArgs
{
public DataTransmitEventArgs(byte[] transmittedBytes)
{
fTransmittedBytes = transmittedBytes;
}
byte[] fTransmittedBytes;
public byte[] TransmittedBytes
{
get { return fTransmittedBytes; }
}
}
and my Delphi codes:
DataTransmitEventArgs = class
public
fTransmittedBytes: array of byte;
constructor Create(transmittedBytes: array of byte);overload;
property TransmittedBytes[index: byte]:Byte read fTransmittedBytes;
end;
constructor DataTransmitEventArgs.Create(transmittedBytes: array of byte);
begin
fTransmittedBytes := transmittedBytes;
end;
how do i do these c # codes with delphi
You forgot to specify what error message you receive and on what line (always do that!), but almost certainly it was E2008: Incompatible types at the property line.
According to the documentation, which should always be your first source for information, an array property cannot have fields (like fTransmittedBytes) as its access specifiers (read or write). Instead, they must be methods:
For array properties, access specifiers must list methods rather than fields. The method in a read specifier must be a function that takes the number and type of parameters listed in the property's index parameter list, in the same order, and whose result type is identical to the property's type. The method in a write specifier must be a procedure that takes the number and type of parameters listed in the property's index parameter list, in the same order, plus an additional value or const parameter of the same type as the property.
Let's do an example:
type
TTest = class
private
FData: array of Byte;
public
property Data[Index: Integer]: Byte read FData write FData;
end;
violates this requirement and emits E2008 at the read (and write if you fix the read).
Instead, you must do
type
TTest = class
private
FData: array of Byte;
function GetData(Index: Integer): Byte;
procedure SetData(Index: Integer; const Value: Byte);
public
property Data[Index: Integer]: Byte read GetData write SetData;
end;
{ TTest }
function TTest.GetData(Index: Integer): Byte;
begin
Result := FData[Index];
end;
procedure TTest.SetData(Index: Integer; const Value: Byte);
begin
FData[Index] := Value;
end;
If you want additional safety, do (uses Math)
function TTest.GetData(Index: Integer): Byte;
begin
if InRange(Index, Low(FData), High(FData)) then
Result := FData[Index]
else
raise Exception.CreateFmt('Index out of bounds: %d', [Index]);
end;
procedure TTest.SetData(Index: Integer; const Value: Byte);
begin
if InRange(Index, Low(FData), High(FData)) then
FData[Index] := Value
else
raise Exception.CreateFmt('Index out of bounds: %d', [Index]);
end;
Alternatively, it is quite possible to skip the property and have the field be public:
type
TTest = class
public
Data: array of Byte;
end;
Finally, it is possible to have the field private but a public property of a dynamic array type:
type
TTest = class
private
FData: TArray<Byte>;
public
property Data: TArray<Byte> read FData write FData;
end;
If you are on a pre-generics version of Delphi, TArray<Byte> is obviously not available, so you then have to define your own type: type ByteArray = array of Byte.
In addition, you have
constructor Create(transmittedBytes: array of byte);overload;
which should be
constructor Create(const ATransmittedBytes: array of Byte); overload;
Always use const on open array parameters:
When you pass an array as an open array value parameter, the compiler creates a local copy of the array within the routine's stack frame. Be careful not to overflow the stack by passing large arrays.
Finally, your
fTransmittedBytes := transmittedBytes;
will not compile, either, because you cannot simply assign an open array to a dynamic array.
Instead, you can change the parameter to a dynamic array type:
constructor Create(const ATransmittedBytes: TArray<Byte>); overload;
You might then also want to Copy the array:
FTransmittedBytes := Copy(ATransmittedBytes);
Alternatively, you can use SetLength and a simple for loop to copy the array:
SetLength(FTransmittedBytes, Length(ATransmittedBytes));
for i := 0 to High(ATransmittedBytes) do
FTransmittedBytes[i] := ATransmittedBytes[i];
The lower bound of an open array is always 0.

Assigning to fields in array property in delphi

I have a class which stores generic items in an array. These items are accessed by a default array property:
TMyList<TData> = class
private
items: array of TItem<TData>;
public
function get(position: integer): TData;
procedure edit(position: integer; data: TData);
property Values[position: integer]: TData read get write edit; default;
end;
implementation
function TMyList<TData>.get(position: integer): TData;
begin
result:= items[position];
end;
procedure TMyList<TData>.edit(position: integer; data: TData);
var
item: TItem<TData>;
begin
items[position]:= item;
end;
end;
In this case the items I am storing are all of the type TTest which also has its own properties:
TTest = record
private
FTest: string;
procedure setFTest(const Value: string);
public
property Test: string read FTest write setFTest;
end;
implementation
procedure TColouredPoint.setFTest(const Value: String);
begin
FTest:= Value;
end;
end;
I want to be able to change the value of FTest for an instance of TTest like this:
var
points: TMyList<TTest>;
...
points[index].Test:= 'test';
but this doesn't do anything. There is no error message but the value of points[index].Test doesn't change.
Instead I have to do this:
var
points: TMyList<TTest>;
temp: TTest;
...
temp:= points[index];
temp.Test:= 'test';
points[index]:= temp;
Why does the first version not work?
Why does the first version not work?
Consider this code
points[index].Test := 'test';
The indexed property is converted by the compiler into the a function call and so the compiler effectively compiles this:
points.get(index).Text := 'test';
Now, points.get(index) returns a copy of the TTest value. Since you don't assign that to anything, the compiler introduces a local variable to hold the return value.
So your code becomes, in effect:
var
tmp: TTest;
...
tmp := points.get(index);
tmp.Text := 'test';
That's the last thing that is ever done with tmp, and so the modifications that you make to tmp.Text are simply discarded leaving the underlying object untouched.
This issue is pretty hard to get around when working with value types.
The generic Delphi TList<T> collection allows you direct access to the underlying array, which allows you to operate on the stored values directly.
Another approach is to use a reference rather than a value. One simple way to achieve that is to use a T that is a class rather than a record, i.e. a reference type rather than a value type.
It is because TTest is a record. The getter of the list returns a copy of the actual record and that is what you are changing. It should work when you declare TTest as a class, but then you have to take care of creating and destroying it.

Delphi - Using a function with a property setter

Delphi RIO - have defined a class called TBizObj. One of the properties has to do with DUNS numbers. DUNS numbers are sometimes '0' padded on the left to be exactly 9 characters long, so I have a property called SiteDUNS9 (based off of fSiteDUNS9). The calling program sets the SiteDUNS9 property, but I don't the caller to have to worry about if the DUNS is 9 characters or not, I will handle that in getter/setter properties.
When I define my property to call this function, I get an error 'Incompatible types'. Everything is string... no other types involved. Here is the relevant portion of code:
type
TBizObj = class(TObject)
private
...
fSiteDUNS9: string;
...
function FixDunsLength9(DUNS:string) :string;
published
...
property SiteDUNS9: string read fSiteDUNS9 write FixDunsLength9;
end; // End of the tBizObj Class;
implementation
...
function TBizObj.FixDunsLength9(DUNS:string):string;
begin
// This is a setter function for the DUNS9 routine
result := glib_LeftPad(DUNS, 9, '0');
end;
I have followed the examples on the Embaracadero site but still cannot determine what I am doing wrong.
http://docwiki.embarcadero.com/RADStudio/Rio/en/Properties_(Delphi)
If I change my property definition to
property SiteDUNS9: string read fSiteDUNS9 write fSiteDUNS9;
then my program compiles correctly.
You need to use a procedure instead of a function for a property setter. I would leave the existing function as-is, in case you need it for other purposes, and define a separate procedure for the setter:
type
TBizObj = class(TObject)
private
...
fSiteDUNS9: string;
...
function FixDunsLength9(const DUNS: string): string;
procedure SetSiteDUNS9(const Value: string);
published
...
property SiteDUNS9: string read fSiteDUNS9 write SetSiteDUNS9;
end;
// End of the tBizObj Class;
implementation
...
function TBizObj.FixDunsLength9(const DUNS: string): string;
begin
Result := glib_LeftPad(DUNS, 9, '0');
end;
procedure TBizObj.SetSiteDUNS9(const Value: string);
var
NewValue: string;
begin
NewValue := FixDunsLength9(Value);
if fSiteDUNS9 <> NewValue then
begin
fSiteDUNS9 := NewValue;
...
end;
end;
You need to declare a procedure for the setter method. As the Property Access help says:
write fieldOrMethod
In a write specifier, if fieldOrMethod is a method, it must be a
procedure that takes a single value or const parameter of the same
type as the property (or more, if it is an array property or indexed
property).
In your case you can then write a setter like this:
type
TBizObj = class(TObject)
private
FSiteDUNS9: string;
procedure FixDunsLength9(const DUNS: string);
published
property SiteDUNS9: string read FSiteDUNS9 write FixDunsLength9;
end;
implementation
procedure TBizObj.FixDunsLength9(const DUNS: string);
begin
if DUNS <> FSiteDUNS9 then
begin
DoSomeExtraStuff;
FSiteDUNS9 := DUNS;
end;
end;
But following the naming conventions I would recommend you to name your setter like SetSiteDUNS9 and the parameter call Value.

Delphi: How can I know which of the indexed property has the string index using RTTI

Based on some code like the following:
TListWrapper = class
strict private
FList: TStringList;
function GetItem(index: Integer): TObject; overload;
function GetItem(index: string): TObject; overload;
public
property Items[index: Integer]: TObject read GetItem; default;
property Items[index: string]: TObject read GetItem; default;
end;
I want to write a piece of code which get the value of the string indexed property using RTTI. Something like this:
var
MyList: TListWrapper;
InstanceType: TRttiInstanceType;
IndexedProperty: TRttiIndexedProperty;
begin
MyList:=TListWrapper.Create;
LContext:=TRttiContext.Create;
InstanceType:=LContext.GetType(MyList.ClassType) as TRttiInstanceType;
for IndexedProperty in InstanceType.GetIndexedProperties do
if IndexedProperty.Name.ToLower = 'items' then
begin
//There are two indexed properties with name 'items'
end;
LContext.Free;
MyList.Free;
end;
Question: How can I know which of the indexed property has the string index so that I can then get the value like this?
IndexedProperty.GetValue(MyList, ['some_string_index']);
Note: I am using Delphi 10.2.3 (Tokyo)
You should be able to use the read method parameters. Something like this:
readingMethod := IndexedProperty.ReadMethod;
readMethodParameters := readingMethod.GetParameters;
if readMethodParameters[0].ParamType.TypeKind = tkUString then
// We have the string version
You should obviously check that the readMethod is assigned and that the number of parameters is greater than zero, etc.
From Remy:
In this case, the string type is tkUString (UnicodeString) and it has been this way since Delphi 2009.

How to change a generic type value?

In my application, I've created the TList type list, intended to store Integers or Doubles:
TKList<T> = class
private
FItems: TList<T>;
function GetItem(Index: Integer): T;
procedure SetItem(Index: Integer; const Value: T);
function GetMaxValue(): T;
function GetMinValue(): T;
public
constructor Create; overload;
constructor Create(const AKList: TKList<T>); overload;
destructor Destroy; override;
procedure Assign(const AKList: TKList<T>);
function Add(const AValue: T): Integer;
procedure Clear;
function Count: Integer;
procedure Invert;
function ToString: string; override;
function Info: string;
property Values[Index: Integer]: T read GetItem write SetItem; default;
end;
How can I implement Invert() procedure to invert values in generic List?
Thanks in advance.
Assuming you mean to Reverse the array as in you have values 1, 3, 5 after calling this function you want to have 5, 3, 1
Then, you could implement the procedure like this.
procedure TKList<T>.Invert;
var
I: Integer;
begin
for I := 0 to (Count - 1) div 2 do
FItems.Exchange(I, Count - I - 1);
end;
Altho I would suggest Reverse as it's name, since Invert is kind of confusing.
There's no way to specify constraints on generics such that you can require the types to be numbers, so there's no way you can use numeric operators on the values in your list. Craig Stuntz wrote a series of posts describing how to build a generic statistical library, and he came up against the same problem. He solved it by providing additional arguments to his functions so that the caller could provide implementations for the type-specific numeric operations — the template method pattern. Here's how he declared the Average operation:
type
TBinaryOp<T> = reference to function(ALeft, ARight: T): T
TStatistics<T> = class
public
class function Average(const AData: TEnumerable<T>;
AAdder, ADivider: TBinaryOp<T>;
AMapper: TFunc<integer, T>): T; overload;
Callers of that function need to provide their own code for adding, dividing, and "mapping" the generic type. (Mapping is covered in a later post and isn't important here.) You could write your Invert function like this:
type
TUnaryOp<T> = reference to function(Arg: T): T;
TKList<T> = class
procedure Invert(ANegater: TUnaryOp<T>);
procedure TKList<T>.Invert;
var
i: Integer;
begin
for i := 0 to Pred(Count) do
Values[i] := ANegater(Values[i]);
end;
To make it more convenient to call the methods without having to provide the extra arguments all the time, Stuntz showed how to declare a type-specific descendant that provides the right arguments. You could do it like this:
type
TIntKList = class(TKList<Integer>)
private
class function Negate(Arg: Integer): Integer;
public
procedure Invert;
end;
procedure TIntKList.Invert;
begin
inherited Invert(Negate);
end;
You can provide type-specific descendants for the common numeric types, and if consumers of your class need to use other number-like types, they can provide their own implementations for the basic numeric operations without having to re-implement your entire list class.
Thanks Rob, I got it.
What advantages/disadvantages has the following approach:
procedure TKList<T>.Invert;
var
i: Integer;
Val: TValue;
begin
if TTypeInfo(TypeInfo(T)^).Kind = tkInteger then
begin
for i := 0 to FItems.Count - 1 do
begin
Val := TValue.From<T>(FItems[i]);
TValue.From<Integer>(-Val.AsInteger).AsType<T>;
end;
end
else if TTypeInfo(TypeInfo(T)^).Kind = tkFloat then
begin
for i := 0 to FItems.Count - 1 do
begin
Val := TValue.From<T>(FItems[i]);
FItems[i] := TValue.From<Double>(-Val.AsExtended).AsType<T>;
end;
end;
end;

Resources