It is possible to have a parameter in a routine which can be in the same time either an type, either an string? I know I can accomplish this by overloading a routine, I ask if it possible to do it in another way.
Assume that I have this type - TTest = (t1,t2,t3). I want to have a routine which accepts a parameter of type TTest, but in the same time to be a String, so I can call it myproc(t1) or myproc('blabla')
You should use an overloaded function.
You already have the perfect solution to the problem and there is no need to look for a different way to do this. You could try with a single function that receives a Variant, but then that function will also receive anything which means that the following would also be legal:
myproc(0.5);
myproc(intf);
myproc(-666);
Using an overload allows you to maintain compile time type safety and there is absolutely no loss of generality in using an overload.
Even this can be easily accomplished with overloaded functions, considering it's a good exercise, based on David Hefferman's and Sertac Akyuz answers I made a small example to test both solutions. It is not perfect, it only shows both possibilities.
unit Unit4;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
ttest = (t1,t2);
TForm4 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
function my(aVar:Variant):String;
function MyUntype(const aVar):String;
end;
var
Form4: TForm4;
implementation
{$R *.dfm}
{ TForm4 }
procedure TForm4.FormCreate(Sender: TObject);
var aTestTypeVar : ttest;
aString : String;
begin
my(t1);
my(t2);
my('ssss');
//with untyped params
aString := 'aaaa';
MyUntype(aString);
aTestTypeVar := t1;
aString := IntToStr(Ord(aTestTypeVar));
MyUntype(aString);//can not be a numeral due to delphi Help
end;
function TForm4.my(aVar: Variant): String;
begin
showmessage(VarToStr(aVar));//shows either the string, either position in type
end;
function TForm4.MyUntype(const aVar): String;
begin
//need to cast the parameter
try
ShowMessage(pchar(aVar))
except
showmessage(IntToStr(Ord(ttest(aVar))));
end;
end;
end.
Also I know that Variants are slow and must be used only needed.
Related
I'm learning OOP and I have created a basic program so far. I have create my own clas:
Type
Zombie = class
private
fLife : Integer;
fAge : Integer;
fHeight: String;
public
Constructor Create(pLife, pAge : Integer; pHeight : String);
Procedure SetLife(pLife : Integer);
function GetLife : Integer;
Procedure ShowLife;
end;
The procedure ShowLife does exactly what it says:
procedure Zombie.ShowLife;
begin
ShowMessage(inttostr(fLife));
end;
I'm trying to call this procedure on a Form but it says undeclared identifier:
procedure Tform1.ShowLifebtnClick(Sender: TObject);
begin
Zombies_Unit.ShowLife;
end;
I have included the unit in the user of the Form. How can I use methods on another form
You need to create and free the object before/after you use it. The pattern is like this:
MyZombie := TZombie.Create(10, 20, 30);
try
MyZombie.ShowLife();
finally
MyZombie.Free();
end;
You have to create an instance of your class and call the method of that object like
MyZombie := Zombie.create(20,15);
MyZombie.ShowLife;
...
MyZombie.free;
Sending from mobile, cannot format code.
EDIT/SUPPLEMENT:
As my short answer seems to be suitable to tech bad habits (I am sorry for that) I want to add the following advices to the asker:
Please use Try/Finally constructs to avoid that objects are not removed in case of an error occuring between create() and free() like Zdravko Danev's answer points out. It also makes sense to use common naming conventions to make your code easier to understand (e.g. TZombie as class name).
You must pay attention in one thing: Your class is in the same file of your form? If the answer is no, you must declare the unit name on uses of your form file like:
unit MyUnit;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMyForm = class(TForm)
private
{ Private declarations }
public
{ Public declarations }
end;
var
MyForm: TMyForm;
implementation
uses unitzombie; //The name unit where is your class
{$R *.dfm}
end.
After solving this little problem, you must create your object before calling this methods:
procedure Tform1.ShowLifebtnClick(Sender: TObject);
var
Zombi: Zombie;
begin
Zombi := Zombie.Create(5,10,15);
try
Zombi.ShowLife;
finally
Zombi.Free;
end;
end;
I have a logging class, which links to many modules. The main method of this class is a class method:
type
TSeverity = (seInfo, seWarning, seError);
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity);
end;
Somewhere else I have a function DoSomething() which does some things that I would like to log. However, I do not want to link all the modules of the logger to the module in which 'DoSomething()' is declared to use the logger. Instead I would like to pass an arbitrary logging method as a DoSomething's parameter and call it from its body.
The problem is that TLogger.Log requires parameter of TSeverity type which is defined in logger class. So I can't define a type:
type
TLogProcedure = procedure(AMessage: String; ASverity: TSeverity) of Object;
because I would have to include an unit in which TSeverity is declared.
I was trying to come up with some solution based on generic procedure but I am stuck.
uses
System.SysUtils;
type
TTest = class
public
class function DoSomething<T1, T2>(const ALogProcedure: TProc<T1,T2>): Boolean; overload;
end;
implementation
class function TTest.DoSomething<T1, T2>(const ALogProcedure: TProc<T1, T2>): Boolean;
var
LMessage: String;
LSeverity: Integer;
begin
//Pseudocode here I would like to invoke logging procedure here.
ALogProcedure(T1(LMessage), T2(LSeverity));
end;
Somewehere else in the code I would like to use DoSomething
begin
TTest.DoSomething<String, TSeverity>(Log);
end;
Thanks for help.
Update
Maybe I didn't make myself clear.
unit uDoer;
interface
type
TLogProcedure = procedure(AMessage: String; AErrorLevel: Integer) of Object;
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
// I thoight that I can somehow generalize this procedure using generics.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
Separate unit with one of the logging mechanisms.
unit uLogger;
interface
type
TSeverity = (seInfo, seWarning, seError);
// I know that I could solve my problem by introducing an overloaded method but I don't want to
// do it like this. I thought I can use generics somehow.
TLogger = class
class procedure Log(AMessage: String; ASeverity: TSeverity); {overload;}
{class procedure Log(AMessage: String; ASeverity: Integer); overload;}
end;
implementation
class procedure TLogger.Log(AMessage: String; ASeverity: TSeverity);
begin
//...logging here
end;
{class procedure TLogger.Log(AMessage: String; ASeverity: Integer);
begin
Log(AMessage, TSeverity(ASeverity));
end;}
end.
Sample usage of both units.
implementation
uses
uDoer, uLogger;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(TLogger.Log); //Incompatible types: Integer and TSeverity
end;
Introducing generics here does not help. The actual parameters that you have are not generic. They have fixed type, string and Integer. The function you are passing them to is not generic and receives parameters of type string and TSeverity. These types are mis-matched.
Generics won't help you here because your types are all known ahead of time. There is nothing generic here. What you need to do, somehow, is convert between Integer and TSeverity. Once you can do that then you can call your function.
In your case you should pass a procedure that accepts an Integer, since you don't have TSeverity available at the point where you call the procedure. Then in the implementation of that procedure, where you call the function that does accept a TSeverity, that's where you convert.
In scenarios involving generic procedural types, what you have encountered is quite common. You have a generic procedural type like this:
type
TMyGenericProcedure<T> = procedure(const Arg: T);
In order to call such a procedure you need an instance of T. If you are calling the procedure from a function that is generic on T, then your argument must also be generic. In your case that argument is not generic, it is fixed as Integer. At that point your attempt to use generics unravels.
Having said all of that, what you describe doesn't really hang together at all. How can you possibly come up with the severity argument if you don't know what TSeverity is at that point? That doesn't make any sense to me. How can you just conjure up an integer value and hope that it matches this enumerated type? Some mild re-design would enable you to do this quite simply without any type conversions.
As David Heffernan says, you cannot use generics in this way. Instead you should use a function to map the error level to a severity type, and use that to glue together the two. Based on your updated example, one could modify it like this:
unit uDoer;
interface
type
TLogProcedure = reference to procedure(const AMessage: String; AErrorLevel: Integer);
// TDoer knows nothing about logging mechanisms that are used but it allows to pass ALogProcedure as a parameter.
type
TDoer = class
public
class function DoSomething(const ALogProcedure: TLogProcedure): Boolean;
end;
implementation
class function TDoer.DoSomething(const ALogProcedure: TLogProcedure): Boolean;
begin
ALogProcedure('test', 1);
Result := True;
end;
end.
You can then provide the glue procedure which converts the error level to a severity:
implementation
uses
uDoer, uLogger;
function SeverityFromErrorLevel(const AErrorLevel: Integer): TSeverity;
begin
if (AErrorLevel <= 0) then
result := seInfo
else if (AErrorLevel = 1) then
result := seWarning
else
result := seError;
end;
procedure LogProc(const AMessage: String; AErrorLevel: Integer);
var
severity: TSeverity;
begin
severity := SeverityFromErrorLevel(AErrorLevel);
TLogger.Log(AMessage, severity);
end;
procedure TForm10.FormCreate(Sender: TObject);
begin
TDoer.DoSomething(LogProc);
end;
Note I didn't compile this, but the essence is there. I used a procedure reference (reference to procedure) as they're a lot more flexible, which may come in handy later.
I am having a strange problem of using interface in different versions of Delphi. The following minimized code compiles and runs as expected in Delphi XE and higher but not in Delphi 7. Specificaly, it seems when compiling in Delphi 7, the function TForm1.Load: IMoleculeSubject; does not returns the correct result, i.e., the correct reference to the newly created instance. Could you help to comment about the reason and possible workaround? Many thanks!
uInterface.pas
unit uInterface;
interface
type
IMoleculeSubject = interface
['{BEB4425A-186C-45DF-9DCE-C7175DB0CA90}']
end;
TMoleculeSubject = class(TInterfacedObject, IMoleculeSubject)
end;
implementation
end.
uBusiness.pas
unit uBusiness;
interface
uses
uInterface;
type
TMoleculeDecorator = class(TMoleculeSubject)
private
FID: Integer;
public
property ID: Integer read FID;
constructor Create;
end;
implementation
{ TMoleculeDecorator }
constructor TMoleculeDecorator.Create;
begin
inherited Create;
FID := Random(100);
end;
end.
Unit1.pas
unit Unit1;
interface
uses
uInterface, uBusiness,
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
Forms, Dialogs;
type
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
function Load: IMoleculeSubject;
public
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
var
MolSubject: IMoleculeSubject;
begin
MolSubject := Load;
// The down-cast is to show the returned result is wrong in Delphi 7!
Caption := IntToStr(TMoleculeDecorator(MolSubject).ID);
end;
function TForm1.Load: IMoleculeSubject;
var
MolSubject: IMoleculeSubject;
begin
MolSubject := TMoleculeDecorator.Create;
Result := MolSubject;
end;
end.
The Load function works perfectly well in all versions of Delphi. The problem is your cast, which is what is known as an unsafe typecast. An unsafe typecast from an interface reference to an object has ill-defined behaviour in older versions of Delphi. However, the behaviour is well-defined in modern Delphi. The documentation says more.
So, the basic problem is that your expectations for the behaviour are not compatible with the Delphi 7 version of the language.
If you get the interface to return the ID you will find that the interface you are creating is as expected.
program InterfaceDemo;
{$APPTYPE CONSOLE}
uses
Classes;
type
IMyIntf = interface
function GetID: Integer;
end;
TImplementingObject = class(TInterfacedObject, IMyIntf)
private
FID: Integer;
function GetID: Integer;
public
constructor Create;
end;
{ TImplementingObject }
constructor TImplementingObject.Create;
begin
FID := Random(100);
Writeln(FID);
end;
function TImplementingObject.GetID: Integer;
begin
Result := FID;
end;
var
MyIntf: IMyIntf;
begin
Randomize;
MyIntf := TImplementingObject.Create;
Writeln(MyIntf.GetID);
Readln;
end.
It's rather unusual to ask for the implementing object from an interface. To do so suggests that there is a problem with your design. Should you really need to do so there are a few options:
In modern Delphi you can use the type-safe case with the as operator.
In older Delphi versions there are hacks that retrieve the implementing object: Casting a delphi interface to its implementation class without modifying the interface
You could add a function to the interface that returns the implementing object.
The latter option works in all versions of Delphi and does so without resorting to subterfuge.
Casting interfaces to objects is available since Delphi 2010. Where are workarounds for older Delphi versions, see for example How to cast a Interface to a Object in Delphi
One of my coworkers show me a code written in Delphi-XE XE Version 15.0.3953.35171, which I believe it should raise an access violation. The code is bellow:
unit Unit3;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TForm3 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
function test:TstringList;
{ Public declarations }
end;
var
Form3: TForm3;
implementation
{$R *.dfm}
procedure TForm3.FormCreate(Sender: TObject);
var aStrList : TStringList;
begin
aStrList := TStringList.Create;
test;
FreeAndNil(aStrList);
end;
function TForm3.test: TstringList;
var i:Integer;
begin
for i:=0 to 1000 do
Result.Add('aaa');//AV?
end;
end.
Inspecting aStrList and Result has the following results:
aStrList: TStringList $12FEDC : $42138A
Result: TStringList $12FEC4 : $B01B90
I do not understand why it is working. Result.Add should raise an access violation
LE: It seems that is working only on Debug Build Configuration.
The Result variable in that function has not been initialized and could hold any value. Now, the implementation detail means that, in some combinations of compiler options, your code happens to run with Result referring to a valid object. But that's really just a coincidence of those implementation details.
If this were C++ then that function would exhibit undefined behaviour. Although that term does not have a formal meaning in Delphi, it can be helpful to use that term in a Delphi setting to mean the same thing as in the context of C++.
I would also make the point that even if Result did not refer to a valid string list object, your code would not be guaranteed to raise an access violation. It could be that Result points to a block of memory that just happens to look enough like a string list for that code to execute successfully.
If you do things properly, you can predict the behaviour of your program. If your code is flawed and induces undefined behaviour, then your program's behaviour becomes unpredictable. It may work. It may fail. Or that code may execute fine, but then lead to a failure later in the program's execution. And so on.
I have the following code which is working, but I don't understand it 100% (please see the comments from code):
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs;
type
TMyRec=record
a:Integer;
b:String;
end;
TRecArray=array of TMyRec;
PRecArray = ^TRecArray;
TForm1 = class(TForm)
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
v1:TRecArray;
procedure Test(a:PRecArray);
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
{ TForm1 }
procedure TForm1.FormCreate(Sender: TObject);
begin
SetLength(v1,3);
v1[0].b:='test1';//set the first value
Test(PRecArray(v1));//call method to change the value assigned before
end;
procedure TForm1.Test(a: PRecArray);
begin
ShowMessage(v1[0].b);//shows test1
try
a^[0].b:='test2' //this is raising an error...
except
end;
PRecArray(#a)^[0].b:='test3';//this is working...
ShowMessage(v1[0].b);//shows test3
end;
end.
I don't understand why 'a^[0].b:='test2' is raising an error.
Thank you!
Your 'Test' procedure expects a 'PRecArray', but you're passing a 'TRecArray' to it. Try calling it like
Test(#v1);//call method to change the value assigned before
Typecasting a 'TRecArray' to a 'PRecArray' will not make it a 'PRecArray'. (Note: your 'test3' will fail then of course.)
I see several things that are suspicious.
1
There is hardly ever a need to take a pointer to a dynamic array, as dynamic array variables are already pointers (well, references).
To pass such an array to a function or procedure, use var parameters:
procedure TForm1.Test(var a: TRecArray);
Now you don't have to use pointer syntax to access the array:
a[0].b := 'test2';
2
You call Test with:
Test(PRecArray(v1));
In your original, Test took a PRecArray, but you are not passing one (you are passing a TRecArray), so you should have done:
Test(#v1); // or Test(Addr(v1));
Applying my change above, where Test has a var parameter, simply use:
Test(v1);
3
Ok, this is probably not suspicous, but I'd like to plug my article Addressing Pointers, about pointers for Delphi programmers. It explains many of the issues you seem to have.
You could replace the
procedure TForm1.Test(a: TPointerArrayRec);
with
procedure TForm1.Test(var a: TArrayRec);
it's simpler and you don't have to use the deprecation dereference operator ^.