如何将通用过程TProc<T1,T2>作为参数传递并调用它?

4

我有一个日志记录类,与许多模块相关联。该类的主方法是一个类方法:

type
  TSeverity = (seInfo, seWarning, seError);

TLogger = class
  class procedure Log(AMessage: String; ASeverity: TSeverity);
end;

在别的地方,我有一个名为DoSomething()的函数,它会做一些事情,我想记录下来。但是,我不想将所有记录器模块链接到声明DoSomething()的模块中以使用记录器。相反,我想将任意的日志方法作为DoSomething()的参数传递,并从其体内调用它。
问题在于TLogger.Log需要TSeverity类型的参数,而该类型定义在记录器类中。因此,我无法定义该类型:
type
  TLogProcedure = procedure(AMessage: String; ASverity: TSeverity) of Object;

因为我需要包含一个声明了TSeverity的单元。

我试图想出一些基于通用过程的解决方案,但是我卡住了。

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;

在代码的其他位置,我希望使用DoSomething

begin
  TTest.DoSomething<String, TSeverity>(Log);
end;

感谢您的帮助。

更新

也许我表达不够清楚。

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.

使用其中一种日志记录机制来分离单元。

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.

两个单元的使用示例。

implementation

uses
  uDoer, uLogger;

procedure TForm10.FormCreate(Sender: TObject);
begin
  TDoer.DoSomething(TLogger.Log); //Incompatible types: Integer and TSeverity
end;

1
你的参数是整数还是TSeverity? - David Heffernan
@whosrdaddy 谢谢,但那超出了我的问题范围。 - Wodzu
5
“我不想添加这个依赖。”“好的。那么请将 TSeverity 的定义从日志模块中提取出来,放到一个单独的模块中,并在两个地方使用这个新模块。现在它不再是日志模块的一部分,因此你就没有那个依赖了。” - Ken White
泛型在这里没有任何用处。您知道类型是什么。泛型不是为了避免类型安全而存在的手段。 - David Heffernan
1
@Wodzu 当然,你可以自己决定,我只是告诉你你的决定不会带你走向任何地方。 - David Heffernan
显示剩余7条评论
2个回答

2
在这里引入泛型并没有帮助。您拥有的实际参数不是泛型的,它们具有固定的类型,即stringInteger。您要传递给的函数不是泛型的,它接收类型为stringTSeverity的参数。这些类型不匹配。
在此处使用泛型无济于事,因为您的类型都是预先已知的。这里没有任何通用性可言。您需要做的是以某种方式在IntegerTSeverity之间进行转换。一旦您能够这样做,就可以调用函数了。
在您的情况下,您应该传递一个接受Integer的过程,因为您在调用过程的地方没有TSeverity可用。然后,在调用确实接受TSeverity的函数的过程的实现中,就在那里进行转换。
在涉及泛型过程类型的情况下,您遇到的情况非常常见。您有一个像这样的通用过程类型:
type
  TMyGenericProcedure<T> = procedure(const Arg: T);

为了调用这样的过程,您需要一个T的实例。如果您从泛型的T上调用函数,则您的参数也必须是泛型的。在您的情况下,该参数不是通用的,而是固定为Integer。此时,你尝试使用泛型就会失败。
说了这么多,你所描述的实际上并不合理。如果在那个时候你不知道TSeverity是什么,你怎么可能得到severity参数呢?对我来说,这没有任何意义。你怎么能凭空想象一个整数值,并希望它与这个枚举类型匹配?进行一些轻微的重新设计可以让您简单地完成此操作,而不需要进行任何类型转换。

请看一下我的更新答案,或许现在会更清晰。 - Wodzu
1
问题之前已经非常清楚了,也许您可以重新阅读我的答案,看看是否更加清晰。如果您坚决要使用泛型,请继续使用。 - David Heffernan
泛型无法解决不安全的问题。你要么让“TSeverity”可用,要么进行转换。泛型如何帮助解决这些问题?但是,你似乎还没有准备好考虑泛型可能不是解决问题的方法。当我第一次这样说时,你告诉我不要提出这样的建议,如果你想用泛型解决问题,那么你会这样做。坦白地说,我认为你需要开放一点思路。 - David Heffernan
让我们在聊天中继续这个讨论。点击此处进入聊天室 - David Heffernan
就T2而言,它可以是任何东西 - 但我们知道在这种情况下它不是。 - Dsm
显示剩余7条评论

0
正如David Heffernan所说,您不能以这种方式使用泛型。相反,您应该使用一个函数将错误级别映射到严重性类型,并使用它将两者粘合在一起。根据您更新的示例,可以像这样修改它:
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.

然后,您可以提供粘合过程,将错误级别转换为严重性:

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;

请注意,我没有编译这个程序,但是它的本质在那里。我使用了一个过程引用(reference to procedure),因为它们更加灵活,以后可能会派上用场。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接