Вопрос

Одна приятная особенность анонимных методов заключается в том, что я могу использовать локальные переменные в контексте вызова.Есть ли причина, по которой это не работает для внешних параметров и результатов функций?

function ReturnTwoStrings (out Str1 : String) : String;
begin
  ExecuteProcedure (procedure
                    begin
                      Str1 := 'First String';
                      Result := 'Second String';
                    end);
end;

Конечно, очень искусственный пример, но я сталкивался с ситуациями, когда это было бы полезно.

Когда я пытаюсь скомпилировать это, компилятор жалуется, что «не может захватить символы».Кроме того, однажды, когда я попытался это сделать, у меня возникла внутренняя ошибка.

РЕДАКТИРОВАТЬ Я только что понял, что это работает для нормальных параметров, таких как

... (List : TList)

Разве это не так же проблематично, как и другие случаи?Кто гарантирует, что ссылка по-прежнему указывает на живой объект при каждом выполнении анонимного метода?

Это было полезно?

Решение

Параметры Var и out, а также переменную Result невозможно захватить, поскольку безопасность этой операции невозможно проверить статически.Если переменная Result имеет управляемый тип, например строку или интерфейс, память фактически выделяется вызывающей стороной, и ссылка на это хранилище передается как неявный параметр;другими словами, переменная Result, в зависимости от ее типа, аналогична выходному параметру.

Безопасность не может быть проверена по причине, упомянутой Джоном.Замыкание, созданное анонимным методом, может пережить активацию метода, в котором оно было создано, а также может пережить активацию метода, вызвавшего метод, в котором оно было создано.Таким образом, любые захваченные параметры var или out или переменные Result могут оказаться потерянными, и любая запись в них изнутри замыкания в будущем приведет к повреждению стека.

Конечно, Delphi не работает в управляемой среде и не имеет таких ограничений безопасности, как, например.С#.Язык может позволить вам делать то, что вы хотите.Однако в ситуациях, когда что-то пошло не так, будет сложно диагностировать ошибки.Плохое поведение могло бы проявиться в виде локальных переменных в обычном изменении значения без видимой непосредственной причины;было бы еще хуже, если бы ссылка на метод вызывалась из другого потока.

Это будет довольно сложно отладить.Даже точки останова аппаратной памяти были бы относительно плохим инструментом, поскольку стек часто модифицируется.Необходимо будет включать точки останова аппаратной памяти условно при достижении другой точки останова (например,при входе в метод).Отладчик Delphi может это сделать, но я рискну предположить, что большинство людей не знают об этой технике.

