Utiliser les fermetures pour garder une trace d’une variable: bonne idée ou sale tour?

Ok, j’ai besoin de pouvoir suivre les objects de type valeur qui sont des propriétés sur un autre object, ce qui est impossible sans que ces propriétés implémentent une interface IObservable ou similaire. Ensuite, j’ai pensé aux fermetures et au célèbre exemple de Jon Skeet et à la manière dont cela imprime 9 (ou 10) plusieurs fois et non un ordre croissant de nombres. Alors j’ai pensé pourquoi ne pas faire ça:

Class MyClass { ... Func variable; ... public void DoSomethingThatGetsCalledOften() { MyValueType blah = variable(); //error checking too not shown for brevity //use it } ... } ... in some other consuming code ... MyClass myClass = new MyClass(); myClass.variable = () => myOtherObject.MyOtherProperty; //then myClass will get the current value of the variable whenever it needs it 

Évidemment, cela nécessiterait une certaine compréhension du fonctionnement des fermetures, mais ma question est la suivante: s’agit-il d’une bonne idée ou d’une mauvaise manipulation et d’une mauvaise utilisation du système de fermeture?

Edit: Puisque certaines personnes semblent ne pas comprendre ce que je veux dire, voici un programme de console qui le démontre:

 using System; using System.Linq; namespace Test { class Program { public static void Main() { float myFloat = 5; Func test = () => myFloat; Console.WriteLine(test()); myFloat = 10; Console.WriteLine(test()); Console.Read(); } } } 

Cela affichera 5 puis 10 .

Vous êtes tombé sur le célèbre koan: Les fermetures sont l’object du pauvre. Vous utilisez l’ Action pour remplacer un getter de propriété de type T Une telle chose serait (légèrement) moins un sale tour dans un langage plus dynamic, car elle pourrait être implémentée en injectant un getter décoré avec votre fonction de journalisation, mais en C #, il n’existe pas de moyen élégant d’attaquer la propriété de quelqu’un lorsque ils ne s’y attendent pas.

En tant que mécanisme permettant d’obtenir la valeur d’une propriété, cela fonctionnera (mais il ne fournira aucun mécanisme permettant de détecter rapidement les mises à jour). Cependant, cela dépend de la manière dont vous comptez l’utiliser. Pour le faire facilement, vous devez utiliser une stack de lambdas dans le code ou DynamicMethod code DynamicMethod / Expression faire au moment de l’exécution. Dans la plupart des cas, quelque chose de plus semblable à la reflection serait plus pratique.

Je ne m’inquiéterais pas nécessairement de l’aspect “type de valeur”; dans la plupart des cas, il ne s’agit pas d’un goulot d’étranglement, malgré le FUD – et il est généralement beaucoup plus facile de gérer un tel code avec un object que via des génériques ou similaires.

J’ai dans mon IDE du code qui illustre DynamicMethod contre la reflection brute (que j’ai l’intention de bloguer un jour prochain), montrant ainsi que le code basé sur la reflection ne doit pas nécessairement être lent (ou simplement utiliser HyperDescriptor ).

L’autre option consiste à implémenter les interfaces correctes / append les événements corrects. Peut-être via PostSharp, éventuellement via des types dynamics (hériter et remplacer au moment de l’exécution), éventuellement avec du code normal.

Vous devrez saisir le membre de votre variable sous la forme Func (ou un autre delegate MyValueType ), mais vous ne pourrez pas affecter la valeur de blah de cette manière. Tout comme pour l’utilisation de la fermeture dans la boucle foreach ci-dessus, elle ne sera évaluée qu’à un moment donné . Ce n’est pas un moyen de garder la valeur de votre variable synchronisée avec l’autre object. En fait, il n’existe aucun moyen de le faire sans:

  • surveiller en permanence la valeur de la propriété de l’autre instance dans une sorte de boucle, comme un Timer
  • implémentation d’un événement de notification de changement sur la classe de l’autre instance

Vous seriez en mesure d’implémenter une propriété comme celle-ci (puisqu’une propriété est évaluée à chaque appel), mais quel est l’intérêt d’utiliser un délégué personnalisé, mis à part le fait que vous ne devez rien savoir de l’autre instance.

modifier

Je vais essayer de rendre cela un peu plus clair. En utilisant ce code que vous avez posté:

 Class MyClass { ... Action variable; ... MyValueType blah = variable(); //use it ... } ... MyClass myClass = new MyClass(); myClass.variable = () => myOtherObject.MyOtherProperty; 

Tout d’abord, pour que cette fonction soit fonctionnelle, la variable doit être Func et non Action ( Func renvoie une valeur, pas l’ Action ; puisque vous essayez d’affecter une valeur à une variable, vous avez besoin que l’expression retourne une valeur).

Deuxièmement, le principal problème de votre approche est – en supposant que je lis votre code correctement – que vous essayez d’affecter la valeur de la variable d’instance blah à la valeur évaluée de variable() dans la déclaration de classe. Cela ne fonctionnera pas pour deux raisons:

  • les affectations dans les déclarations de classe ne peuvent pas accéder aux membres d’instance (quelle variable est)
  • l’affectation d’une variable dans une déclaration de classe n’a lieu que lors de la construction de l’object. Même si la première condition était présente, vous obtiendriez simplement une NullReferenceException lors de l’instanciation de votre object, car il NullReferenceException d’évaluer une variable , qui serait null à ce moment.
  • Même en ignorant les deux premiers, la valeur de blah ne représenterait que la valeur évaluée de variable() à tout moment de son évaluation. Cela ne “pointerait” pas sur cette fonction et restrait automatiquement synchronisé, comme si vous essayiez de le faire.

Si vous ne recherchez pas une sorte de synchronisation automatique, rien ne vous empêche de simplement garder le délégué Func à évaluer; Cette approche n’a rien de particulièrement bon ou mauvais, et ce n’est une fermeture sauf si le délégué (dans votre cas, une expression lambda) implique l’utilisation d’une variable locale.