Question

Un des avantages des méthodes anonymes est que je peux utiliser des variables locales dans le contexte de l'appel. Y a-t-il une raison pour laquelle cela ne fonctionne pas pour les paramètres sortants et les résultats de fonction?

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

Exemple très artificiel bien sûr, mais j’ai rencontré quelques situations où cela aurait été utile.

Lorsque j'essaie de le compiler, le compilateur se plaint de "ne pas pouvoir capturer les symboles". De plus, j'ai eu une erreur interne une fois lorsque j'ai essayé de le faire.

MODIFIER je viens de me rendre compte que cela fonctionne pour des paramètres normaux tels que

... (List : TList)

N’est-ce pas aussi problématique que les autres cas? Qui garantit que la référence pointe toujours sur un objet vivant lorsque la méthode anonyme est exécutée?

Était-ce utile?

La solution

Les paramètres Var et Out et la variable Résultat ne peuvent pas être capturés car la sécurité de cette opération ne peut pas être vérifiée de manière statique. Lorsque la variable de résultat est d'un type géré, tel qu'une chaîne ou une interface, la mémoire est effectivement allouée par l'appelant et une référence à cette mémoire est transmise en tant que paramètre implicite. autrement dit, la variable Result, en fonction de son type, est comme un paramètre out.

La sécurité ne peut pas être vérifiée pour la raison mentionnée par Jon. La fermeture créée par une méthode anonyme peut survivre à l'activation de la méthode où elle a été créée et, de la même manière, à l'activation de la méthode qui a appelé la méthode à laquelle elle a été créée. Ainsi, tout paramètre var ou out ou les variables de résultat capturées pourraient devenir orphelins et toute écriture dans la fermeture à l’avenir corromprait la pile.

Bien entendu, Delphi ne s’exécute pas dans un environnement géré et n’a pas les mêmes restrictions de sécurité que par exemple. C #. La langue pourrait vous laisser faire ce que vous voulez. Cependant, il serait difficile de diagnostiquer les bogues dans des situations où cela aurait mal tourné. Le mauvais comportement se manifesterait en tant que variables locales dans une routine changeant de valeur sans cause immédiate visible; ce serait encore pire si la référence de la méthode était appelée depuis un autre thread.

Cela serait assez difficile à déboguer. Même les points d'arrêt de la mémoire matérielle seraient un outil relativement médiocre, car la pile est modifiée fréquemment. Il serait nécessaire d'activer les points d'arrêt de la mémoire matérielle de manière conditionnelle lors de la frappe d'un autre point d'arrêt (par exemple, lors de la saisie de la méthode). Le débogueur Delphi peut le faire, mais je suppose que la plupart des gens ne connaissent pas la technique.

Mettre à jour : en ce qui concerne les ajouts à votre question, la sémantique de passage de références d'instances par valeur diffère peu d'une méthode à l'autre (qui capture les paramètres fermete0 et capture les paramètres ne contenant pas Chaque méthode peut conserver une référence à l’argument passé par valeur; les méthodes ne capturant pas le paramètre peuvent simplement ajouter la référence à une liste ou la stocker dans un champ privé.

La situation est différente avec les paramètres passés par référence car les attentes de l'appelant sont différentes. Un programmeur faisant cela:

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

serait extrêmement surpris que GetSomeString conserve une référence à la variable s transmise. D'autre part:

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

Il n'est pas surprenant que AddObject conserve une référence, car son nom même implique qu'il ajoute le paramètre à un magasin avec état. Que ce magasin à état ait la forme d'une fermeture ou non est un détail d'implémentation de la méthode AddObject .

Autres conseils

Le problème est que votre variable Str1 n'est pas "détenue". par ReturnTwoStrings, de sorte que votre méthode anonyme ne puisse pas la capturer.

La raison pour laquelle il ne peut pas le capturer est que le compilateur ne connaît pas le propriétaire ultime (quelque part dans la pile d'appels vers l'appel de ReturnTwoStrings), il ne peut donc pas déterminer où le capturer.

Modifier: (ajouté après un commentaire de Smasher )

Le noyau des méthodes anonymes est qu'elles capturent les variables (pas leurs valeurs).

Allen Bauer (CodeGear) en explique un peu plus sur à propos de la capture de variable sur son blog .

