tags:

views:

498

answers:

9

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".

A: 

What about this

string prefix = "OLD:";
string _prefix=prefix;
Func<string, string> prependAction = (x => _prefix + x);
prefix = "NEW:";
Console.WriteLine(prependAction("brownie"));
Dested
This doesn't solve the serialization problem as the _prefix variable is still attached to the class that contains the code.
Brownie
A: 

How about:

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

?

Martijn
This doesn't solve the serialization problem as the prefixCopy variable is still attached to the class that contains the code.
Brownie
+1  A: 

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

kronoz
A: 

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.

Bittercoder
I think you missed the point of the question. Your delegate will fail to serialize because a closure has still been formed, just on a different variable.
Paul Batum
A: 

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);
Rasmus Faber
If that code is contained within a method non serializable class (In my case an ASP.NET codebehind) then the prependAction will still refer to that class and not serialize.
Brownie
A: 

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.

Martijn
A: 

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.

Rasmus Faber
Almost worked! Unfortunately the compiler generated a private sealed class to hold the prefix variable. The compiler generated class isn't serializable.
Brownie
+5  A: 

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.

Rasmus Faber
+1  A: 

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.

chakrit
While this does output "OLD:Brownie" a closure has still been formed and therefore the serialization still fails.
Paul Batum