Pregunta

Una cosa buena de los métodos anónimos es que puedo usar variables que son locales en el contexto de la llamada. ¿Hay alguna razón por la que esto no funcione para los parámetros de salida y los resultados de la función?

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

Ejemplo muy artificial, por supuesto, pero me topé con algunas situaciones en las que esto hubiera sido útil.

Cuando intento compilar esto, el compilador se queja de que " no puede capturar símbolos " ;. Además, recibí un error interno una vez cuando intenté hacer esto.

EDIT Acabo de darme cuenta de que funciona para parámetros normales como

... (List : TList)

¿No es eso tan problemático como los otros casos? ¿Quién garantiza que la referencia sigue apuntando a un objeto vivo cada vez que se ejecuta el método anónimo?

¿Fue útil?

Solución

Los parámetros Var y out y la variable Resultado no se pueden capturar porque la seguridad de esta operación no se puede verificar de forma estática. Cuando la variable Resultado es de un tipo administrado, como una cadena o una interfaz, el llamante asigna realmente el almacenamiento y se pasa una referencia a este almacenamiento como un parámetro implícito; en otras palabras, la variable Resultado, dependiendo de su tipo, es como un parámetro de salida.

La seguridad no se puede verificar por la razón que Jon mencionó. El cierre creado por un método anónimo puede sobrevivir a la activación del método en el que se creó, y también puede sobrevivir a la activación del método que llamó al método en el que se creó. Por lo tanto, cualquier parámetro var o out o variable de Resultado capturada podría quedar huérfano, y cualquier escritura en el interior del cierre en el futuro corrompería la pila.

Por supuesto, Delphi no se ejecuta en un entorno administrado, y no tiene las mismas restricciones de seguridad como, por ejemplo, DO#. El idioma podría permitirte hacer lo que quieras. Sin embargo, resultaría difícil diagnosticar errores en situaciones en las que salió mal. El mal comportamiento se manifestaría como variables locales en un valor cambiante de rutina sin causa próxima visible; sería aún peor si la referencia del método fuera llamada desde otro hilo.

Esto sería bastante difícil de depurar. Incluso los puntos de interrupción de la memoria del hardware serían una herramienta relativamente pobre, ya que la pila se modifica con frecuencia. Uno tendría que activar los puntos de interrupción de la memoria del hardware de forma condicional al alcanzar otro punto de interrupción (por ejemplo, al ingresar el método). El depurador de Delphi puede hacer esto, pero me atrevería a suponer que la mayoría de las personas no conocen la técnica.

