Ответ 1
Я не думаю, что это ошибка. Критически, вы определили 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). Поскольку метод member является типом procedure of object
(object method), и вы назначаете его типу 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;
Как показал Дэвид, введение локальной переменной (которая может быть записана) является одним из правильных решений. В качестве альтернативы, если вам не нужен тип TConstFunc
для анонимности, вы можете просто объявить его как обычный метод объекта:
TConstFunc<T1, T2, TResult> = 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 Error] 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
впоследствии естественным образом приводит к нарушению доступа, даже если предположить, что экземпляр объекта, на который он ссылался при выполнении задания, все еще жив.
Это радикально другое поведение, чем если бы мы просто определяли TFoo
как procedure of object
вместо reference to procedure
. Если бы мы это сделали, приведенный выше код работает так, как можно было бы наивно ожидать (вывод foobar
на консоль).