Question

I have the following code:

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

Because the compiler replaces the prefix variable with a closure "NEW:brownie" is printed to the console.

Is there an easy way to prevent the compiler from lifting the prefix variable whilst still making use of a lambda expression? I would like a way of making my Func work identically to:

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

The reason I need this is I would like to serialize the resulting delegate. If the prefix variable is in a non-serializable class the above function will not serialize.

The only way around this I can see at the moment is to create a new serializable class that stores the string as a member variable and has the string prepend method:

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

With helper class:

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

This seems like a lot of extra work to get the compiler to be "dumb".

Was it helpful?

Solution

I see the underlying problem now. It is deeper than I first thought. Basically the solution is to modify the expression tree before serializing it, by replacing all subtrees that do not depend on the parameters with constant nodes. This is apparently called "funcletization". There is an explanation of it here.

OTHER TIPS

Just make another closure...

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"));

Havn't tested it yet as I don't have access to VS at the moment, but normally, this is how I solve such problem.

Lambdas automatically 'suck' in local variables, I'm afraid that's simply how they work by definition.

This is a pretty common problem i.e. variables being modified by a closure unintentionally - a far simpler solution is just to go:

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

If you're using resharper it will actually identify the places in your code where you're at risk of causing unexpected side effects such as this - so if the file is "all green" your code should be OK.

I think in some ways it would have been nice if we had some syntactic sugar to handle this situation so we could have written it as a one-liner i.e.

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

Where some prefix operator would cause the variable's value to be evaluated prior to constructing the anonymous delegate/function.

I get the problem now: the lambda refers to the containing class which might not 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 needs not reference the containing class.

There are already several answers here explaining how you can avoid the lambda "lifting" your variable. Unfortunately that does not solve your underlying problem. Being unable to serialize the lambda has nothing to do with the lambda having "lifted" your variable. If the lambda expression needs an instance of a non-serialize class to compute, it makes perfect sense that it cannot be serialized.

Depending on what you actually are trying to do (I can't quite decide from your post), a 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);

use:

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

What about this

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

How about:

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

?

Well, if we're gonna talk about "problems" here, lambdas come from the functional programming world, and in a purely functional programming langauge, there are no assignments and so your problem would never arise because prefix's value could never change. I understand C# thinks it's cool to import ideas from functional programs (because FP is cool!) but it's very hard to make it pretty, because C# is and will always be an imperative programming language.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top