How to Tell a Lambda Function to Capture a Copy Instead of a Reference in C#

How to tell a lambda function to capture a copy instead of a reference in C#?

What the compiler is doing is pulling your lambda and any variables captured by the lambda into a compiler generated nested class.

After compilation your example looks a lot like this:

class Program
{
delegate void Action();
static void Main(string[] args)
{
List<Action> actions = new List<Action>();

DisplayClass1 displayClass1 = new DisplayClass1();
for (displayClass1.i = 0; displayClass1.i < 10; ++displayClass1.i )
actions.Add(new Action(displayClass1.Lambda));

foreach (Action a in actions)
a();
}

class DisplayClass1
{
int i;
void Lambda()
{
Console.WriteLine(i);
}
}
}

By making a copy within the for loop, the compiler generates new objects in each iteration, like so:

for (int i = 0; i < 10; ++i)
{
DisplayClass1 displayClass1 = new DisplayClass1();
displayClass1.i = i;
actions.Add(new Action(displayClass1.Lambda));
}

Confusion about value capturing in C# lambda

The point here is closure. After compilation a is not a local variable anymore - it's a field of auto-generated class, both in function scope and in lambda.

How can I capture the value of an outer variable inside a lambda expression?

This has more to do with lambdas than threading. A lambda captures the reference to a variable, not the variable's value. This means that when you try to use i in your code, its value will be whatever was stored in i last.

To avoid this, you should copy the variable's value to a local variable when the lambda starts. The problem is, starting a task has overhead and the first copy may be executed only after the loop finishes. The following code will also fail

for (var i = 0; i < 50; ++i) {
Task.Factory.StartNew(() => {
var i1=i;
Debug.Print("Error: " + i1.ToString());
});
}

As James Manning noted, you can add a variable local to the loop and copy the loop variable there. This way you are creating 50 different variables to hold the value of the loop variable, but at least you get the expected result. The problem is, you do get a lot of additional allocations.

for (var i = 0; i < 50; ++i) {
var i1=i;
Task.Factory.StartNew(() => {
Debug.Print("Error: " + i1.ToString());
});
}

The best solution is to pass the loop parameter as a state parameter:

for (var i = 0; i < 50; ++i) {
Task.Factory.StartNew(o => {
var i1=(int)o;
Debug.Print("Error: " + i1.ToString());
}, i);
}

Using a state parameter results in fewer allocations. Looking at the decompiled code:

  • the second snippet will create 50 closures and 50 delegates
  • the third snippet will create 50 boxed ints but only a single delegate

Is copying performed when capturing a value-type into a lambda?

There will be no copies. Lambdas capture variables, not values.

You can use Reflector to look at the compile code: the compiler will move the "someStruct" variable into a helper class.

private static void Foo()
{
DisplayClass locals = new DisplayClass();
locals.someStruct = new SomeStruct { Num = 5 };
action = new Action(locals.b__1);
}
private sealed class DisplayClass
{
// Fields
public SomeStruct someStruct;

// Methods
public void b__1()
{
Console.WriteLine(this.someStruct.Num);
}
}

Copying structures will never cause user-defined code to run, so you cannot really check it that way.
Actually, the code will do a copy when assigning to the "someStruct" variable. It would do that even for local variables without any lambdas.

C++ lambda capture this vs capture by reference

For the specific example you've provided, capturing by this is what you want. Conceptually, capturing this by reference doesn't make a whole lot of sense, since you can't change the value of this, you can only use it as a pointer to access members of the class or to get the address of the class instance. Inside your lambda function, if you access things which implicitly use the this pointer (e.g. you call a member function or access a member variable without explicitly using this), the compiler treats it as though you had used this anyway. You can list multiple captures too, so if you want to capture both members and local variables, you can choose independently whether to capture them by reference or by value. The following article should give you a good grounding in lambdas and captures:

https://crascit.com/2015/03/01/lambdas-for-lunch/

Also, your example uses std::function as the return type through which the lambda is passed back to the caller. Be aware that std::function isn't always as cheap as you may think, so if you are able to use a lambda directly rather than having to wrap it in a std::function, it will likely be more efficient. The following article, while not directly related to your original question, may still give you some useful material relating to lambdas and std::function (see the section An alternative way to store the function object, but the article in general may be of interest):

https://crascit.com/2015/06/03/on-leaving-scope-part-2/

Does C# Pass by Value to Lambdas?

count is an int, and ints are value types, which means they are indeed passed by value. There is no semantic difference between your first and second example.

(That said, it looks to me like it should be incrementing count, since it should be capturing the original reference as far as the closure. To clarify -- although count will be passed by value down into SomeFunction, things don't get "passed" into your lambda expression when you use them inside the expression -- they are the same reference as the external variable.)



Related Topics



Leave a reply



Submit