I am trying to figure out how to genericize this helper method; so that it returns the same type that was asked for:
type
TScreenHelper = class helper for TScreen
public
function FindForm(DesiredFormClass: TFormClass): TForm;
end;
Right now the caller has to cast the return value to type they already want:
var
frmReportReminderSetup: TfrmReportReminderSetup;
begin
//frmReportReminderSetup := Screen.FindForm(TfrmReportReminderSetup); Doesn't compile
frmReportReminderSetup := TfrmReportReminderSetup(Screen.FindForm(TfrmReportReminderSetup));
The non-generic implementation is:
function TScreenHelper.FindForm(DesiredFormClass: TFormClass): TForm;
var
f: TForm;
i: Integer;
begin
Result := nil;
for i := 0 to Screen.FormCount-1 do //Screen.Forms does not support enumeration
begin
f := Screen.Forms[i];
if (f is DesiredFormClass) then
begin
Result := f;
Exit;
end;
end;
end;
Generics
What i want is some way to use generics so that the function returns the type it was asked for.
frmContoso := Screen.FindForm(TfrmContoso);
In pseudo-code, the signature that i want would be something like:
function FindForm(T: TFormClass): T;
When it comes time to turn this into actual Delphi syntax, i think you must specify one of the T references in angle brackets:
function FindForm(<T>): T;
But i don't think the is allowed there; i think it has to be before the opening parenthesis:
function FindForm<T>: T;
Try that
TScreenHelper = class helper for TScreen
public
function FindFormOld(DesiredFormClass: TFormClass): TForm;
function FindForm<T>: T;
end;
function TScreenHelper.FindForm<T>: T;
var
f: TForm;
i: Integer;
begin
Result := nil;
for i := 0 to Screen.FormCount-1 do //Screen.Forms does not support enumeration
begin
f := Screen.Forms[i];
if (f is T) then
begin
Result := f as T;
Exit;
end;
end;
end;
Except that fails to compile:
Result := nil; E2010 Incompatible types: 'T' and 'Pointer'
I can sorta see what's wrong. It doesn't understand that T is a class (i.e. what if it was an Integer
? Then it would absolutely be wrong to set it to nil
.)
Constraints
So i need to somehow give the compiler the hint of what type T
will be:
TScreenHelper = class helper for TScreen
public
function FindForm<T: TFormClass>: T;
end;
function TScreenHelper.FindForm<T: TFormClass>: T;
var
f: TForm;
i: Integer;
begin
Result := nil;
for i := 0 to Screen.FormCount-1 do //Screen.Forms does not support enumeration
begin
f := Screen.Forms[i];
if (f is T) then
begin
Result := f as T;
Exit;
end;
end;
end;
This new signature is confusing to call; you no longer pass a desired type to the function. Instead you now call a variation of the function that you want:
frmContoso := Screen.FindForm<TfrmConsoto>();
But no matter; it's way of generics.
Except that's not valid
The syntax:
function FindForm<T: TFormClass>: T;
is not valid, as TFormClass
is not one of the allowed Delphi constraint types:
Constraints in Generics
Specifying Generics with Constraints
Constraint items include:
- Zero, one, or multiple interface types
- Zero or one class type
- The reserved word "constructor", "class", or "record"
(emphasis mine)
While i'm allowed one class type, i'm not passing a class type; i'm passing a class of class type.
So now i'm stuck. In my attempt to save myself typing 25 characters, i've now blown an hour on the minutia of Delphi generics.
tl;dr
How do i genericize:
function FindForm(DesiredFormClass: TFormClass): TForm;
Example non-working code
program Project2;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, Vcl.Forms;
type
TScreenHelperCore = class(TObject)
public
class function FindForm<T: TForm>: T;
end;
TfrmContoso = class(TForm)
public
end;
{ TScreenHelperCore }
class function TScreenHelperCore.FindForm<T: TForm>: T;
// \__[dcc32 Error] Project2.dpr(23): E2029 ',', ';' or '>' expected but ':' found
var
f: TForm;
i: Integer;
begin
Result := nil;
for i := 0 to Screen.FormCount-1 do //Screen.Forms does not support enumeration
begin
f := Screen.Forms[i];
if (f is T) then
begin
Result := f;
Exit;
end;
end;
end;
var
f: TfrmContoso;
begin
try
f := TScreenHelperCore.FindForm<TfrmContoso>;
if f = nil then
f := TfrmContoso.Create(nil);
f.ShowModal;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.