Is there a way in Delphi to pass parameter value to Function or Procedure with random order, so i don't have to make sure that the order is right.
Example:
procedure InsertEmp(ID: Integer;Name: String;Gender: String);
begin
//Content
end
Then I will use this procedure like this:
InsertEmp(1,'Zemmy','Male');
But if at another time i change the function parameter order like this:
procedure InsertEmp(ID: Integer;NickName:String;Name: String;Gender: String);
begin
//Content
end
I have to make position correction to my function as following:
InsertEmp(1,'Jim','Zemmy','Male');
Can i pass the parameter value without correcting the order? Maybe a way like this:
InsertEmp(Gender = 'Male',NickName = 'Jim',ID = 1,Name = 'Zemmy');
Thank you for your help.
You can have multiple overloaded procedures/functions taking parameters that are slightly different.
procedure InsertEmp(id: Integer; name: String; gender: String); overload;
procedure InsertEmp(id: Integer; nickName,name: String; gender: String); overload;
For a random parameter order, there are serializing libraries taking strings as input and then parsing them for input values. Simple key-value pairs or JSON are examples.
Having two or more parameters of the same type, the called procedure cannot determine their meaning.
I think the answer can be a mix of the whosrdaddy's comment an the LU RD's answer.
Using overloading for a record constructor, you can obtain a smart solution.
type
TEmployee = record
ID: Integer;
NickName,
Name,
Gender: String;
constructor Create(const AID: Integer; const ANickname, AName, AGender: String); overload;
constructor Create(const AID: Integer; const AName, AGender: String); overload;
end;
procedure InsertEmp(const AEmployee: TEmployee);
begin
//Content
end;
constructor TEmployee.Create(const AID: Integer; const ANickname, AName, AGender: String);
begin
ID := AID;
Nickname := ANickName
Name := AName;
Gender := AGender;
end;
constructor TEmployee.Create(const AID: Integer; const AName, AGender: String);
begin
Create(AID, '', AName, AGender);
end;
var
myEmployee: TEmployee;
begin
myEmployee := TEmployee.Create(1, 'Zemmy', 'Male');
InsertEmp(myEmployee);
. . .
myEmployee := TEmployee.Create(1, 'Jim', 'Zemmy', 'Male');
InsertEmp(myEmployee);
end.
No you can not. If you want to do something like that you can pass a single string in a Key - Value format
InsertEmp('Gender=Male,NickName=Jim,ID=1,Name=Zemmy');
and then parse it inside you function.
There is another option if you can use only one variable type. Use open arrays. Declare
procedure InsertEmp(values: array of string);
and pass any parameter like this
InsertEmp(['Gender = Male', 'NickName = Jim', 'Name = Zemmy' ...]);
Related
The program receives product information datas through window message.
Incoming datas processed in TProductInstance.PutProductData procedure.
Product information contains date, name, price.
I want to store datas as TObjectDictionary. The key is same date of the product and value is product information data list as TObjectList.
Also I want to maintain datas only in latest 7 days.
By the way, when I remove the item from TObjectDictionary for maintaining, error occurs like below.
First chance exception at $75214598.Exception class EInvalidPointer with message 'Invalid pointer operation'. Process product.exe (3848).
This is caused by FProductDictionary.Remove(StringKey);.
How can I avoid EInvalidPointer error with maintain latest 7 days datas?
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
TProductItemList = class(TObjectList<TProductItem>);
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TProductItemList;
FProductDictionary: TObjectDictionary<String, TProductItemList>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
begin
FLatestDate := '';
FProductList := TProductItemList.Create;
FProductDictionary := TObjectDictionary<String, TProductItemList>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
UPDATED
type
TProductItem = class(TObject)
private
FDate: String;
FName: String;
FPrice: Integer;
procedure SetDate(const value: String);
procedure SetName(const value: String);
procedure SetPrice(const value: Integer);
public
property Date: String read FDate write SetDate;
property Name: String read FName write SetName;
property Price: Integer read FPrice write SetPrice;
constructor Create(const date, name: String; const price: Integer);
end;
type
TProductInstance = class(TObject)
private
public
FLatestDate: String;
FProductList: TObjectList<TProductItem>;
FProductDictionary: TObjectDictionary<String, TObjectList<TProductItem>>;
constructor Create;
destructor Destroy; override;
procedure PutProductData(var Data: LP_Data);
end;
implementation
constructor TProductInstance.Create;
var
LProductItem: TProductItem;
LProductItemList: TObjectList<TProductItem>;
LStringList: TStringList;
begin
FLatestDate := '';
FProductList := TObjectList<TProductItem>.Create;
FProductDictionary := TObjectDictionary<String, TObjectList<TProductItem>>.Create([doOwnsValues]);
end;
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name), Trim(LP_Data^.price)));
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
LProductItemList := TObjectList<ProductItem>.Create;
for LProductItem in FProductList do
begin
LProductItemList.Add(LProductItem);
end;
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), LProductItemList);
FProductList.Clear;
LStringList := TStringList.Create;
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
begin
LStringList.Add(StringKey);
end;
end;
for StringKey in LStringList do
begin
FProductDictionary.Remove(StringKey);
end;
FreeAndNil(LStringList);
end;
end;
Updated code occurs EInvalidPointer error on FProductDictionary.Remove(StringKey); What did I wrong?
The code you present is incomplete. You did not show the destructor for TProductInstance. For a question such as this you should always supply a simple MCVE. This is quite easy to achieve in a single console .dpr file.
Looking at what we can see, it is clear that the lifetime management in the code is broken. Let us critique this method.
procedure TProductInstance.PutProductData(var Data: LP_Data);
var
StringKey: String;
begin
if (Trim(LP_Data^.date) <> FLatestDate) then
begin
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
for StringKey in FProductDictionary.Keys do
begin
if (GetDateToInt(Trim(LP_Data^.date)) - GetDateToInt(FLatestDate) > 7) then
FProductDictionary.Remove(StringKey);
end;
FProductList.Free;
end;
FProductList.Add(TProductItem.Create(Trim(LP_Data^.date), Trim(LP_Data^.name),
Trim(LP_Data^.price)));
FLatestDate := Trim(LP_Data^.date);
end;
Because FProductDictionary owns its values, when you do
FProductDictionary.AddOrSetValue(Trim(LP_Data^.date), FProductList);
then FProductDictionary becomes the owner of FProductList. That means that you should not destroy FProductList ever. However, you do exactly that:
FProductList.Free;
So you are going to be destroying FProductList multiple times which is a clear error.
What to do next? You need to deal with the lifetime issues. I cannot know from the code presented here what you are trying to achieve, and how the lifetime should be managed. You will need to work out who is responsible for owning what, and make sure that you stick to a clear lifetime management policy.
On the face of it, my best guess would be that you need to remove the FProductList field. When you need to add a new item to FProductDictionary, instantiate a new instance of TProductItemList, populate it, and add it to the dictionary. At that point the dictionary takes control of the lifetime of the TProductItemList.
As one final comment, I would suggest that the type TProductItemList is pointless. I would remove it. Use TObjectList<TProductItem> to make the code clearer to the reader. The reader can look at TObjectList<TProductItem> and know immediately what it is, since TObjectList<T> is such a ubiquitous type.
Is there a trick to pass records with different type as parameter in a procedure? For example, look at this pseudo-code:
type
TPerson = record
Species: string;
CountLegs: Integer;
end;
TSpider = record
Species: string;
CountLegs: Integer;
Color: TColor;
end;
var
APerson: TPerson;
ASpider: TSpider;
// Is there a trick to pass different record types as parameter in a procedure?:
procedure DoSomethingWithARecord(const ARecord: TAbstractRecord?);
begin
if ARecord is TPerson then
DoSomethingWithThisPerson(ARecord as TPerson)
else if ARecord is TSpider then
DoSomethingWithThisSpider(ARecord as TSpider);
end;
procedure DefineRecords;
begin
APerson.Species := 'Human';
APerson.CountLegs := 2;
ASpider.Species := 'Insect';
ASpider.CountLegs := 8;
ASpider.Color := clBtnFace;
DoSomethingWithARecord(APerson);
DoSomethingWithARecord(ASpider);
end;
Record instances don't contain type information in the same way that classes do. So you would need to pass an extra argument to indicate which type you were working with. For instance:
type
TRecordType = (rtPerson, rtSpider);
procedure DoSomething(RecordType: TRecordType; const ARecord);
begin
case RecordType of
rtPerson:
DoSomethingWithThisPerson(TPerson(ARecord));
rtSpider:
DoSomethingWithThisSpider(TSpider(ARecord));
end;
end;
You might contemplate putting the type code in the first field of each record:
type
TPerson = record
RecordType: TRecordType;
Species: string;
CountLegs: Integer;
end;
TSpider = record
RecordType: TRecordType;
Species: string;
CountLegs: Integer;
Color: TColor;
end;
function GetRecordType(ARecord): TRecordType;
begin
Result := TRecordType(ARecord);
end;
....
procedure DoSomething(const ARecord);
begin
case GetRecordType(ARecord) of
rtPerson:
DoSomethingWithThisPerson(TPerson(ARecord));
rtSpider:
DoSomethingWithThisSpider(TSpider(ARecord));
end;
end;
You could use generics:
type
TMyRecordDispatcher = record
class procedure DoSomething<T: record>(const Value: T); static;
end;
class procedure TMyRecordDispatcher.DoSomething<T>(const Value: T);
begin
if TypeInfo(T) = TypeInfo(TPerson) then
DoSomethingWithThisPerson(PPerson(#Value)^)
else if TypeInfo(T) = TypeInfo(TSpider) then
DoSomethingWithThisSpider(PSpider(#Value)^);
end;
And call the functions like this:
TMyRecordDispatcher.DoSomething(APerson);
TMyRecordDispatcher.DoSomething(ASpider);
This uses generic type inference and so allows you not to explicitly state the type. Although as an example of generics it makes me cringe. Please don't do this.
In my view all of this is messy and brittle. Much of the above reimplements run time method dispatch, polymorphism. Classes are more suited to this. I don't endorse any of the code above.
On the other hand, perhaps this is all needless. What's wrong with:
DoSomethingWithThisPerson(Person);
DoSomethingWithThisSpider(Spider);
Since you know the types at compile time, why opt for anything more complex?
You could use function overloading to make it possible to omit the type from the function name.
procedure DoSomething(const APerson: TPerson); overload;
begin
....
end;
procedure DoSomething(const ASpider: TSpider); overload;
begin
....
end;
....
DoSomething(Person);
DoSomething(Spider);
Is it possible to create a function-pointer with a default parameter, something like
TFunctionPointer = function(sName:AnsiString; tOptional: TObject = nil):smallint;
What I want to achieve:
A function pointer, which can accept a function of type
function A(sName:AnsiString)
or
function B(sName:AnsiString, tOptional: TObject)
How can I achieve this?
Default parameter is just a syntactic sugar - actually function call has two parameters.
But you can use function references and anonymous methods to create such function pointers - function adapters.
type
fnA = function(const sName: AnsiString): integer;
fnB = function(const sName: AnsiString; const tOptional: TObject); integer;
fnRef = reference to function(const sName: AnsiString; const tOptional: TObject): integer;
fnBridge = record
Bridge: fnRef;
class operator Implicit(fn: fnA): fnBridge;
class operator Implicit(fn: fnB): fnBridge;
end;
class operator fnBridge.Implicit(fn: fnA): fnBridge;
begin
Result.Bridge :=
function(const sName: AnsiString; const tOptional: TObject): integer
begin
Result := fn(sName);
end;
end;
class operator fnBridge.Implicit(fn: fnB): fnBridge;
begin
Result.Bridge :=
function(const sName: AnsiString; const tOptional: TObject): integer
begin
Result := fn(sName, tOptional);
end;
end;
function A(const sName: AnsiString): integer;
begin Result := Length(sName) end;
function B(const sName: AnsiString; const tOptional: TObject): integer;
begin Result := Length(sName) - Length(tOptional.ClassName) end;
function Consumer (const Param1, Param2: integer; const Action: fnBridge): integer;
begin
Result := Param1 + Param2 * Action.Bridge('ABCDE', Application);
end;
....
ShowMessage( IntToStr( Consumer(10, 20, A) ));
ShowMessage( IntToStr( Consumer(10, 20, B) ));
PS: since Delphi version was not specified it means that answer for ANY Delphi version suits fine. This method should work wit hany version starting with Delphi 2009 and later.
PPS: references to functions with captured variables are implemented internally as TInterfacedObject descendants. So overall this is just a reduced case of "Strategy pattern" using "higher-order functions"
http://en.wikipedia.org/wiki/Strategy_pattern
http://en.wikipedia.org/wiki/Higher-order_function
That is not possible. In order for a function to be of type TFunctionPointer, it must declare two parameters.
A default parameter is still a parameter. Your TFunctionPointer is a function with two parameters. When you call it and supply only one parameter, the compiler supplies the default parameter at the call site. So two parameters are still passed to the function.
To expand on this. Consider the following:
procedure Foo(Bar: Integer=666);
begin
end;
When you call the procedure like this:
Foo();
it looks as though the procedure has no parameters. But that is not the case. The compiler translates your code into this:
Foo(666);
The conclusion is that if you want to allow receipt of functions with different numbers of parameters, you'll need to provide an explicit mechanism to receive those different function types. For instance:
procedure DoSomething(const Callback: TProc<string, TObject>); overload;
begin
Callback(str, obj);
end;
procedure DoSomething(const Callback: TProc<string>); overload;
begin
DoSomething(
procedure(arg1: string; arg2: TObject)
begin
Callback(arg1);
end
);
end;
The title may be updated once the question is posted, But I start with an .ini file I would like to save Integers , Strings , Bool To this .ini file. Which I can do with
WriteString
WriteInteger
WriteBool
Then I Would like to read it into a list, Where when i pulled the data from the list it would know its all ready a integer or string or bool?
Currently I have to write everything as a string and then i read into a stringlist.
As said, you can read all data as string. And you can use the following function to determine the datatype:
type
TDataType = (dtString, dtBoolean, dtInteger);
function GetDatatype(const AValue: string): TDataType;
var
temp : Integer;
begin
if TryStrToInt(AValue, temp) then
Result := dtInteger
else if (Uppercase(AValue) = 'TRUE') or (Uppercase(AValue) = 'FALSE') then
Result := dtBoolean
else
Result := dtString;
end;
You can (ab)use the object property of the stringlist to store the datatype:
procedure TMyObject.AddInteger(const AValue: Integer);
begin
List.AddObject(IntToStr(AValue), TObject(dtInteger));
end;
procedure TMyObject.AddBoolean(const AValue: Boolean);
begin
List.AddObject(BoolToStr(AValue), TObject(dtBoolean));
end;
procedure TMyObject.AddString(const AValue: String);
begin
List.AddObject(AValue, TObject(dtString));
end;
function TMyObject.GetDataType(const AIndex: Integer): TDataType;
begin
Result := TDataType(List.Objects[AIndex]);
end;
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;