2014-07-26 37 views
1

编辑,澄清这个问题,因为我简化太多,从而消除了我实际上正面临为什么我不能在方法体之外声明一个通用的匿名方法?

这个问题我有在长身体的实现委托。
因此,我不想在我使用它的函数中声明它。

type 
    TTaskDelegate<A, B> = reference to procedure(const Data: IData); 
    //-----------^^^^^^ note the type parameters here 
    //-But no type parameters here---------------------------^^^^^^ 

的委托声明一样,这样我可以将其存储在一个看起来像记录:

TMultiDelegate = record 
    strict private 
    fAA: TTaskDelegate<TOmniValue, TOmniValue>; 
    fAB: TTaskDelegate<TOmniValue, IOmniBlockingCollection>; 
    fBA: TTaskDelegate<IOmniBlockingCollection, TOmniValue>; 
    fBB: TTaskDelegate<IOmniBlockingCollection, IOmniBlockingCollection>; 
    fSimple: TSimpleTaskDelegate; 
    fOutputCount: Integer; 
    function GetDelegateType: TDelegateType; 
    public 
    constructor Init(AA: TTaskDelegate<TOmniValue, TOmniValue>; const OutputCount: integer = 1); overload; 
    constructor Init(AB: TTaskDelegate<TOmniValue, IOmniBlockingCollection>;   const OutputCount: integer = 1); overload; 
    ..... 

类型参数也起到提醒的通用程序的实现者是什么输入和输出类型是。

因为类型参数在方法头的其余部分中没有重复,所以在声明函数时必须保留它们。
因此Stefan的答案不起作用。

只是声明它作为单位常量(或单位变量)不起作用。
由于其通用签名,将其声明为单位过程也不起作用。
下面的代码无法编译:

样品A

const 
    Test: integer = 0; 

const 
    DoesNotCompile: TTaskDelegate<TOmniValue, TOmniValue> = 
    procedure(const Data: IData) 
    begin 
     //Do stuff  
    end; 

E2026 Constant expression expected

//This variant will not compile either. 
procedure DoStuff<TOmniValue, TOmniValue>(const Data: IData) 
begin 
    //DoStuff 
end; 

当我把它包在它的工作的功能。

样品B

function ListSplitterDelegate: TTaskDelegate<TOmniValue, TOmniValue>; 
begin 
    Result:= 
    procedure(const Data: IData) 
    begin 
     //Do stuff  
    end; 
end; 

感觉有点画蛇添足这种方式来做到这一点。
有没有办法避免必须将通用匿名函数包装在另一个函数中?

回答

0

你所要做的是不可能的。匿名方法是有状态的,参考计数对象本质上不是恒定的。赋值匿名方法会产生一个闭包,其状态会随着环境的变化而不断检查和修改。它在某些方面与其他编译器管理的类型相似,例如动态数组,由于某种类似的原因,它也不能是const

您创建ListSplitterDelegate函数的解决方案可能是您可以做的最好的解决方案。否则,您需要声明DoesNotCompile作为变量并在运行时分配它。

var 
    CompilesOk : TTaskDelegate<TOmniValue, TOmniValue>; 

    //... 

initialization 
    CompilesOk := procedure(const Data: IData<TOmniValue, TOmniValue>) 
       begin 
        //Do stuff  
       end; 

显然,这有CompilesOk可以被覆盖的问题(否则为许多原因是一个坏主意)。 ListSplitterDelegate是最好的解决方案如果你需要这是一个匿名方法。我不认为你需要这是一个匿名方法,但是,因为有可能分配一个reference to类型的常规方法

SSCCE证明(使用更新后的代码示例和签名):

unit Unit1; 

interface 

type 
    IData = interface 
    end; 
    TOmniValue = record 
    end; 
    TTaskDelegate<A,B> = reference to procedure(const Data: IData); 

    TMultiDelegate = record 
    strict private 
    fAA: TTaskDelegate<TOmniValue, TOmniValue>; 
    public 
    constructor Init(AB: TTaskDelegate<TOmniValue, TOmniValue>); 
    procedure DoIt; 
    end; 

implementation 

constructor TMultiDelegate.Init(AB: TTaskDelegate<TOmniValue, TOmniValue>); 
begin 
    fAA := AB; 
end; 

procedure TMultiDelegate.DoIt; 
var dat: IData; 
begin 
    fAA(dat); 
end; 

end. 

主营:

program Project1; 

{$APPTYPE CONSOLE} 
{$R *.res} 

uses 
    Unit1 in 'Unit1.pas'; 

procedure DoSomething(const Data : IData); 
begin 
    writeLn('foo'); 
end; 

var 
    tmd : TMultiDelegate; 
begin 
    tmd.Init(DoSomething); 
    tmd.DoIt; 
    ReadLn; 
end. 

编译罚款。按预期运行。在德尔福XE2测试。

+1

在单元的初始化部分直接分配它不是一个好主意,因为这会导致内存泄漏报告。如果你想这样做,你应该把它放到一个例程中,并在初始化部分调用它。 –

+0

@StefanGlienke有很多原因,这不是一个好主意。我只是用它来证明需要在运行时创建匿名方法。另外,将它放入常规会使我们回到使用Johan的'ListSplitterDelegate'的(更好的)解决方案的复杂程度,所以根本没有理由使用上述方法。 –

2

更新到编辑的问题:

声明它作为正常程序应该只是罚款:

procedure Whatever_TOmniValue_TOmniValue(const Data: IData); 
begin 
    //Do stuff  
end; 
+0

是的,对于普通功能它可以,但不能用带有通用点缀的方法。该函数看起来像这样:'procedure Whatever (const Data:IData ); 开始 //做东西 end;'{{这是没有意义的,在任何情况下都不允许} – Johan

+0

使用你给出的确切过程给出错误:'E2035没有足够的实际参数',因为签名不匹配; '无论'不是'TTaskDelegate',它只是一个简单的TTaskDelegate。 – Johan

+0

我写的例程与您声明的'TTaskDelegate 兼容。 –

相关问题