delphi – 为什么我不能将我的函数引用分配给一个匹配的变量? E
我正在尝试构建一个自定义比较器,它允许将比较功能分配给内部字段.为了简化比较器的创建,我尝试添加一个类构造函数类函数Construct来初始化比较器.
现在,如果我尝试编译以下示例,编译器将显示
我有以下示例代码: program ConsoleDemo1; {$APPTYPE CONSOLE} {$R *.res} uses Generics.Collections,Generics.Defaults,System.SysUtils; type TConstFunc<T1,T2,TResult> = reference to function(const Arg1: T1; const Arg2: T2): TResult; TDemo = class(TComparer<string>) private FVar: TConstFunc<string,string,Integer>; function CompareInternal(const L,R: string): Integer; public class function Construct(): TDemo; function Compare(const L,R: string): Integer; override; end; function TDemo.Compare(const L,R: string): Integer; begin Result := FVar(L,R); end; function TDemo.CompareInternal(const L,R: string): Integer; begin Result := AnsiCompareStr(L,R); end; class function TDemo.Construct: TDemo; begin Result := TDemo.Create(); Result.FVar := Result.CompareInternal; end; end. 解决方法
我不认为这是一个bug.最重要的是,您将TConstFunc定义为匿名方法类型.这些被管理,引用计数,非常特殊的类型与常规对象方法截然不同.通过编译器魔术,它们通常是分配兼容的,但有几个重要的注意事项.考虑更简洁:
program Project1; {$APPTYPE CONSOLE} type TFoo = reference to procedure; TDemo = class private FFoo : TFoo; procedure Foo; public class function Construct(): TDemo; end; procedure TDemo.Foo; begin WriteLn('foo'); end; class function TDemo.Construct: TDemo; begin result := TDemo.Create(); result.FFoo := result.foo; end; end. 这也产生相同的编译器错误(E2555).因为成员方法是一个object(object method)类型的过程,而是将它分配给一个对过程(匿名方法)类型的引用,这相当于(我怀疑编译器正在将其扩展为): class function TDemo.Construct: TDemo; begin result := TDemo.Create(); result.FFoo := procedure begin result.foo; end; end; 编译器不能直接分配方法引用(因为它们是不同类型的),因此(我猜想)必须将它包装成一个隐式需要捕获结果变量的匿名方法.函数返回值不能被匿名方法捕获,但只有局部变量可以. 在你的情况下(或者,实际上,对于任何函数类型),由于匿名包装器隐藏了结果变量,所以不能表达等价物,但我们可以想象在理论上是相同的: class function TDemo.Construct: TDemo; begin Result := TDemo.Create(); Result.FVar := function(const L,R : string) : integer begin result := result.CompareInternal(L,R); // ** can't do this end; end; 正如David所说,引入一个局部变量(可以被捕获)是一个正确的解决方案.或者,如果您不需要TConstFunc类型为匿名,则可以简单地将其声明为常规对象方法: TConstFunc<T1,TResult> = function(const Arg1: T1; const Arg2: T2): TResult of object; 尝试捕获结果的另一个示例将失败: program Project1; {$APPTYPE CONSOLE} type TBar = reference to procedure; TDemo = class private FFoo : Integer; FBar : TBar; public class function Construct(): TDemo; end; class function TDemo.Construct: TDemo; begin result := TDemo.Create(); result.FFoo := 1; result.FBar := procedure begin WriteLn(result.FFoo); end; end; end. 这个不起作用的根本原因是因为一个方法的返回值实际上是一个var参数,匿名闭包捕获变量而不是值.这是一个关键点.同样,这也是不允许的: program Project1; {$APPTYPE CONSOLE} type TFoo = reference to procedure; TDemo = class private FFoo : TFoo; procedure Bar(var x : integer); end; procedure TDemo.Bar(var x: Integer); begin FFoo := procedure begin WriteLn(x); end; end; begin end.
在引用类型的情况下,如在原始示例中,您真的只对捕获引用的值而不是包含该引用的变量感兴趣.这不会使其在语法上相同,编译器为此而为您创建一个新变量是不合适的. 我们可以重写上面的内容,引入一个变量: procedure TDemo.Bar(var x: Integer); var y : integer; begin y := x; FFoo := procedure begin WriteLn(y); end; end; 这是允许的,但预期的行为将是非常不同的.在捕获x(不允许)的情况下,我们期望FFoo总是将任何变量作为参数x传递的当前值写入到Bar,而不管在此过程中哪里或何时被更改.我们也预期,即使在创建它的任何范围之后,关闭也会保持变量存活. 然而,在后一种情况下,我们预期FFoo输出y的值,它是变量x的值,就像最后一次调用Bar一样. 回到第一个例子,考虑一下: program Project1; {$APPTYPE CONSOLE} type TFoo = reference to procedure; TDemo = class private FFoo : TFoo; FBar : string; procedure Foo; public class function Construct(): TDemo; end; procedure TDemo.Foo; begin WriteLn('foo' + FBar); end; class function TDemo.Construct: TDemo; var LDemo : TDemo; begin result := TDemo.Create(); LDemo := result; LDemo.FBar := 'bar'; result.FFoo := LDemo.foo; LDemo := nil; result.FFoo(); // **access violation end; var LDemo:TDemo; begin LDemo := TDemo.Construct; end. 这里很清楚: result.FFoo := LDemo.foo; 我们还没有为存储在LDemo中的TDemo实例分配方法foo的正常引用,但实际上已经捕获了变量LDemo本身,而不是当时包含的值.将LDemo设置为零后,自然会产生访问冲突,甚至认为在分配作业时引用的对象实例仍然存在. 这与我们简单地将TFoo定义为对象的过程而不是引用过程的行为截然不同.如果我们这样做,那么上面的代码就像一个可能天真地期待的(输出foobar到控制台). (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |