SlideShare a Scribd company logo
1 of 13
Download to read offline
How to capture a variable in C# and not to
shoot yourself in the foot
Author: Ivan Kishchenko
Date: 27.01.2017
Back in 2005, with the release of C# 2.0 standard we got a possibility to pass a variable to the body of an
anonymous delegate by capturing it from the current context. In 2008 the C# 3.0 brought us lambdas,
user anonymous classes, LINQ requests and much more. Now it January, 2017 and the majority of C#
developers are looking forward to the release of the C# 7.0 standard that should provide us a bunch of
new useful features. However, there are still old features that need to be fixed. That's why there are
plenty of ways to shoot yourself in the foot. Today we are going to speak about one of them, and it is
related with quite an unobvious mechanism of variable capture in the body of anonymous functions in
C#.
Introduction
As I have stated above, we are going to discuss peculiarities of the mechanism of variable capture in the
body of anonymous functions in C#. I should warn in advance, that the article will contain a large
number of technical details, but I hope that both experienced and beginner programmers will find my
article interesting and simple to comprehend.
But enough talking. I'll give you simple example of the code, you should tell, what will be printed in the
console.
So, here we go.
void Foo()
{
var actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
actions.Add(() => Console.WriteLine(i));
}
foreach(var a in actions)
{
a();
}
}
And now attention please, here is the answer. The console will print the number 10 ten times.
10
10
10
10
10
10
10
10
10
10
This article is for those who thought otherwise. Let's try to sort out, what are the reasons of such
behavior.
Why does it happen so?
Upon the declaration of an anonymous function (it can be an anonymous delegate or lambda) inside
your class, one more container class will be declared during the compilation, which contains fields for all
the captured variables and a method, containing a body of the anonymous function. The disassembled
structure of the program for the code fragment given above will be as follows:
In this case the Foo method in this fragment is declared inside the Program class. The compiler
generated a container class c__DisplayClass1_0 for the lambda () => Console.WriteLine(i), and inside of
the class-container it generated a field i, having a captured variable with the same name and the
method b__0, containing the body of the lambda.
Let's consider the disassembled IL code of the b__0 method (lambda body) with my comments:
.method assembly hidebysig instance void '<Foo>b__0'() cil managed
{
.maxstack 8
// Puts the current class item (equivalent to 'this')
// to the top of the stack.
// It is necessary for the access to
// the fields of the current class.
IL_0000: ldarg.0
// Puts the value of the 'i' field to the top of the stack
// of the current class instance
IL_0001: ldfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::i
// Calls a method to output the string to the console.
// Passes values from the stack as arguments.
IL_0006: call void [mscorlib]System.Console::WriteLine(int32)
// Exits the method.
IL_000b: ret
}
All correct, that's exactly what we do inside lambda, no magic. Let's go on.
As we know, the int type (the full name is Int32) is a structure, which means that it passed by value, not
by reference.
The value of the i variable should be copied (according the logic) during the creation of the container
class instance. And if you answered my question in the beginning of the article incorrectly, then most
likely you expected that the container would be created right before the declaration of the lambda in
the code.
In reality, the i variable won't be created after the compilation in the Foo method at all. Instead of it, an
instance of the container class c__DisplayClass1_0 will get created, and its field will be initialized with 0
instead of the i variable. Moreover, in all the fragments where we used a local variable i, there will be a
field of a container class used.
The important point is that an instance of the container class is created before the loop, because its field
i will be used in the loop as an iterator.
As a result, we get one instance of the container class for all iterations of the for loop. Adding a new
lambda to the actions list upon every iteration, we actually add the same reference to the instance of
the container class created previously. As a result, when we traverse all the items of the actions list with
the foreach loop, they all have the same instance of the container class. And we take into account that
the for loop increments the value of an iterator after every iteration (even after the last one), then the
value of the i field inside the container class after the exit from the loop gets equal to 10 after executing
the for loop.
You can make sure of it by looking at the disassembled IL code of the Foo method (with my comments):
.method private hidebysig instance void Foo() cil managed
{
.maxstack 3
// -========== DECLARATION OF LOCAL VARIABLES ==========-
.locals init(
// A list of 'actions'.
[0] class [mscorlib]System.Collections.Generic.List'1
<class [mscorlib]System.Action> actions,
// A container class for the lambda.
[1] class TestSolution.Program/
'<>c__DisplayClass1_0' 'CS$<>8__locals0',
// A technical variable V_2 is necessary for temporary
// storing the results of the addition operation.
[2] int32 V_2,
// Technical variable V_3 is necessary for storing
// the enumerator of the 'actions' list during
// the iteration of the 'foreach' loop.
[3] valuetype
[mscorlib]System.Collections.Generic.List'1/Enumerator<class
[mscorlib]System.Action> V_3)
// -================= INITIALIZATION =================-
// An instance of the Actions list is created and assigned to the
// 'actions' variable.
IL_0000: newobj instance void class
[mscorlib]System.Collections.Generic.List'1<class
[mscorlib]System.Action>::.ctor()
IL_0005: stloc.0
// An instance of the container class is created
// and assigned to a corresponding local variable
IL_0006: newobj instance void
TestSolution.Program/'<>c__DisplayClass1_0'::.ctor()
IL_000b: stloc.1
// A reference of the container class is loaded to the stack.
IL_000c: ldloc.1
// Number 0 is loaded to the stack.
IL_000d: ldc.i4.0
// 0 is assigned to the 'i' field of the previous
// object on the stack (an instance of a container class).
IL_000e: stfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::i
// -================= THE FOR LOOP =================-
// Jumps to the command IL_0037.
IL_0013: br.s IL_0037
// The references of the 'actions'
// list and an instance of the container class
// are loaded to the stack.
IL_0015: ldloc.0
IL_0016: ldloc.1
// The reference to the 'Foo' method of the container class
// is loaded to the stack.
IL_0017: ldftn instance void
TestSolution.Program/'<>c__DisplayClass1_0'::'<Foo>b__0'()
// An instance of the 'Action' class is created and the reference
// to the 'Foo' method of the container class is passed into it.
IL_001d: newobj instance void
[mscorlib]System.Action::.ctor(object, native int)
// The method 'Add' is called for the 'actions' list
// by adding an instance of the 'Action' class.
IL_0022: callvirt instance void class
[mscorlib]System.Collections.Generic.List'1<class
[mscorlib]System.Action>::Add(!0)
// The value of the 'i' field of the instance of a container class
// is loaded to the stack.
IL_0027: ldloc.1
IL_0028: ldfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::i
// The value of the 'i' field is assigned
// to the technical variable 'V_2'.
IL_002d: stloc.2
// The reference to the instance of a container class and the value
// of a technical variable 'V_2' is loaded to the stack.
IL_002e: ldloc.1
IL_002f: ldloc.2
// 1 is loaded to the stack.
IL_0030: ldc.i4.1
// It adds two first values on the stack
// and assigns them to the third.
IL_0031: add
// The result of the addition is assigned to the 'i' field
// (in fact, it is an increment)
IL_0032: stfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::i
// The value of the 'i' field of the container class instance
// is loaded to the stack.
IL_0037: ldloc.1
IL_0038: ldfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::i
// 10 is loaded to the stack.
IL_003d: ldc.i4.s 10
// If the value of the 'i' field is less than 10,
// it jumps to the command IL_0015.
IL_003f: blt.s IL_0015
// -================= THE FOREACH LOOP =================-
//// The reference to the 'actions' list is loaded to the stack.
IL_0041: ldloc.0
// The technical variable V_3 is assigned with the result
// of the 'GetEnumerator' method of the 'actions' list.
IL_0042: callvirt instance valuetype
[mscorlib]System.Collections.Generic.List'1/Enumerator<!0> class
[mscorlib]System.Collections.Generic.List'1<class
[mscorlib]System.Action>::GetEnumerator()
IL_0047: stloc.3
// The initialization of the try block
// (the foreach loop is converted to
// the try-finally construct)
.try
{
// Jumps to the command IL_0056.
IL_0048: br.s IL_0056
// Calls get_Current method of the V_3 variable.
// The result is written to the stack.
// (A reference to the Action object in the current iteration).
IL_004a: ldloca.s V_3
IL_004c: call instance !0 valuetype
[mscorlib]System.Collections.Generic.List'1/Enumerator<class
[mscorlib]System.Action>::get_Current()
// Calls the Invoke method of the Action
// object in the current iteration
IL_0051: callvirt instance void
[mscorlib]System.Action::Invoke()
// Calls MoveNext method of the V_3 variable.
// The result is written to the stack.
IL_0056: ldloca.s V_3
IL_0058: call instance bool valuetype
[mscorlib]System.Collections.Generic.List'1/Enumerator<class
[mscorlib]System.Action>::MoveNext()
// If the result of the MoveNext method is not null,
// then it jumps to the IL_004a command.
IL_005d: brtrue.s IL_004a
// Finishes the try block execution and jumps to finally.
IL_005f: leave.s IL_006f
} // end .try
finally
{
// Calls the Dispose method of the V_3 variable.
IL_0061: ldloca.s V_3
IL_0063: constrained. Valuetype
[mscorlib]System.Collections.Generic.List'1/Enumerator<class
[mscorlib]System.Action>
IL_0069: callvirt instance void
[mscorlib]System.IDisposable::Dispose()
// Finishes the execution of the finally block.
IL_006e: endfinally
}
// Finishes the execution of the current method.
IL_006f: ret
}
Conclusion
The guys from Microsoft say that this is a feature, not a bug and that this behavior was made
intentionally, aiming to increase the performance of the programs. You will find more information by
this link. In reality it results in bugs and confusion of novice developers.
An interesting fact is that the foreach loop had the same behavior before the C# 5.0 standard. The
Microsoft was bombarded with complaints about nonintuitive behavior in the bug-tracker, but with the
release of the C# 5.0 standard this behavior was changed by declaring the iterator variable inside every
loop iteration, not before it on the compilation stage, but for all other constructions similar behavior
remained without any changes. More information can be found by the link in the Breaking Changes
section.
You may ask how to avoid such an error? Actually the answer is very simple. You need to keep track of
where and what variables you capture. Just remember that the container class will be created in that
place where you have declared your variable that you will capture. If the capture occurs in the body of
the loop, and the variable is declared outside it, then it's necessary to reassign it inside the body of the
loop to a new local variable. The correct version of the example given in the beginning can be as follows:
void Foo()
{
var actions = new List<Action>();
for (int i = 0; i < 10; i++)
{
var index = i; // <=
actions.Add(() => Console.WriteLine(index));
}
foreach(var a in actions)
{
a();
}
}
If you execute this code, the console will show the numbers from 0 to 9, as expected:
0
1
2
3
4
5
6
7
8
9
Looking at the IL code of the for loop from this example, we'll see that an instance of the container class
will be created upon every iteration of the loop. Thus, the actions list will contain references to various
instances with correct values of the iterators.
// -================= THE FOR LOOP =================-
// Jumps to the command IL_002d.
IL_0008: br.s IL_002d
// Creates an instance of a container class
// and loads the reference to the stack.
IL_000a: newobj instance void
TestSolution.Program/'<>c__DisplayClass1_0'::.ctor()
IL_000f: stloc.2
IL_0010: ldloc.2
// Assigns the 'index' field in the container class
// with a value 'i'.
IL_0011: ldloc.1
IL_0012: stfld int32
TestSolution.Program/'<>c__DisplayClass1_0'::index
// Creates an instance of the 'Action' class with a reference to
// the method of a container class and add it to the 'actions' list.
IL_0017: ldloc.0
IL_0018: ldloc.2
IL_0019: ldftn instance void
TestSolution.Program/'<>c__DisplayClass1_0'::'<Foo>b__0'()
IL_001f: newobj instance void
[mscorlib]System.Action::.ctor(object, native int)
IL_0024: callvirt instance void class
[mscorlib]System.Collections.Generic.List'1<class
[mscorlib]System.Action>::Add(!0)
// Performs the increment to the 'i' variable
IL_0029: ldloc.1
IL_002a: ldc.i4.1
IL_002b: add
IL_002c: stloc.1
// Loads the value of the 'i' variable to the stack
// This time it is not in the container class
IL_002d: ldloc.1
// Compares the value of the variable 'i' with 10.
// If 'i < 10', then jumps to the command IL_000a.
IL_002e: ldc.i4.s 10
IL_0030: blt.s IL_000a
Finally, let me remind you that we are all human beings and we all make errors, that's why it would be
illogical, and as a rule long and resource-intensive to hope only for the human factor when searching for
bugs and typos. So, it's always a good idea to use technical solutions to detect errors in the code. The
machine doesn't get tired and does the work much quicker.
Quite recently, we as a team of PVS-Studio static code analyzer developers have created a diagnostic
rule that is aimed at detecting incorrect capture of the variables and anonymous functions inside the
loops. In my turn I suggest checking your code with our analyzer and see if it can detect bugs in your
code.
At this point, I'm finishing my article, I wish you clean code bugless programs.

