ven*_*eis 15 delphi anonymous-function delphi-10-seattle
我正在尝试构建一个自定义比较器,它允许将比较函数分配给内部字段.为了简化比较器的创建,我尝试添加一个类似构造函数的类函数Construct来初始化比较器.
现在,如果我尝试编译以下示例,则显示编译器
[dcc32 Fehler] ConsoleDemo1.dpr(37):E2555无法跟踪符号'结果'
我有以下示例代码:
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.
Run Code Online (Sandbox Code Playgroud)
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.
Run Code Online (Sandbox Code Playgroud)
这也会产生相同的编译器错误(E2555).因为成员方法是procedure of object(对象方法)类型,并且您将它分配给reference to procedure(匿名方法)类型,这相当于(并且我怀疑编译器正在扩展它):
class function TDemo.Construct: TDemo;
begin
result := TDemo.Create();
result.FFoo := procedure
begin
result.foo;
end;
end;
Run Code Online (Sandbox Code Playgroud)
编译器不能直接分配方法引用(因为它们是不同的类型),因此(我猜)必须将它包装在匿名方法中,该方法隐式地需要捕获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;
Run Code Online (Sandbox Code Playgroud)
正如David所示,引入局部变量(可以捕获)是一个正确的解决方案.或者,如果您不需要TConstFunc匿名类型,则可以将其声明为常规对象方法:
TConstFunc<T1, T2, TResult> = function(const Arg1: T1; const Arg2: T2): TResult of object;
Run Code Online (Sandbox Code Playgroud)
尝试捕获的另一个示例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.
Run Code Online (Sandbox Code Playgroud)
这不起作用的根本原因是因为方法的返回值实际上是一个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.
Run Code Online (Sandbox Code Playgroud)
[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;
Run Code Online (Sandbox Code Playgroud)
这是允许的,但预期的行为将是非常不同的.在拍摄的情况下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.
Run Code Online (Sandbox Code Playgroud)
这里很清楚:
result.FFoo := LDemo.foo;
Run Code Online (Sandbox Code Playgroud)
我们没有为正在存储foo的实例的方法分配正常引用,但实际上已经捕获了变量本身,而不是当时包含的值.设置于后自然产生的访问冲突,甚至认为它称作时作出转让仍然活着的对象实例. TDemoLDemo LDemoLDemonil
这与我们简单定义为a 而不是a的行为截然不同.如果我们这样做了,上面的代码就像人们可能天真地期望的那样(输出到控制台). TFooprocedure of objectreference to procedurefoobar
Dav*_*nan 11
我的英文Delphi上的编译器错误如下:
[dcc32错误] E2555无法捕获符号'结果'
这是由于设计有缺陷.根本没有理由在这里进行任何变量捕获.赋值的右侧是实例方法而不是匿名方法.但是编译器通过将方法包装在匿名方法中来处理它.编译器进行翻译
Result.FVar := Result.CompareInternal;
Run Code Online (Sandbox Code Playgroud)
至
Result.FVar :=
function(const Arg1, Arg2: string): Integer
begin
InnerResult := OuterResult.CompareInternal(Arg1, Arg2);
end;
Run Code Online (Sandbox Code Playgroud)
抛开对两个单独的结果变量的混淆,编译器拒绝这一点,因为外部结果变量不是本地变量,它是一个var参数.因此无法捕获.
但在我看来,整个设计是错误的.不需要任何变量捕获.当你写作时,Result.CompareInternal你打算引用一种常规of object方法.通过更好的设计,编译器将允许此分配而无需创建匿名方法.
您可以像这样解决问题:
class function TDemo.Construct: TDemo;
var
Demo: TDemo;
begin
Demo := TDemo.Create();
Demo.FVar := Demo.CompareInternal;
Result := Demo;
end;
Run Code Online (Sandbox Code Playgroud)
这里Demo可以捕获局部变量.
或者像我建议的那样:
program ConsoleDemo1;
{$APPTYPE CONSOLE}
uses
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
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.
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
1168 次 |
| 最近记录: |