我正在尝试构建一个自定义比较器,它允许将比较函数分配给内部字段.为了简化比较器的创建,我尝试添加一个类似构造函数的类函数Construct
来初始化比较器.
现在,如果我尝试编译以下示例,则显示编译器
[dcc32 Fehler] ConsoleDemo1.dpr(37):E2555无法跟踪符号'结果'
我有以下示例代码:
program ConsoleDemo1; {$APPTYPE CONSOLE} {$R *.res} uses Generics.Collections, Generics.Defaults, System.SysUtils; type TConstFunc= reference to function(const Arg1: T1; const Arg2: T2): TResult; TDemo = class(TComparer ) private FVar: TConstFunc ; 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.
J..... 12
我不认为这是一个错误.关键的是,您已将其定义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).因为成员方法是procedure of object
(对象方法)类型,并且您将它分配给reference to procedure
(匿名方法)类型,这相当于(并且我怀疑编译器正在扩展它):
class function TDemo.Construct: TDemo; begin result := TDemo.Create(); result.FFoo := procedure begin result.foo; end; end;
编译器不能直接分配方法引用(因为它们是不同的类型),因此(我猜)必须将它包装在匿名方法中,该方法隐式地需要捕获result
变量. 但是,匿名方法无法捕获函数返回值,只能使用局部变量.
在你的情况下(或者,实际上,对于任何function
类型),由于匿名包装器隐藏result
变量,甚至无法表达等价物,但我们可以想象在理论上相同:
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= function(const Arg1: T1; const Arg2: T2): TResult of object;
尝试捕获的另一个示例result
失败:
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.
[dcc32错误] Project1.dpr(18):E2555无法捕获符号'x'
对于引用类型,如在原始示例中,您实际上只对捕获引用的值而不是包含它的变量感兴趣.这不会使它在语法上等效,编译器为此目的创建一个新变量是不合适的.
我们可以重写上面的内容,引入一个变量:
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;
我们没有为正在存储foo
的实例的方法分配正常引用,但实际上已经捕获了变量本身,而不是当时包含的值.设置于后自然产生的访问冲突,甚至认为它称作时作出转让仍然活着的对象实例. TDemo
LDemo
LDemo
LDemo
nil
这与我们简单定义为a 而不是a的行为截然不同.如果我们这样做了,上面的代码就像人们可能天真地期望的那样(输出到控制台). TFoo
procedure of object
reference to procedure
foobar
我不认为这是一个错误.关键的是,您已将其定义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).因为成员方法是procedure of object
(对象方法)类型,并且您将它分配给reference to procedure
(匿名方法)类型,这相当于(并且我怀疑编译器正在扩展它):
class function TDemo.Construct: TDemo; begin result := TDemo.Create(); result.FFoo := procedure begin result.foo; end; end;
编译器不能直接分配方法引用(因为它们是不同的类型),因此(我猜)必须将它包装在匿名方法中,该方法隐式地需要捕获result
变量. 但是,匿名方法无法捕获函数返回值,只能使用局部变量.
在你的情况下(或者,实际上,对于任何function
类型),由于匿名包装器隐藏result
变量,甚至无法表达等价物,但我们可以想象在理论上相同:
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= function(const Arg1: T1; const Arg2: T2): TResult of object;
尝试捕获的另一个示例result
失败:
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.
[dcc32错误] Project1.dpr(18):E2555无法捕获符号'x'
对于引用类型,如在原始示例中,您实际上只对捕获引用的值而不是包含它的变量感兴趣.这不会使它在语法上等效,编译器为此目的创建一个新变量是不合适的.
我们可以重写上面的内容,引入一个变量:
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;
我们没有为正在存储foo
的实例的方法分配正常引用,但实际上已经捕获了变量本身,而不是当时包含的值.设置于后自然产生的访问冲突,甚至认为它称作时作出转让仍然活着的对象实例. TDemo
LDemo
LDemo
LDemo
nil
这与我们简单定义为a 而不是a的行为截然不同.如果我们这样做了,上面的代码就像人们可能天真地期望的那样(输出到控制台). TFoo
procedure of object
reference to procedure
foobar
我的英文Delphi上的编译器错误如下:
[dcc32错误] E2555无法捕获符号'结果'
这是由于设计有缺陷.根本没有理由在这里进行任何变量捕获.赋值的右侧是实例方法而不是匿名方法.但是编译器通过将方法包装在匿名方法中来处理它.编译器进行翻译
Result.FVar := Result.CompareInternal;
至
Result.FVar := function(const Arg1, Arg2: string): Integer begin InnerResult := OuterResult.CompareInternal(Arg1, Arg2); end;
抛开对两个单独的结果变量的混淆,编译器拒绝这一点,因为外部结果变量不是本地变量,它是一个var
参数.因此无法捕获.
但在我看来,整个设计是错误的.不需要任何变量捕获.当你写作时,Result.CompareInternal
你打算引用一种常规of object
方法.通过更好的设计,编译器将允许此分配而无需创建匿名方法.
您可以像这样解决问题:
class function TDemo.Construct: TDemo; var Demo: TDemo; begin Demo := TDemo.Create(); Demo.FVar := Demo.CompareInternal; Result := Demo; end;
这里Demo
可以捕获局部变量.
或者像我建议的那样:
program ConsoleDemo1; {$APPTYPE CONSOLE} uses Generics.Defaults, System.SysUtils; type TConstFunc= reference to function(const Arg1: T1; const Arg2: T2): TResult; TDemo = class(TComparer ) private FVar: TConstFunc ; function CompareInternal(const L, R: string): Integer; public constructor Create; function Compare(const L, R: string): Integer; override; end; constructor TDemo.Create; begin inherited; FVar := CompareInternal; 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; end.