More Related Content

What's hot

Understanding the Common Intermediate Language (CIL)
Understanding the Common Intermediate Language (CIL)Understanding the Common Intermediate Language (CIL)
Understanding the Common Intermediate Language (CIL)James Crowley
 
Why Spring <3 Kotlin
Why Spring <3 KotlinWhy Spring <3 Kotlin
Why Spring <3 KotlinVMware Tanzu
 
C# Variables and Operators
C# Variables and OperatorsC# Variables and Operators
C# Variables and OperatorsSunil OS
 
An introduction to JVM performance
An introduction to JVM performanceAn introduction to JVM performance
An introduction to JVM performanceRafael Winterhalter
 
Improving Java performance at JBCNConf 2015
Improving Java performance at JBCNConf 2015Improving Java performance at JBCNConf 2015
Improving Java performance at JBCNConf 2015Raimon Ràfols
 
Improving Android Performance at Droidcon UK 2014
Improving Android Performance at Droidcon UK 2014Improving Android Performance at Droidcon UK 2014
Improving Android Performance at Droidcon UK 2014Raimon Ràfols
 
Documenting Bugs in Doxygen
Documenting Bugs in DoxygenDocumenting Bugs in Doxygen
Documenting Bugs in DoxygenPVS-Studio
 
Java Tutorial | My Heart
Java Tutorial | My HeartJava Tutorial | My Heart
Java Tutorial | My HeartBui Kiet
 
