Pascal如何构建可管理不同procedure of object类型的泛型类
解决方案
Delphi 原生泛型暂不支持将 procedure of object 作为类型参数的编译期约束,可通过以下两种方案实现相同的通用回调管理能力:
方案1:全平台通用实现(运行时类型校验)
所有 procedure of object/function of object 类型本质都是 TMethod 结构,我们可以通过运行时校验泛型参数的类型信息,实现通用基类,仅在传入非方法类型时报错。通用逻辑(订阅、取消订阅等)全部封装在基类中,仅触发回调的逻辑按不同签名在子类实现即可。
代码实现
unit FunctionManager; interface uses System.SysUtils, System.Generics.Collections, System.TypInfo; type // 通用泛型回调管理器基类 TFunctionManager<T> = class(TList<T>) public constructor Create; // 订阅回调:可扩展去重、优先级等逻辑 procedure Subscribe(fnCallback: T); // 取消订阅 procedure Unsubscribe(fnCallback: T); end; // 无参数通知回调管理器示例 TNotifyEventManager = class(TFunctionManager<TNotifyEvent>) public procedure FireCallbacks(Sender: TObject); end; // 带Int参数返回布尔值的回调管理器示例 TNotifyIntEventManager = class(TFunctionManager<TNotifyIntEvent>) public // 示例逻辑:所有回调返回True时整体返回True,否则返回False function FireCallbacks(Sender: TObject; Value: Integer): Boolean; end; implementation { TFunctionManager<T> } constructor TFunctionManager<T>.Create; begin inherited Create; // 运行时校验泛型参数必须是方法类型 if PTypeInfo(TypeInfo(T))^.Kind <> tkMethod then raise EInvalidCast.Create('泛型参数T必须是方法指针(of object)类型'); end; procedure TFunctionManager<T>.Subscribe(fnCallback: T); begin // 可添加重复订阅校验逻辑 if IndexOf(fnCallback) = -1 then Add(fnCallback); end; procedure TFunctionManager<T>.Unsubscribe(fnCallback: T); begin Remove(fnCallback); end; { TNotifyEventManager } procedure TNotifyEventManager.FireCallbacks(Sender: TObject); var ev: TNotifyEvent; begin for ev in Self do ev(Sender); end; { TNotifyIntEventManager } function TNotifyIntEventManager.FireCallbacks(Sender: TObject; Value: Integer): Boolean; var ev: TNotifyIntEvent; begin Result := True; for ev in Self do begin if not ev(Sender, Value) then Result := False; end; end;
使用示例
// 实例化不同类型的回调管理器 var notifyMgr: TNotifyEventManager; intNotifyMgr: TNotifyIntEventManager; begin notifyMgr := TNotifyEventManager.Create; // 订阅回调 notifyMgr.Subscribe(MyNotifyProc); // 触发所有回调 notifyMgr.FireCallbacks(Self); // 取消订阅 notifyMgr.Unsubscribe(MyNotifyProc); notifyMgr.Free; end;
方案2:Free Pascal专属实现(编译期约束)
Free Pascal 3.0及以上版本原生支持方法类型作为泛型约束,可直接按你预期的写法实现:
{$mode objfpc} {$modeswitch advancedrecords} {$modeswitch generics} uses Contnrs, Generics.Collections; type // 直接声明带方法类型约束的泛型类 generic TFunctionManager<T: procedure of object> = class(specialize TList<T>) public procedure Subscribe(fnCallback: T); procedure Unsubscribe(fnCallback: T); // 其余通用方法 end;
注意事项
- 运行时校验会在类实例化时触发,开发阶段即可发现传入非法类型的问题,不会影响线上运行性能
- 不同签名的回调触发逻辑无法通用,需根据业务需求在对应子类实现即可
内容的提问来源于stack exchange,提问作者Rob Lambden
相关产品推荐
相关产品推荐

