Question

J'ai le code suivant:

string prefix = "OLD:";
Func<string, string> prependAction = (x => prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Parce que le compilateur remplace la variable préfixe par une fermeture "NEW: brownie". est imprimé sur la console.

Existe-t-il un moyen simple d'empêcher le compilateur de lever la variable préfixe tout en utilisant une expression lambda? J'aimerais que mon Func fonctionne de manière identique à:

Func<string, string> prependAction = (x => "OLD:" + x);

Si j’ai besoin de cela, je voudrais sérialiser le délégué résultant. Si la variable préfixe est dans une classe non sérialisable, la fonction ci-dessus ne sera pas sérialisée.

Pour le moment, la seule solution à ce problème est de créer une nouvelle classe sérialisable qui stocke la chaîne en tant que variable membre et comporte la méthode string prepend:

string prefix = "NEW:";
var prepender = new Prepender {Prefix = prefix};
Func<string, string> prependAction = prepender.Prepend;
prefix = "OLD:";
Console.WriteLine(prependAction("brownie"));

Avec la classe d'assistance:

[Serializable]
public class Prepender
{
    public string Prefix { get; set; }
    public string Prepend(string str)
    {
        return Prefix + str;
    }
}

Cela semble être beaucoup de travail supplémentaire pour que le compilateur soit "stupide".

Était-ce utile?

La solution

Je vois le problème sous-jacent maintenant. C'est plus profond que ce que j'avais d'abord pensé. La solution consiste essentiellement à modifier l'arbre d'expression avant de le sérialiser, en remplaçant tous les sous-arbres qui ne dépendent pas des paramètres par des noeuds constants. Ceci est apparemment appelé "funcletization". Il existe une explication à ce sujet ici .

Autres conseils

Faites juste une autre fermeture ...

Dites, quelque chose comme:

var prepend = "OLD:";

Func<string, Func<string, string>> makePrepender = x => y => (x + y);
Func<string, string> oldPrepend = makePrepender(prepend);

prepend = "NEW:";

Console.WriteLine(oldPrepend("Brownie"));

Je ne l'ai pas encore testé car je n'ai pas accès à VS pour le moment, mais normalement, c'est comme ça que je résous un tel problème.

Lambdas "aspire" automatiquement les variables locales, je crains que ce ne soit simplement comme cela qu'elles fonctionnent par définition.

C’est un problème assez courant, c’est-à-dire que les variables sont modifiées par une fermeture de manière non intentionnelle - une solution beaucoup plus simple consiste simplement à aller:

string prefix = "OLD:";
var actionPrefix = prefix;
Func<string, string> prependAction = (x => actionPrefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Si vous utilisez resharper, il identifiera les endroits de votre code où vous risquez de provoquer des effets secondaires inattendus, comme celui-ci. Par conséquent, si le fichier est "tout en vert". votre code devrait être OK.

Je pense qu'à certains égards, il aurait été bien si nous avions un sucre syntaxique pour gérer cette situation afin que nous puissions l'écrire comme un one-liner, c'est-à-dire.

Func<string, string> prependAction = (x => ~prefix + x);

Où un opérateur de préfixe voudrait que la valeur de la variable soit évaluée avant de construire le délégué / la fonction anonyme.

Je comprends maintenant le problème: le lambda fait référence à la classe contenante qui pourrait ne pas être sérialisable. Ensuite, faites quelque chose comme ça:

public void static Func<string, string> MakePrependAction(String prefix){
    return (x => prefix + x);
}

(Notez le mot-clé static.) Ensuite, le lambda n'a pas besoin de faire référence à la classe qui le contient.

Il existe déjà plusieurs réponses ici expliquant comment vous pouvez éviter le lambda " levage " votre variable. Malheureusement, cela ne résout pas votre problème sous-jacent. L’impossibilité de sérialiser le lambda n’a rien à voir avec le lambda ayant été "levé". votre variable. Si l'expression lambda nécessite une instance d'une classe non sérialisée pour être calculée, cela signifie parfaitement qu'elle ne peut pas être sérialisée.

En fonction de ce que vous essayez réellement de faire (je ne peux pas vraiment décider à partir de votre message), une solution consisterait à déplacer la partie non sérialisable du lambda à l'extérieur.

Par exemple, au lieu de:

NonSerializable nonSerializable = new NonSerializable();
Func<string, string> prependAction = (x => nonSerializable.ToString() + x);

utiliser:

NonSerializable nonSerializable = new NonSerializable();
string prefix = nonSerializable.ToString();
Func<string, string> prependAction = (x => prefix + x);

Qu'en est-il de cette

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

Que diriez-vous de:

string prefix = "OLD:";
string prefixCopy = prefix;
Func<string, string> prependAction = (x => prefixCopy + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));

?

Eh bien, si nous allons parler de "problèmes", ici, les lambdas proviennent du monde de la programmation fonctionnelle et, dans un langage purement fonctionnel, il n’existe aucune affectation et votre problème ne se poserait donc jamais car la valeur du préfixe ne pourrait jamais changer. Je comprends que C # pense que c’est cool d’importer des idées de programmes fonctionnels (parce que FP est cool!), Mais il est très difficile de le rendre joli, parce que C # est et sera toujours un langage de programmation impératif.

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