Understanding Java byte code and the class file format
Understanding Java byte code and the class file formatUnderstanding Java byte code and the class file format
Understanding Java byte code and the class file formatRafael Winterhalter
 
12 Exceptions handling
12 Exceptions handling12 Exceptions handling
12 Exceptions handlingmaznabili
 
Synapseindia reviews.odp.
Synapseindia reviews.odp.Synapseindia reviews.odp.
Synapseindia reviews.odp.Tarunsingh198
 
Checking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-xChecking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-xAndrey Karpov
 
Analysis of bugs in Orchard CMS
Analysis of bugs in Orchard CMSAnalysis of bugs in Orchard CMS
Analysis of bugs in Orchard CMSPVS-Studio
 

What's hot (19)

c++ lab manual
c++ lab manualc++ lab manual
c++ lab manual
 
Understanding the Common Intermediate Language (CIL)
Understanding the Common Intermediate Language (CIL)Understanding the Common Intermediate Language (CIL)
Understanding the Common Intermediate Language (CIL)
 
Why Spring <3 Kotlin
Why Spring <3 KotlinWhy Spring <3 Kotlin
Why Spring <3 Kotlin
 
Lecture 1
Lecture 1Lecture 1
Lecture 1
 
C# Variables and Operators
C# Variables and OperatorsC# Variables and Operators
C# Variables and Operators
 