Actualización : con respecto a las adiciones a su pregunta, la semántica de pasar referencias de instancia por valor es poco diferente entre los métodos que contienen un cierre (y capturan el parámetro0 y los métodos que no contienen un cierre. Cualquiera de los dos métodos puede retener una referencia al argumento pasado por valor; los métodos que no capturan el parámetro pueden simplemente agregar la referencia a una lista, o almacenarla en un campo privado.

La situación es diferente con los parámetros pasados ??por referencia porque las expectativas de la persona que llama son diferentes. Un programador haciendo esto:

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

estaría extremadamente sorprendido si GetSomeString mantuviera una referencia a la variable s pasada. Por otra parte:

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

No es sorprendente que AddObject mantenga una referencia, ya que el mismo nombre implica que está agregando el parámetro a alguna tienda con estado. Si esa tienda con estado tiene la forma de un cierre o no, es un detalle de implementación del método AddObject .

Otros consejos

El problema es que su variable Str1 no es " poseída " por ReturnTwoStrings, para que su método anónimo no pueda capturarlo.

La razón por la que no puede capturarlo, es que el compilador no conoce al propietario final (en algún lugar de la pila de llamadas para llamar a ReturnTwoStrings), por lo que no puede determinar de dónde capturarlo.

Editar: (Agregado después de un comentario de Smasher )

El núcleo de los métodos anónimos es que capturan las variables (no sus valores).

Allen Bauer (CodeGear) explica un poco más sobre captura de variables en su blog .

Hay una C # question sobre eludir su problema también.

El parámetro de salida y el valor de retorno son irrelevantes después de que la función regrese. ¿Cómo espera que se comporte el método anónimo si lo captura y lo ejecuta más tarde? (En particular, si utiliza el método anónimo para crear un delegado pero nunca lo ejecuta, el parámetro de salida y el valor de retorno no se establecerían en el momento en que se devuelva la función).

Los parámetros de salida son particularmente difíciles: la variable de que los alias de los parámetros de salida pueden no existir para cuando más tarde llames al delegado. Por ejemplo, suponga que pudo capturar el parámetro de salida y devolver el método anónimo, pero el parámetro de salida es una variable local en la función de llamada y está en la pila. Si el método de llamada regresaba luego de almacenar el delegado en algún lugar (o devolverlo), ¿qué pasaría cuando finalmente se llamara al delegado? ¿Dónde se escribiría cuando se configuró el valor del parámetro out?

Pongo esto en una respuesta por separado porque su EDITAR hace que su pregunta sea realmente diferente.

Probablemente extenderé esta respuesta más adelante ya que tengo un poco de prisa por llegar a un cliente.

Su edición indica que debe reconsiderar los tipos de valores, los tipos de referencia y el efecto de var, out, const y sin marca de parámetros.

Hagamos lo primero en los tipos de valor.

Los valores de los tipos de valor viven en la pila y tienen un comportamiento de copia en la asignación. (Intentaré incluir un ejemplo en eso más adelante).

Cuando no tenga una marca de parámetro, el valor real pasado a un método (procedimiento o función) se copiará al valor local de ese parámetro dentro del método. Por lo tanto, el método no funciona con el valor que se le pasa, sino con una copia.

Cuando haya salido, var o const, entonces no se realizará ninguna copia: el método se referirá al valor real pasado. Para var, permitirá cambiar ese valor real, para const no lo permitirá. Por fuera, no podrá leer el valor real, pero podrá escribir el valor real.

Los valores de los tipos de referencia viven en el montón, por lo que para ellos no importa si tiene marcadas las variables var, const o no: cuando cambia algo, cambia el valor del montón.

Para los tipos de referencia, aún obtienes una copia cuando no tienes marcado de parámetros, pero es una copia de una referencia que aún apunta al valor en el montón.

Aquí es donde los métodos anónimos se complican: hacen una captura de variable. (Barry probablemente puede explicar esto incluso mejor, pero lo intentaré) En su caso editado, el método anónimo capturará la copia local de la Lista. El método anónimo funcionará en esa copia local y, desde la perspectiva del compilador, todo es excelente.

Sin embargo, el quid de su edición es la combinación de 'funciona para parámetros normales' y 'quien garantiza que la referencia todavía apunta a un objeto vivo cada vez que se ejecuta el método anónimo'.

Eso siempre es un problema con los parámetros de referencia, no importa si usas métodos anónimos o no.

Por ejemplo esto:

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

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

¿Quién garantiza que cuando alguien llama DoSomething, la instancia a la que apunta FValue todavía existe? La respuesta es que debe garantizar esto usted mismo al no llamar a DoSomething cuando la instancia de FValue haya fallecido. Lo mismo se aplica a su edición: no debe llamar al método anónimo cuando la instancia subyacente haya muerto.

Esta es una de las áreas donde las soluciones de recuento de referencias o de recolección de basura facilitan la vida: ¡la instancia se mantendrá viva hasta que la última referencia haya desaparecido (lo que podría causar que la instancia viva más de lo que originalmente anticipó!) .

Entonces, con su edición, su pregunta realmente cambia de métodos anónimos a las implicaciones de usar parámetros escritos de referencia y la administración de por vida en general.

Espero que mi respuesta te ayude a ir a esa área.

--jeroen

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top