Is there a good reason why I get the following output for the following code-example, or is it do to optimization of the Visual Studio 2005 compiler?
Output:
5 0 0 4
5 1 1 4
5 2 2 4
5 3 3 4
5 4 4 4
Columns 2 and 3 is what I expected.
using
System;using System.Collections.Generic;
using System.IO;
using System.Text;
namespace Test
{
public delegate void TestDelegate();
public class TestObj
{
private int i;
public TestObj(int i) { this.i = i; }
public override string ToString() { return i.ToString(); }
}
class Program
{
public static event TestDelegate act;
static void Main(string[] args)
{
int k;
for (int i = 0; i < 5; i++)
{
int j = i;
k = i;
TestObj t = new TestObj(i);
act += delegate() { Console.WriteLine(i + " " + t + " " + j + " " + k); };
}
act();
Console.ReadLine();
}
}
}
Thanks!
AlanPosted Dec 6, 2007, 4:58 PM
Yes, you will get situations where outer variables which are reference types will produce different behaviour to those which are value types (such as int). All you can do is to think each one out carefully.
It has to be admitted that this stuff is very strange for folks who are used to 'imperative' languages such as C#, C++ or VB.Net. It's also been beefed up considerably with the introduction of lambda expressions and anonymous types in C# 3.0, which are fundamental to the way LINQ works, and it's clear that Microsoft are very keen on adding functional language concepts to their .NET languages.
In fact, they announced recently that their own functional language, F#, is to be included in the next version of Visual Studio so I think we're going to have to get used to this way of thinking in future. Personally though I'll be sticking with C# :)
Kasper HallenborgPosted Dec 6, 2007, 4:43 PM
Properly my confusion related to my original problem, from where this example was created. There I had a foreach loop (looping over object elements in a list) inside a for loop (looping over some integers. Like
for(int i=0; i<..; i++)
foreach(Element e in list)
delegate() { something with e and i }
and the e's were ok, but the i's were not. And if something should have been wrong, I was actually have expected the oppesite. So a second thought to also your explanation help me understand that the life-time of thoose local variables are extend as long as the delegates exists, and there was no copy of value, but that must have been the case with the e's.
Thanks! again.
Kasper HallenborgPosted Dec 6, 2007, 4:27 PM
Thank, Alan!!
Your explanation makes sense, and I was thinking something similar myself. However I still find it a little bit strange, when using integral types and structs, where I would have expected a more immediate "call-by-value" behaviour. The currenct behaviour is more like "call-by-reference" and the integers have not been declared or used with either ref or out.
So I understand you explanation and the behaviour of the example, but I'm not sure if I find it very logical compared to the normal behaviour of working with integral types.
AlanPosted Dec 6, 2007, 4:07 PM
The reason for the apparently strange results is the presence of this 'anonymous method' :
delegate() { Console.WriteLine(i + " " + t + " " + j + " " + k); };
An anonymous method 'captures' any 'outer variables' (local variables or value parameters in whose scope the method is defined) to form what's known as a 'closure'.
This means that the lifetime of the outer variables is extended for as long as the anonymous method itself exists and so, when the anonymous method is executed, it's the current value of those variables that is used, not the value they had when the method was defined.
In this case, we actually have 5 anonymous methods which are invoked in turn when the event, 'act', is fired. If we now consider the state of the variables which are captured by these anonymous methods when 'act' is fired:
1. The for loop variable 'i' has a value of 5 i.e. the value it had when the for loop test i < 5 failed and the loop ended.
2. A new instance of 't' (and hence of TestObj) is created each time through the loop and the value of its field 'i' is set to the current value of the loop variable (also called 'i'). So each anonymous method captures 't' with successive field values of 0,1,2,3,4 which are then returned by the TestObj.ToString() method.
3. A new instance of the local variable 'j' is also created each time through the loop and set to the value of the for loop variable 'i'. So each anonymous method captures 'j' with successive field values of 0,1,2,3,4.
4. Finally, the local variable 'k' is declared outside the for loop and consequently there is only one instance. This has been set to 4 by the time the for loop ends.
So, I think you'll now be able to see, Kasper, why you got the results you did when you ran this code.