An introduction to JVM performance
An introduction to JVM performanceAn introduction to JVM performance
An introduction to JVM performance
 
Improving Java performance at JBCNConf 2015
Improving Java performance at JBCNConf 2015Improving Java performance at JBCNConf 2015
Improving Java performance at JBCNConf 2015
 
Improving Android Performance at Droidcon UK 2014
Improving Android Performance at Droidcon UK 2014Improving Android Performance at Droidcon UK 2014
Improving Android Performance at Droidcon UK 2014
 
Oop Extract
Oop ExtractOop Extract
Oop Extract
 
Documenting Bugs in Doxygen
Documenting Bugs in DoxygenDocumenting Bugs in Doxygen
Documenting Bugs in Doxygen
 
Java Tutorial | My Heart
Java Tutorial | My HeartJava Tutorial | My Heart
Java Tutorial | My Heart
 
Understanding Java byte code and the class file format
Understanding Java byte code and the class file formatUnderstanding Java byte code and the class file format
Understanding Java byte code and the class file format
 
Overview Of Msil
Overview Of MsilOverview Of Msil
Overview Of Msil
 
12 Exceptions handling
12 Exceptions handling12 Exceptions handling
12 Exceptions handling
 
Log4 J
Log4 JLog4 J
Log4 J
 
Synapseindia reviews.odp.
Synapseindia reviews.odp.Synapseindia reviews.odp.
Synapseindia reviews.odp.
 
Checking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-xChecking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-x
 
itretion.docx
itretion.docxitretion.docx
itretion.docx
 
Analysis of bugs in Orchard CMS
Analysis of bugs in Orchard CMSAnalysis of bugs in Orchard CMS
Analysis of bugs in Orchard CMS
 

Viewers also liked

Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?PVS-Studio
 
1st European Scholar TimeBanking Association Logo Contest
1st European Scholar TimeBanking Association Logo Contest1st European Scholar TimeBanking Association Logo Contest
1st European Scholar TimeBanking Association Logo Contestjosepbofilllamiquela
 
Esperanto, Loglan and Dothraki: Why do people construct new languages?
Esperanto, Loglan and Dothraki: Why do people construct new languages?Esperanto, Loglan and Dothraki: Why do people construct new languages?
Esperanto, Loglan and Dothraki: Why do people construct new languages?Melanie JI Mueller
 
Bezpieczenstwo w czasie_ferii(5)
Bezpieczenstwo w czasie_ferii(5)Bezpieczenstwo w czasie_ferii(5)
Bezpieczenstwo w czasie_ferii(5)dorotax
 
Why, How, and WIIFM - Sensible Social Media
Why, How, and WIIFM - Sensible Social MediaWhy, How, and WIIFM - Sensible Social Media
Why, How, and WIIFM - Sensible Social MediaSheila Scarborough
 
Ольга Нерода: Тренды в дизайне Email-рассылок
Ольга Нерода: Тренды в дизайне Email-рассылокОльга Нерода: Тренды в дизайне Email-рассылок
Ольга Нерода: Тренды в дизайне Email-рассылокAlexander Rys
 

Viewers also liked (9)

Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?Rechecking SharpDevelop: Any New Bugs?
Rechecking SharpDevelop: Any New Bugs?
 
