Salient Solutions

wrasslin ones and nones for fun and profit - Sky Sanders' Blog
Get your own ranked flair here
posts - 92, comments - 108, trackbacks - 0

One line of code to rule them all -or- C# closures and variable scope, the not so obvious

While trolling the interwebs trying to clear up some lexical ambiguity that clouds my mind at times regarding c# closures, lambdas, actions, predicates, anonymous methods and delegates and ran across a post on Rick's blog with a comment that brought a long forgotten hair-pulling-outty session I had while writing some JavaScript UI code. Specifically, the not so obvious way that closure scope combined with delegates can creep up and bite you on the ass if you don't pay attention.
    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
    }
While this snippet is valid, while fact checking my terminology I was directed to a stackoverflow post that has more discussion and succinct explanation that I am willing or able to provide. go there.

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 |

Feedback

Gravatar

# re: One line of code to rule them all -or- C# closures and variable scope, the not so obvious

Following my own analysis, thousands of people on our planet get the loan from good banks. So, there's a good chance to get a commercial loan in all countries.
8/23/2010 5:22 AM | LYNDA27Barron

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 7 and 7 and type the answer here:

Powered by: