Can a property setter use open array parameter? - delphi

The doc says dynamic arrays can be passed into a function/procedure using open array parameters.
For example, the following works, in accordance with the doc.
type
TAInteger = array of Integer;
function Work(const A: array of Integer): Integer;
begin
Result := Length(A);
end;
However, the code below complains about E2008 Incompatible types:
type
TTest = class
private
procedure SetIntegerArray(const Value: array of Integer);
published
property Value: TAInteger write SetIntegerArray;
end;
I am confused why there is the "Incompatible types" error, and whether a property setter can use open array parameters ?

Can a property setter use open array parameter?
No.
The type of the parameter that contains the new property value must be compatible with the property's type. An open array is not a type. Your property setter must be written like this:
procedure SetIntegerArray(const Value: TAInteger);

Related

No overloaded version with these arguments - var param with read/write property

I have two overloaded procedures, of which I want to call the second one:
function ModifySyncProperties(AEWSItemId: String; AEvent: TcxSchedulerEvent; var AEWSChangeKey: String): Boolean; overload;
function ModifySyncProperties(AEWSItemId: String; ATTID: Integer; ASyncID: String; var AEWSChangeKey: String): Boolean; overload;
This fails with an error, though:
lSuccess := FDMExchange.ModifySyncProperties(lEWSId, lApp.EventID, lNewOutlookID, lApp.EWSItemChangeKey);
There is no overloaded version of 'ModifySyncProperties' that can be called with these arguments
This works, though:
lChangeKey := lApp.EWSItemChangeKey;
lSuccess := FDMExchange.ModifySyncProperties(lEWSId, lApp.EventID, lNewOutlookID, lChangeKey);
lApp.EWSItemChangeKey := lChangeKey;
Here are the types and variables:
lNewOutlookID,
lEWSID,
lChangeKey : String;
lApp : TEWSAppointment;
lSuccess : Boolean;
TEWSAppointment is defined in the interface section of another unit as this:
TEWSAppointment = class
private
FEventID: Integer;
...
FEWSItemChangeKey: String;
...
public
property EventID: Integer read FEventID write FEventID;
...
property EWSItemChangeKey: String read FEWSItemChangeKey write FEWSItemChangeKey;
...
end;
Why does the compiler not accept the read/write lApp property as a var parameter?
I'm using Delphi Rio 10.3.1.
The documentation for var parameters says:
If a routine's declaration specifies a var parameter, you must pass an assignable expression - that is, a variable, typed constant (in the {$J+} state), dereferenced pointer, field, or indexed variable to the routine when you call it.
A property does not meet this requirement.
Further, the documentation for properties calls this out explicitly:
Unlike fields, properties cannot be passed as var parameters, nor can the # operator be applied to a property.
At the implementation level, the ABI, var parameters are implemented by passed the address of the variable. Since a property does not necessarily have a backing variable with an address, the compiler cannot directly obtain such a variable address.
Furthermore, if the property getter or setter perform actions beyond reading or writing to a variable, then they would need to be called.
In principle at least the language could support the usage you wish by declaring a local variable and compiling this code:
localVar := myProperty;
foo(localVar);
myProperty := localVar;
However, the designers of the compiler did not implement this when properties were introduced to the language.

Delphi - How can I pass Generic parameter to function that accept Array of const parameter

I have a 'Base class' which contain a 'function' that accept parameter of type 'Array of const' as shown below:-
type
TBaseClass = class(TObject)
public
procedure NotifyAll(const AParams: array of const);
end;
procedure TBaseClass.NotifyAll(const AParams: array of const);
begin
// do something
end;
I have another 'Generic class' that is derived from the 'base class' ( defined above )
type
TEventMulticaster<T> = class(TBaseClass)
public
procedure Notify(AUser: T); reintroduce;
end;
procedure TEventMulticaster<T>.Notify(AUser: T);
begin
inherited NotifyAll([AUser]); ERROR HERE
end;
Every time I compile this code it gives error saying:
Bad argument type in variable type array constructor
What does it referring to be wrong?
You cannot pass a Generic argument as a variant open array parameter. The language Generics support simply does not cater for that.
What you can do instead is wrap the Generic argument in a variant type, for instance TValue. Now, you cannot pass TValue instances in a variant open array parameter either, but you can change NotifyAll() to accept an open array of TValue instead:
procedure NotifyAll(const AParams: array of TValue);
Once you have this in place, you can call it from your Generic method like so:
NotifyAll([TValue.From<T>(AUser)]);
Fundamentally, what you are attempting to do here is combine compile-time parameter variance (Generics) with run-time parameter variance. For the latter, there are various options. Variant open array parameters are one such option, but they do not play well with Generics. The alternative that I suggest here, TValue, does have good interop with Generics.
The System.Rtti unit has something exactly for you needs, but not widely known:
TValueArrayToArrayOfConst() and
ArrayOfConstToTValueArray()
So your implementation should be:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;
Notes:
TValueArrayToArrayOfConst()'s result is a non-owning container. It contains memory pointers backed by the source array of the TValue container. ParametersAsTValueArray is alive and not being altered while the array of const is used.
One of these 2 Rtti procedures has a bug with regard to TClass values processing. TClass becomes a Pointer on some stage, and string.Format() breaks because Pointer and TClass are not the same thing. Perform tests on all TVarRec.VType, they are not so many, much less that Variant's VType.