1st European Scholar TimeBanking Association Logo Contest
1st European Scholar TimeBanking Association Logo Contest1st European Scholar TimeBanking Association Logo Contest
1st European Scholar TimeBanking Association Logo Contest
 
Esperanto, Loglan and Dothraki: Why do people construct new languages?
Esperanto, Loglan and Dothraki: Why do people construct new languages?Esperanto, Loglan and Dothraki: Why do people construct new languages?
Esperanto, Loglan and Dothraki: Why do people construct new languages?
 
Nami ppt eng v3.3.1
Nami ppt eng v3.3.1Nami ppt eng v3.3.1
Nami ppt eng v3.3.1
 
Bezpieczenstwo w czasie_ferii(5)
Bezpieczenstwo w czasie_ferii(5)Bezpieczenstwo w czasie_ferii(5)
Bezpieczenstwo w czasie_ferii(5)
 
Why, How, and WIIFM - Sensible Social Media
Why, How, and WIIFM - Sensible Social MediaWhy, How, and WIIFM - Sensible Social Media
Why, How, and WIIFM - Sensible Social Media
 
Metaprogramming with javascript
Metaprogramming with javascriptMetaprogramming with javascript
Metaprogramming with javascript
 
Ольга Нерода: Тренды в дизайне Email-рассылок
Ольга Нерода: Тренды в дизайне Email-рассылокОльга Нерода: Тренды в дизайне Email-рассылок
Ольга Нерода: Тренды в дизайне Email-рассылок
 
Android
AndroidAndroid
Android
 

Similar to How to capture a variable in C# and not to shoot yourself in the foot

C++ Interview Question And Answer
C++ Interview Question And AnswerC++ Interview Question And Answer
C++ Interview Question And AnswerJagan Mohan Bishoyi
 
C++ questions And Answer
C++ questions And AnswerC++ questions And Answer
C++ questions And Answerlavparmar007
 
1183 c-interview-questions-and-answers
1183 c-interview-questions-and-answers1183 c-interview-questions-and-answers
1183 c-interview-questions-and-answersAkash Gawali
 
Introduction to object oriented programming concepts
Introduction to object oriented programming conceptsIntroduction to object oriented programming concepts
Introduction to object oriented programming conceptsGanesh Karthik
 
C questions
C questionsC questions
C questionsparm112
 
Introduction to c_plus_plus
Introduction to c_plus_plusIntroduction to c_plus_plus
Introduction to c_plus_plusSayed Ahmed
 
Introduction to c_plus_plus (6)
Introduction to c_plus_plus (6)Introduction to c_plus_plus (6)
Introduction to c_plus_plus (6)Sayed Ahmed
 
Object-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modulesObject-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modulesDurgesh Singh
 
Errors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 librariesErrors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 librariesPVS-Studio
 
The Ring programming language version 1.2 book - Part 5 of 84
The Ring programming language version 1.2 book - Part 5 of 84The Ring programming language version 1.2 book - Part 5 of 84
The Ring programming language version 1.2 book - Part 5 of 84Mahmoud Samir Fayed
 
cbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxcbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxSRamadossbiher
 
cbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxcbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxSRamadossbiher
 
1669958779195.pdf
1669958779195.pdf1669958779195.pdf
1669958779195.pdfvenud11
 
Still Comparing "this" Pointer to Null?
Still Comparing "this" Pointer to Null?Still Comparing "this" Pointer to Null?
Still Comparing "this" Pointer to Null?Andrey Karpov
 
.NET Portfolio
.NET Portfolio.NET Portfolio
.NET Portfoliomwillmer
 

Similar to How to capture a variable in C# and not to shoot yourself in the foot (20)

C++ Interview Question And Answer
C++ Interview Question And AnswerC++ Interview Question And Answer
C++ Interview Question And Answer
 
C++ questions And Answer
C++ questions And AnswerC++ questions And Answer
C++ questions And Answer
 
1183 c-interview-questions-and-answers
1183 c-interview-questions-and-answers1183 c-interview-questions-and-answers
1183 c-interview-questions-and-answers
 
Introduction to object oriented programming concepts
Introduction to object oriented programming conceptsIntroduction to object oriented programming concepts
Introduction to object oriented programming concepts
 
.NET for hackers
.NET for hackers.NET for hackers
.NET for hackers
 
C questions
C questionsC questions
C questions
 
C# Unit 2 notes
C# Unit 2 notesC# Unit 2 notes
C# Unit 2 notes
 
Introduction to c_plus_plus
Introduction to c_plus_plusIntroduction to c_plus_plus
Introduction to c_plus_plus
 
Introduction to c_plus_plus (6)
Introduction to c_plus_plus (6)Introduction to c_plus_plus (6)
Introduction to c_plus_plus (6)
 
Object-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modulesObject-oriented programming (OOP) with Complete understanding modules
Object-oriented programming (OOP) with Complete understanding modules
 
