Prevent .NET from "Canceling" Local Variables
I have the following code:
string prefix = "OLD:"; Func<string, string> prependAction = (x => prefix + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie")); Since the compiler replaces the prefix variable with closure, "NEW: brownie" is printed to the console.
Is there an easy way to stop the compiler from removing the prefix variable while continuing to use the lambda expression? I would like my Func to work identically:
Func<string, string> prependAction = (x => "OLD:" + x); The reason I need this is to serialize the resulting delegate. If the prefix variable is in a non-serializable class, the above function will not be serialized.
The only way I see at the moment is to create a new serializable class that saves the string as a member variable and has a string prefix method:
string prefix = "NEW:"; var prepender = new Prepender {Prefix = prefix}; Func<string, string> prependAction = prepender.Prepend; prefix = "OLD:"; Console.WriteLine(prependAction("brownie")); Using the helper class:
[Serializable] public class Prepender { public string Prefix { get; set; } public string Prepend(string str) { return Prefix + str; } } It seems like a lot of work to make the compiler be "stupid".
Now I see the main problem. It is deeper than I thought. Basically, the solution is to modify the expression tree before it is serialized, replacing all subtrees that are independent of parameters with constant nodes. This is apparently called "funcletization." There is an explanation for this here .
Just do another close ...
Say something like:
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")); I have not tested it yet, since I do not have access to VS at the moment, but usually I solve this problem this way.
Lambdas automatically "suck" in local variables, I'm afraid it's just how they work by definition.
This is a fairly common problem, that is, variables that were inadvertently changed by closure - a much simpler solution - is simple:
string prefix = "OLD:"; var actionPrefix = prefix; Func<string, string> prependAction = (x => actionPrefix + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie")); If you use resharper, it actually identifies places in your code where you run the risk of unexpected side effects such as this - so if the file is green, your code should be fine.
I think that in a sense it would be nice if we had some kind of syntactic sugar to handle this situation, so we could write it as a one-line i.e.
Func<string, string> prependAction = (x => ~prefix + x); If any prefix operator causes the calculation of the value of the variable before creating an anonymous delegate / function.
There are already a few answers here explaining how you can avoid the lambda of "raising" your variable. Unfortunately, this does not solve your main problem. The inability to serialize a lambda has nothing to do with the lambda that "raised" your variable. If a lambda expression needs an instance of a non-serialized class, then it makes sense that it cannot be serialized.
Depending on what you are actually trying to do (I can't decide from your post), the solution would be to move the non-serializable part of the lambda outside.
For example, instead of:
NonSerializable nonSerializable = new NonSerializable(); Func<string, string> prependAction = (x => nonSerializable.ToString() + x); using:
NonSerializable nonSerializable = new NonSerializable(); string prefix = nonSerializable.ToString(); Func<string, string> prependAction = (x => prefix + x); Now I get the problem: lambda refers to an contained class that cannot be serializable. Then do something like this:
public void static Func<string, string> MakePrependAction(String prefix){ return (x => prefix + x); } (Note the static keyword.) Then the lambda should not refer to the containing class.
How about this
string prefix = "OLD:"; string _prefix=prefix; Func<string, string> prependAction = (x => _prefix + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie")); What about:
string prefix = "OLD:"; string prefixCopy = prefix; Func<string, string> prependAction = (x => prefixCopy + x); prefix = "NEW:"; Console.WriteLine(prependAction("brownie")); ?
Well, if we talk about the “problems” here, lambdas come from the world of functional programming, and in purely functional langauge programming there are no assignments, and therefore your problem will never arise because the prefix value will never change. I understand that C # considers It’s great to import ideas from functional programs (because FP is cool!), but it’s very difficult to do it beautifully, because C # will always and always be an imperative programming language.