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
How to Inject JavaScript in Webbrowser Control
How to Shut Down the Computer from C#
Keeping ASP.NET Session Open/Alive
How to Delete a Line from a Text File in C#
Detect If Running as Administrator with or Without Elevated Privileges
Default Visibility for C# Classes and Members (Fields, Methods, etc.)
Associating Enums with Strings in C#
Using Stored Procedure Output Parameters in C#
How Might I Schedule a C# Windows Service to Perform a Task Daily
How to Get JSON.Net to Serialize Members of a Class Deriving from List<T>
Embedding JavaScript Engine into .Net
Get the Generated SQL Statement from a SQLcommand Object
"The Controls Collection Cannot Be Modified Because the Control Contains Code Blocks"
Create an Array or List of All Dates Between Two Dates
"The Remote Certificate Is Invalid According to the Validation Procedure." Using Gmail Smtp Server
Best Practices for Exception Management in Java or C#
How to Create a Dynamic Button Click Event on a Dynamic Button