Notes on c++
Notes on c++Notes on c++
Notes on c++
 
Errors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 librariesErrors detected in the Visual C++ 2012 libraries
Errors detected in the Visual C++ 2012 libraries
 
The Ring programming language version 1.2 book - Part 5 of 84
The Ring programming language version 1.2 book - Part 5 of 84The Ring programming language version 1.2 book - Part 5 of 84
The Ring programming language version 1.2 book - Part 5 of 84
 
cbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxcbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptx
 
cbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptxcbybalaguruswami-e-180803051831.pptx
cbybalaguruswami-e-180803051831.pptx
 
1669958779195.pdf
1669958779195.pdf1669958779195.pdf
1669958779195.pdf
 
Still Comparing "this" Pointer to Null?
Still Comparing "this" Pointer to Null?Still Comparing "this" Pointer to Null?
Still Comparing "this" Pointer to Null?
 
.NET Portfolio
.NET Portfolio.NET Portfolio
.NET Portfolio
 
C++ Interview Questions
C++ Interview QuestionsC++ Interview Questions
C++ Interview Questions
 
C by balaguruswami - e.balagurusamy
C   by balaguruswami - e.balagurusamyC   by balaguruswami - e.balagurusamy
C by balaguruswami - e.balagurusamy
 

Recently uploaded

The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️Delhi Call girls
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerThousandEyes
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Steffen Staab
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...Health
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...panagenda
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...OnePlan Solutions
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsAndolasoft Inc
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 

Recently uploaded (20)

The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
Shapes for Sharing between Graph Data Spaces - and Epistemic Querying of RDF-...
 
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
+971565801893>>SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHAB...
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 

