Hi,
today I dug deeper into the world of javascript closures and found an interesting mozilla.org -page explaining the topic of closures perfectly. I started getting curious about how implementations like in https://jsfiddle.net/v7gjv/ as described in https://developer.mozilla.org/en/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake could look like in C# and whether the same solutions lead to same success.
The problem depicted in the article referenced above is that a closure was used wrongly inside the method "setupHelp".
today I dug deeper into the world of javascript closures and found an interesting mozilla.org -page explaining the topic of closures perfectly. I started getting curious about how implementations like in https://jsfiddle.net/v7gjv/ as described in https://developer.mozilla.org/en/docs/Web/JavaScript/Closures#Creating_closures_in_loops_A_common_mistake could look like in C# and whether the same solutions lead to same success.
The problem depicted in the article referenced above is that a closure was used wrongly inside the method "setupHelp".
1 2 3 4 5 6 | for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { showHelp(item.help); } } |
The problem (quote taken over from the article):
The reason for this is that the functions assigned toIn the article a solution was provided. They used a function factory method, but I think the following solution is a bit better (even if it is possibly the same thing, but with less code and without the need of jumping around on the screen; the cleanliness of this solution might be doubted, but I do like this version the most):onfocus
are closures; they consist of the function definition and the captured environment from thesetupHelp
function's scope. Three closures have been created, but each one shares the same single environment. By the time theonfocus
callbacks are executed, the loop has run its course and the item variable (shared by all three closures) has been left pointing to the last entry in thehelpText
list.
1 2 3 4 5 6 7 8 9 | for (var i = 0; i < helpText.length; i++) { var item = helpText[i]; document.getElementById(item.id).onfocus = function() { var myItem = item; return function() { showHelp(myItem.help); } }(); } |
... here we create an anonymous function on line 3 and call it instantly on line 8 to return an actual event handler (line 5). This function call brings us in a new scope in which we can access the variable from line 2 on line 4 and store it inside of our new scope to use it in line 6 from the event handler.
So... can we have such problems in C#?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | class Program { class MenuItem { public event EventHandler<EventArgs> onfocus; public void Call_onfocus() { onfocus(this, EventArgs.Empty); } } static void showHelp(string name, string help) { Console.WriteLine("{0,7}: {1}", name, help); } static void Main(string[] args) { var helpText = new List<Tuple<string, string>>{ new Tuple<string, string>("email", "Your e-mail address"), new Tuple<string, string>("name", "Your full name"), new Tuple<string, string>("age", "Your age (you must be over 16)"), }; var document = new Dictionary<string, MenuItem>(); helpText.ForEach(x => document.Add(x.Item1, new MenuItem())); for (var i = 0; i < helpText.Count; i++) { var item = helpText[i]; document[item.Item1].onfocus += (sender, e) => { showHelp(item.Item1, item.Item2); }; } document.ToList().ForEach(x => x.Value.Call_onfocus()); } } |
Unfortunately the version that breaks in javascript works just fine in C#. So I needed to change a bit in the for-loop to demonstrate the problem...
1 2 3 4 5 6 7 | for (var i = 0; i < helpText.Count; i++) { document[helpText[i].Item1].onfocus += (sender, e) => { showHelp(helpText[i].Item1, helpText[i].Item2); }; } |
The for-loop now accesses the array directly and here we are... out-of-range-exception,.. boom... because i is 3 when the Call_onfocus (line 36 in the listing above) calls the event calling showHelp (line 5).
Now we know, that the problem is also a real-world problem in .NET environments. Can we now take over the way to fix the issue in javascript to the C# world? It would be helpful to know one working solution for two (or all kind of) different worlds.
I was able to do so, but I admit the solution is a bit ... let's say: different.
1 2 3 4 5 6 7 8 9 10 11 12 13 | for (var i = 0; i < helpText.Count; i++) { document[helpText[i].Item1].onfocus += ( new Func<EventHandler<EventArgs>>( () => { var myItem = i; return (sender, e) => { showHelp(helpText[myItem].Item1, helpText[myItem].Item2); }; })()); } |
... as we can see here: I reused the solution of listing #2 and stored the changing value into a newly created scope I created by writing an ad-hoc function I instantly call.
The good things here are that we are not limited to pass in data through event args and minimize the lines of code/number of declared functions, but to be honest I would not recommend to use this kind of hoax too much...
kind regards,
Daniel
No comments:
Post a Comment