Обновлять:Что касается дополнений к вашему вопросу, семантика передачи ссылок на экземпляры по значению мало отличается между методами, которые содержат замыкание (и захватывают параметр0, и методами, которые не содержат замыкание.Любой метод может сохранять ссылку на аргумент, передаваемый по значению;методы, не фиксирующие параметр, могут просто добавить ссылку в список или сохранить ее в частном поле.

Ситуация иная с параметрами, передаваемыми по ссылке, поскольку ожидания вызывающего объекта другие.Программист делает это:

procedure GetSomeString(out s: string);
// ...
GetSomeString(s);

был бы крайне удивлен, если бы GetSomeString сохранил ссылку на s передана переменная.С другой стороны:

procedure AddObject(obj: TObject);
// ...
AddObject(TObject.Create);

Неудивительно, что AddObject сохраняет ссылку, поскольку само имя подразумевает, что он добавляет параметр в какое-то хранилище с состоянием.Независимо от того, имеет ли это хранилище с состоянием замыкание или нет, это деталь реализации AddObject метод.

Другие советы

Проблема в том, что ваша переменная Str1 не «принадлежит» ReturnTwoStrings, поэтому ваш анонимный метод не может ее захватить.

Причина, по которой он не может его захватить, заключается в том, что компилятор не знает конечного владельца (где-то в стеке вызовов в направлении вызова ReturnTwoStrings), поэтому он не может определить, откуда его захватить.

Редактировать: (Добавлено после комментария Сокрушитель)

Суть анонимных методов заключается в том, что они фиксируют переменные (а не их значения).

Аллен Бауэр (CodeGear) объясняет немного больше о захват переменных в его блоге.

Eсть Вопрос C# об обходе вашей проблемы также.

Параметр out и возвращаемое значение не имеют значения после возврата функции — как бы вы ожидали, что анонимный метод поведет себя, если бы вы его захватили и выполнили позже?(В частности, если вы используете анонимный метод для создания делегата, но никогда не выполняете его, параметр out и возвращаемое значение не будут установлены к моменту возврата функции.)

Выходные параметры особенно сложны — переменная, псевдонимы которой могут даже не существовать к моменту последующего вызова делегата.Например, предположим, что вам удалось захватить параметр out и вернуть анонимный метод, но параметр out является локальной переменной в вызывающей функции и находится в стеке.Если вызывающий метод затем вернулся после сохранения где-то делегата (или его возврата), что произойдет, когда делегат будет наконец вызван?Куда он будет писать, когда будет установлено значение параметра out?

Я помещаю это в отдельный ответ, потому что ваше EDIT делает ваш вопрос совсем другим.

Я, вероятно, расширю этот ответ позже, так как немного спешу добраться до клиента.

Ваше изменение указывает на то, что вам необходимо переосмыслить типы значений, ссылочные типы и влияние var, out, const и вообще отсутствие маркировки параметров.

Давайте сначала займемся типами значений.

Значения типов значений находятся в стеке и имеют поведение копирования при присвоении.(Я постараюсь включить пример на эту тему позже).

Если у вас нет маркировки параметров, фактическое значение, переданное методу (процедуре или функции), будет скопировано в локальное значение этого параметра внутри метода.Таким образом, метод работает не с переданным ему значением, а с копией.

Если у вас есть out, var или const, копирование не происходит:метод будет ссылаться на переданное фактическое значение.Для var это позволит изменить это фактическое значение, для const — нет.Во-первых, вы не сможете прочитать фактическое значение, но все равно сможете записать фактическое значение.

Значения ссылочных типов живут в куче, поэтому для них вряд ли имеет значение, есть ли у вас out, var, const или нет маркировки параметров:когда вы что-то меняете, вы меняете значение в куче.

Для ссылочных типов вы все равно получаете копию, если у вас нет маркировки параметров, но это копия ссылки, которая все еще указывает на значение в куче.

Здесь анонимные методы усложняются:они выполняют захват переменных.(Барри, вероятно, может объяснить это еще лучше, но я попробую) В вашем отредактированном случае анонимный метод захватит локальную копию списка.Анонимный метод будет работать с этой локальной копией, и с точки зрения компилятора все отлично.

Однако суть вашего редактирования заключается в сочетании «оно работает для обычных параметров» и «кто гарантирует, что ссылка по-прежнему указывает на живой объект всякий раз, когда выполняется анонимный метод».

Это всегда проблема со ссылочными параметрами, независимо от того, используете ли вы анонимные методы или нет.

Например это:

procedure TMyClass.AddObject(Value: TObject);
begin
  FValue := Value;
end;

procedure TMyClass.DoSomething();
begin
  ShowMessage(FValue.ToString());
end;

Кто гарантирует, что когда кто-то вызывает DoSomething, экземпляр, на который указывает FValue, все еще существует?Ответ заключается в том, что вы должны гарантировать это сами, не вызывая DoSomething, когда экземпляр FValue умер.То же самое относится и к вашему редактированию:вам не следует вызывать анонимный метод, когда базовый экземпляр умер.

Это одна из областей, где решения с подсчетом ссылок или сбором мусора облегчают жизнь:там экземпляр будет поддерживаться до тех пор, пока не исчезнет последняя ссылка на него (что может привести к тому, что экземпляр будет жить дольше, чем вы изначально ожидали!).

Итак, после вашего редактирования ваш вопрос фактически меняется с анонимных методов на последствия использования ссылочных типизированных параметров и управления сроком службы в целом.

Надеюсь, мой ответ поможет вам в этой области.

--джероен

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top