public class ClosureScopeStudy { public void AccessingAModifiedClosure_OR_WhatNotToDoAndHowToFixIt() { var listA = new List {new A(), new A(), new A(), new A()}; var listB = new List(); foreach (A a in listA) { // on each iteration 'a', which has been declared in // the enclosing scope of the foreach, // is being reassigned to enumerator.next var b = new B(); b.Event += delegate { SomeAction(a); }; // Access to modified closure: ^^^ // a reference to 'a' is captured by this // delegates closure. // when the closure loses scope the // reference has been set to the enumerator.last listB.Add(b); } listB.ForEach(Out); Console.WriteLine("Not what you expected, eh?\n"); // Results: // b.ID=0, a.ID=3 // b.ID=1, a.ID=3 // b.ID=2, a.ID=3 // b.ID=3, a.ID=3 // Not what you expected, eh? // try it again with a very subtle yet simple fix listA = new List {new A(), new A(), new A(), new A()}; listB = new List(); foreach (A a in listA) { // dereference the enumerator into the scope // of this block A localA = a; var b = new B(); b.Event += delegate { SomeAction(localA); }; listB.Add(b); } // 'a' is still being reassigned on each iteration, but 'localA' // is only ever assigned once and results in expected behavior. listB.ForEach(Out); Console.WriteLine("What you expected.\n"); // Results // // b.ID=4, a.ID=4 // b.ID=5, a.ID=5 // b.ID=6, a.ID=6 // b.ID=7, a.ID=7 // What you expected. // same thing using a lambda listA = new List {new A(), new A(), new A(), new A()}; listB = new List(); foreach (A a in listA) { A localA = a; var b = new B(); b.Event += ((sender, e) => SomeAction(localA)); listB.Add(b); } listB.ForEach(Out); Console.WriteLine("Using a lambda.\n"); // Results // b.ID=8, a.ID=8 // b.ID=9, a.ID=9 // b.ID=10, a.ID=10 // b.ID=11, a.ID=11 // Using a lambda. Console.WriteLine("\nFinished. Press Enter"); Console.ReadLine(); } private static void Out(B b) { Console.Write("b.ID={0}, ", b.Id); b.OnEvent(EventArgs.Empty); } private static void SomeAction(A a) { Console.WriteLine("a.ID={0}", a.Id); } #region Nested type: A public class A { private static int _id; public int Id; public A() { Id = _id++; } } #endregion #region Nested type: B public class B { private static int _id; public int Id; public B() { Id = _id++; } public event EventHandler Event; internal void OnEvent(EventArgs e) { EventHandler handler = Event; if (handler != null) handler(this, e); } } #endregion }
Or you can read this JavaScript Closures for Dummies and notice example 5. Same rules apply, albeit by different means
Print | posted on Wednesday, December 16, 2009 2:28 AM |
Powered by:
Copyright © Sky Sanders