Сфера анонимных методов - PullRequest
10 голосов
/ 29 апреля 2009

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

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

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

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

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

... (List : TList)

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

Ответы [ 4 ]

20 голосов
/ 29 апреля 2009

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

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

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

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

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

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

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

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

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

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

6 голосов
/ 29 апреля 2009

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

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

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

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

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

Существует C # вопрос об обходе вашей проблемы .

4 голосов
/ 29 апреля 2009

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

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

0 голосов
/ 01 мая 2009

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- Йерун

...