Il existe un question C #. de contourner votre problème .

Le paramètre out et la valeur de retour ne sont plus pertinents après le retour de la fonction. Comment vous attendez-vous à ce que la méthode anonyme se comporte si vous l'avez capturée et exécutée ultérieurement? (En particulier, si vous utilisez la méthode anonyme pour créer un délégué mais ne l'exécutez jamais, le paramètre out et la valeur renvoyée ne seraient pas définis au moment du retour de la fonction.)

Les paramètres de sortie sont particulièrement difficiles - la variable selon laquelle les alias de paramètres de sortie peuvent ne pas exister au moment où vous appelez ultérieurement le délégué. Par exemple, supposons que vous puissiez capturer le paramètre out et renvoyer la méthode anonyme, mais que le paramètre out est une variable locale de la fonction appelante et qu'il se trouve sur la pile. Si la méthode appelante était ensuite renvoyée après avoir stocké le délégué quelque part (ou l'avait renvoyée), que se passerait-il lorsque le délégué serait finalement appelé? Où écrirait-il lorsque la valeur du paramètre out était définie?

Je mets ceci dans une réponse séparée parce que votre modification rend votre question très différente.

Je vais probablement étendre cette réponse plus tard, car je suis un peu pressé de contacter un client.

Votre édition indique que vous devez repenser les types de valeur, les types de référence et l'effet de var, out, const et aucun paramètre de marquage.

Commençons par les types de valeur.

Les valeurs des types de valeur résident dans la pile et ont un comportement de copie lors de l'affectation. (J'essaierai d'inclure un exemple à ce sujet plus tard).

Lorsque vous n’avez pas de marquage de paramètre, la valeur réelle transmise à une méthode (procédure ou fonction) sera copiée dans la valeur locale de ce paramètre dans la méthode. La méthode ne fonctionne donc pas sur la valeur qui lui est transmise, mais sur une copie.

Lorsque vous avez out, var ou const, aucune copie n’est effectuée: la méthode fera référence à la valeur réelle transmise. Pour var, cela permettra de changer cette valeur réelle, pour const cela ne le permettra pas. Dans le cas contraire, vous ne pourrez pas lire la valeur réelle, mais pourrez néanmoins écrire la valeur réelle.

Les valeurs des types de référence sont stockées dans le segment de mémoire. Par conséquent, peu importe si elles ont un marqueur out, var, const ou aucun paramètre: lorsque vous modifiez quelque chose, vous modifiez la valeur du segment.

Pour les types de référence, vous obtenez toujours une copie lorsque vous ne définissez aucun paramètre, mais il s'agit d'une copie d'une référence qui pointe toujours vers la valeur du segment de mémoire.

C’est là que les méthodes anonymes se compliquent: elles effectuent une capture de variable. (Barry peut probablement expliquer cela encore mieux, mais je vais essayer) Dans votre cas modifié, la méthode anonyme capturera la copie locale de la liste. La méthode anonyme fonctionnera sur cette copie locale et, du point de vue du compilateur, tout est dandy.

Cependant, le point crucial de votre édition est la combinaison de "cela fonctionne pour les paramètres normaux" et de "qui garantit que la référence pointe toujours sur un objet vivant chaque fois que la méthode anonyme est exécutée".

Cela pose toujours un problème de paramètres de référence, que vous utilisiez des méthodes anonymes ou non.

Par exemple ceci:

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

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

Qui garantit que, lorsque quelqu'un appelle DoSomething, l'instance désignée par FValue existe toujours? La réponse est que vous devez le garantir vous-même en n'appelant pas DoSomething lorsque l'instance de FValue est décédée. Il en va de même pour votre édition: vous ne devez pas appeler la méthode anonyme lorsque l'instance sous-jacente est morte.

C’est l’un des domaines dans lequel les solutions de comptage de références ou de récupération d’ordures simplifient la vie: dans ce cas, l’instance sera conservée en vie jusqu’à disparition de la dernière référence (ce qui pourrait faire en sorte que l’instance vive plus longtemps que prévu initialement!). .

Ainsi, avec votre modification, votre question passe des méthodes anonymes aux implications de l'utilisation de paramètres de référence et de la gestion de la durée de vie en général.

J'espère que ma réponse vous aidera à aller dans ce domaine.

- jeroen

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top