How to capture a variable in C# and not to shoot yourself in the foot

  • 1. How to capture a variable in C# and not to shoot yourself in the foot Author: Ivan Kishchenko Date: 27.01.2017 Back in 2005, with the release of C# 2.0 standard we got a possibility to pass a variable to the body of an anonymous delegate by capturing it from the current context. In 2008 the C# 3.0 brought us lambdas, user anonymous classes, LINQ requests and much more. Now it January, 2017 and the majority of C# developers are looking forward to the release of the C# 7.0 standard that should provide us a bunch of new useful features. However, there are still old features that need to be fixed. That's why there are plenty of ways to shoot yourself in the foot. Today we are going to speak about one of them, and it is related with quite an unobvious mechanism of variable capture in the body of anonymous functions in C#. Introduction As I have stated above, we are going to discuss peculiarities of the mechanism of variable capture in the body of anonymous functions in C#. I should warn in advance, that the article will contain a large number of technical details, but I hope that both experienced and beginner programmers will find my article interesting and simple to comprehend. But enough talking. I'll give you simple example of the code, you should tell, what will be printed in the console. So, here we go. void Foo() { var actions = new List<Action>(); for (int i = 0; i < 10; i++) { actions.Add(() => Console.WriteLine(i)); }
  • 2. foreach(var a in actions) { a(); } } And now attention please, here is the answer. The console will print the number 10 ten times. 10 10 10 10 10 10 10 10 10 10 This article is for those who thought otherwise. Let's try to sort out, what are the reasons of such behavior. Why does it happen so? Upon the declaration of an anonymous function (it can be an anonymous delegate or lambda) inside your class, one more container class will be declared during the compilation, which contains fields for all the captured variables and a method, containing a body of the anonymous function. The disassembled structure of the program for the code fragment given above will be as follows: In this case the Foo method in this fragment is declared inside the Program class. The compiler generated a container class c__DisplayClass1_0 for the lambda () => Console.WriteLine(i), and inside of the class-container it generated a field i, having a captured variable with the same name and the method b__0, containing the body of the lambda. Let's consider the disassembled IL code of the b__0 method (lambda body) with my comments:
  • 3. .method assembly hidebysig instance void '<Foo>b__0'() cil managed { .maxstack 8 // Puts the current class item (equivalent to 'this') // to the top of the stack. // It is necessary for the access to // the fields of the current class. IL_0000: ldarg.0 // Puts the value of the 'i' field to the top of the stack // of the current class instance IL_0001: ldfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::i // Calls a method to output the string to the console. // Passes values from the stack as arguments. IL_0006: call void [mscorlib]System.Console::WriteLine(int32) // Exits the method. IL_000b: ret } All correct, that's exactly what we do inside lambda, no magic. Let's go on. As we know, the int type (the full name is Int32) is a structure, which means that it passed by value, not by reference. The value of the i variable should be copied (according the logic) during the creation of the container class instance. And if you answered my question in the beginning of the article incorrectly, then most likely you expected that the container would be created right before the declaration of the lambda in the code. In reality, the i variable won't be created after the compilation in the Foo method at all. Instead of it, an instance of the container class c__DisplayClass1_0 will get created, and its field will be initialized with 0 instead of the i variable. Moreover, in all the fragments where we used a local variable i, there will be a field of a container class used.
  • 4. The important point is that an instance of the container class is created before the loop, because its field i will be used in the loop as an iterator. As a result, we get one instance of the container class for all iterations of the for loop. Adding a new lambda to the actions list upon every iteration, we actually add the same reference to the instance of the container class created previously. As a result, when we traverse all the items of the actions list with the foreach loop, they all have the same instance of the container class. And we take into account that the for loop increments the value of an iterator after every iteration (even after the last one), then the value of the i field inside the container class after the exit from the loop gets equal to 10 after executing the for loop. You can make sure of it by looking at the disassembled IL code of the Foo method (with my comments): .method private hidebysig instance void Foo() cil managed { .maxstack 3 // -========== DECLARATION OF LOCAL VARIABLES ==========- .locals init( // A list of 'actions'. [0] class [mscorlib]System.Collections.Generic.List'1 <class [mscorlib]System.Action> actions, // A container class for the lambda. [1] class TestSolution.Program/ '<>c__DisplayClass1_0' 'CS$<>8__locals0', // A technical variable V_2 is necessary for temporary // storing the results of the addition operation. [2] int32 V_2, // Technical variable V_3 is necessary for storing // the enumerator of the 'actions' list during // the iteration of the 'foreach' loop. [3] valuetype [mscorlib]System.Collections.Generic.List'1/Enumerator<class
  • 5. [mscorlib]System.Action> V_3) // -================= INITIALIZATION =================- // An instance of the Actions list is created and assigned to the // 'actions' variable. IL_0000: newobj instance void class [mscorlib]System.Collections.Generic.List'1<class [mscorlib]System.Action>::.ctor() IL_0005: stloc.0 // An instance of the container class is created // and assigned to a corresponding local variable IL_0006: newobj instance void TestSolution.Program/'<>c__DisplayClass1_0'::.ctor() IL_000b: stloc.1 // A reference of the container class is loaded to the stack. IL_000c: ldloc.1 // Number 0 is loaded to the stack. IL_000d: ldc.i4.0 // 0 is assigned to the 'i' field of the previous // object on the stack (an instance of a container class). IL_000e: stfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::i
  • 6. // -================= THE FOR LOOP =================- // Jumps to the command IL_0037. IL_0013: br.s IL_0037 // The references of the 'actions' // list and an instance of the container class // are loaded to the stack. IL_0015: ldloc.0 IL_0016: ldloc.1 // The reference to the 'Foo' method of the container class // is loaded to the stack. IL_0017: ldftn instance void TestSolution.Program/'<>c__DisplayClass1_0'::'<Foo>b__0'() // An instance of the 'Action' class is created and the reference // to the 'Foo' method of the container class is passed into it. IL_001d: newobj instance void [mscorlib]System.Action::.ctor(object, native int) // The method 'Add' is called for the 'actions' list // by adding an instance of the 'Action' class. IL_0022: callvirt instance void class [mscorlib]System.Collections.Generic.List'1<class [mscorlib]System.Action>::Add(!0) // The value of the 'i' field of the instance of a container class // is loaded to the stack. IL_0027: ldloc.1
  • 7. IL_0028: ldfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::i // The value of the 'i' field is assigned // to the technical variable 'V_2'. IL_002d: stloc.2 // The reference to the instance of a container class and the value // of a technical variable 'V_2' is loaded to the stack. IL_002e: ldloc.1 IL_002f: ldloc.2 // 1 is loaded to the stack. IL_0030: ldc.i4.1 // It adds two first values on the stack // and assigns them to the third. IL_0031: add // The result of the addition is assigned to the 'i' field // (in fact, it is an increment) IL_0032: stfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::i // The value of the 'i' field of the container class instance // is loaded to the stack. IL_0037: ldloc.1 IL_0038: ldfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::i
  • 8. // 10 is loaded to the stack. IL_003d: ldc.i4.s 10 // If the value of the 'i' field is less than 10, // it jumps to the command IL_0015. IL_003f: blt.s IL_0015 // -================= THE FOREACH LOOP =================- //// The reference to the 'actions' list is loaded to the stack. IL_0041: ldloc.0 // The technical variable V_3 is assigned with the result // of the 'GetEnumerator' method of the 'actions' list. IL_0042: callvirt instance valuetype [mscorlib]System.Collections.Generic.List'1/Enumerator<!0> class [mscorlib]System.Collections.Generic.List'1<class [mscorlib]System.Action>::GetEnumerator() IL_0047: stloc.3 // The initialization of the try block // (the foreach loop is converted to // the try-finally construct) .try { // Jumps to the command IL_0056. IL_0048: br.s IL_0056 // Calls get_Current method of the V_3 variable.
  • 9. // The result is written to the stack. // (A reference to the Action object in the current iteration). IL_004a: ldloca.s V_3 IL_004c: call instance !0 valuetype [mscorlib]System.Collections.Generic.List'1/Enumerator<class [mscorlib]System.Action>::get_Current() // Calls the Invoke method of the Action // object in the current iteration IL_0051: callvirt instance void [mscorlib]System.Action::Invoke() // Calls MoveNext method of the V_3 variable. // The result is written to the stack. IL_0056: ldloca.s V_3 IL_0058: call instance bool valuetype [mscorlib]System.Collections.Generic.List'1/Enumerator<class [mscorlib]System.Action>::MoveNext() // If the result of the MoveNext method is not null, // then it jumps to the IL_004a command. IL_005d: brtrue.s IL_004a // Finishes the try block execution and jumps to finally. IL_005f: leave.s IL_006f } // end .try finally { // Calls the Dispose method of the V_3 variable. IL_0061: ldloca.s V_3
  • 10. IL_0063: constrained. Valuetype [mscorlib]System.Collections.Generic.List'1/Enumerator<class [mscorlib]System.Action> IL_0069: callvirt instance void [mscorlib]System.IDisposable::Dispose() // Finishes the execution of the finally block. IL_006e: endfinally } // Finishes the execution of the current method. IL_006f: ret } Conclusion The guys from Microsoft say that this is a feature, not a bug and that this behavior was made intentionally, aiming to increase the performance of the programs. You will find more information by this link. In reality it results in bugs and confusion of novice developers. An interesting fact is that the foreach loop had the same behavior before the C# 5.0 standard. The Microsoft was bombarded with complaints about nonintuitive behavior in the bug-tracker, but with the release of the C# 5.0 standard this behavior was changed by declaring the iterator variable inside every loop iteration, not before it on the compilation stage, but for all other constructions similar behavior remained without any changes. More information can be found by the link in the Breaking Changes section. You may ask how to avoid such an error? Actually the answer is very simple. You need to keep track of where and what variables you capture. Just remember that the container class will be created in that place where you have declared your variable that you will capture. If the capture occurs in the body of the loop, and the variable is declared outside it, then it's necessary to reassign it inside the body of the loop to a new local variable. The correct version of the example given in the beginning can be as follows: void Foo() { var actions = new List<Action>(); for (int i = 0; i < 10; i++) {
  • 11. var index = i; // <= actions.Add(() => Console.WriteLine(index)); } foreach(var a in actions) { a(); } } If you execute this code, the console will show the numbers from 0 to 9, as expected: 0 1 2 3 4 5 6 7 8 9 Looking at the IL code of the for loop from this example, we'll see that an instance of the container class will be created upon every iteration of the loop. Thus, the actions list will contain references to various instances with correct values of the iterators. // -================= THE FOR LOOP =================- // Jumps to the command IL_002d. IL_0008: br.s IL_002d // Creates an instance of a container class // and loads the reference to the stack. IL_000a: newobj instance void TestSolution.Program/'<>c__DisplayClass1_0'::.ctor()
  • 12. IL_000f: stloc.2 IL_0010: ldloc.2 // Assigns the 'index' field in the container class // with a value 'i'. IL_0011: ldloc.1 IL_0012: stfld int32 TestSolution.Program/'<>c__DisplayClass1_0'::index // Creates an instance of the 'Action' class with a reference to // the method of a container class and add it to the 'actions' list. IL_0017: ldloc.0 IL_0018: ldloc.2 IL_0019: ldftn instance void TestSolution.Program/'<>c__DisplayClass1_0'::'<Foo>b__0'() IL_001f: newobj instance void [mscorlib]System.Action::.ctor(object, native int) IL_0024: callvirt instance void class [mscorlib]System.Collections.Generic.List'1<class [mscorlib]System.Action>::Add(!0) // Performs the increment to the 'i' variable IL_0029: ldloc.1 IL_002a: ldc.i4.1 IL_002b: add IL_002c: stloc.1 // Loads the value of the 'i' variable to the stack
  • 13. // This time it is not in the container class IL_002d: ldloc.1 // Compares the value of the variable 'i' with 10. // If 'i < 10', then jumps to the command IL_000a. IL_002e: ldc.i4.s 10 IL_0030: blt.s IL_000a Finally, let me remind you that we are all human beings and we all make errors, that's why it would be illogical, and as a rule long and resource-intensive to hope only for the human factor when searching for bugs and typos. So, it's always a good idea to use technical solutions to detect errors in the code. The machine doesn't get tired and does the work much quicker. Quite recently, we as a team of PVS-Studio static code analyzer developers have created a diagnostic rule that is aimed at detecting incorrect capture of the variables and anonymous functions inside the loops. In my turn I suggest checking your code with our analyzer and see if it can detect bugs in your code. At this point, I'm finishing my article, I wish you clean code bugless programs.