Generics and Array of const

type
TGeneric<T> = class(TBase)
public
procedure Swap(Harry : T);
procedure NotifyAll(AParams : T);
end;
i have two question
Is there any way i can typecast a generic variable in any other type like variant etc.
how can i pass generic type in function that accept array of const like here
Delphi - How can I pass Generic parameter to function that accept Array of const parameter
Is there any way I can typecast a generic variable in any other type like Variant etc.
As stated in my answer to the question you link, the variant type that works well with generics is TValue. You can make a TValue instance from a generic type using the From method:
procedure TMyClass.Foo<T>(const Item: T);
var
Value: TValue;
begin
Value := TValue.From<T>(Item);
end;
How can I pass generic type in function that accept array of const?
This is the exact same question that was asked in the question you linked to. The answer is the same as stated there, namely that you cannot pass a generic argument as a variant open array parameter. Again, TValue to the rescue. Instead of using a variant open array parameter, use an open array of TValue.
The bottom line here is that the variant type that works well with generics, and that is supplied with the Delphi RTL, is TValue.
Copying my code from linked question:
procedure TEventMulticaster<T>.Notify(AUser: T);
var
ParametersAsTValueArray: array[1 .. 1] of TValue;
begin
ParametersAsTValueArray[1] := TValue.From<T>(AUser);
NotifyAll(TValueArrayToArrayOfConst(ParametersAsTValueArray));
end;

Why can I not use SetLength in a function which receives an array parameter?

I am trying to use the following function to set the length of a dynamic array which is a var param.
There is only an error when I try to compile the code:
[dcc64 Error] lolcode.dpr(138): E2008 Incompatible Types
function execute(var command : array of string) : Boolean;
begin
// Do something
SetLength(command,0);
end;
You are suffering from a common and fundamental mis-understanding of array parameters. What you have here:
function execute(var command: array of string): Boolean;
is not in fact a dynamic array. It is an open array parameter.
Now, you can pass a dynamic array as a parameter to a function that receives an open array. But you cannot modify the length of the dynamic array. You can only modify its elements.
If you need to modify the length of the dynamic array, the procedure must receive a dynamic array. In modern Delphi the idiomatic way to write that is:
function execute(var command: TArray<string>): Boolean;
If you are using an older version of Delphi that does not support generic arrays then you need to declare a type for the parameter:
type
TStringArray = array of string;
....
function execute(var command: TStringArray): Boolean;
How should you choose whether to use open array or dynamic array parameters? In my opinion you should always use open arrays if possible to do so. And if not possible, then use dynamic arrays as a final resort. The reason being a function with an open array parameter is more general than one with a dynamic array parameter. For example, you can pass a constant sized array as an open array parameter, but not if the function receives a dynamic array.
Define a type
type
TStringArray = array of string;
and you can do
function Execute(var StringArray: TStringArray): boolean;
begin
// Do something
SetLength(StringArray, 0);
end;

How to give setter a 2nd parameter in Delphi?

I want to know whether we can do such in Delphi:
I have a private procedure:
procedure SetMySend(const oValue: TTM_MySend_Profile;
displayValue: string = '...');
I have a public property:
property MySend: TTM_MySend_Profile displayLocateID '...'
read FMySend write SetMySend;
Can I give a parameter displayValue here as the 2nd parameter of the setter? I cannot get this compiled.
I cannot figure out the correct way to do it and wonder whether I can do this in Delphi. Thanks for help!
A property setter for a property takes only one parameter, of the same type as the property. There is no syntax that would allow you to write the type of code you are attempting to write. Note that I am ignoring array properties which are not pertinent here.
What you need to do is to write a dedicated setter which supplies the extra parameter to your SetMySend function.
procedure SetMySend(const Value: TTM_MySend_Profile;
const displayValue: string); overload;
procedure SetMySend(const Value: TTM_MySend_Profile); overload;
property MySend: TTM_MySend_Profile read FMySend write SetMySend;
And then in the implementation you write
procedure TMyClass.SetMySend(const Value: TTM_MySend_Profile);
begin
SetMySend(Value, '...');
end;
You could hijack index specifiers to effect something similar, but I would not recommend that.

Resources