Reputation: 6979
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;
Upvotes: 4
Views: 3345
Reputation:
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.
Upvotes: 0
Reputation: 612993
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.
Upvotes: 2