Fork me on GitHub

Tuesday, August 28, 2007

John "Delegate" Doe

It seems I've just experienced a unique little caveat of anonymous delegates. One of my favorite things about an anonymous method in .NET is that it can carry over variable values from the defining code into the definition of the method. Essentially this (which is just sample code and not a good reference for proper logging practice ;D):

private void RegisterClickLogger(string logTitle)
{
Log log = new Log(logTitle);
this.Click += delegate(object sender, EventArgs e)
{
log.Write("Clicked!");
};
}

will write "Clicked!" to the log object created as a local variable in the RegisterClickLogger method even though RegisterClickLogger has stopped executing. The purpose of this post isn't really to introduce this concept though, there are many views on proper usage and such for anonymous methods out there, but rather to point out a unique scenario that caused me to think a bit about it.

As some of you may have discovered through other annoying experiences (like myself :p), Dictionary stores it's values in a structure called KeyValuePair. The key here is that it's a "structure" (a Value Type), not a class (Reference Type). A common issue with this is that it complicates serialization (when using a Reference Type for one of the type parameters). Today, however, the following code through me for a bit of a loop:

foreach (KeyValuePair<string, Type> pair in s_tests)
{
b = new Button();
b.Text = pair.Key;
b.Click += delegate(object sender, EventArgs e)
{
Form f = (Form)Activator.CreateInstance(pair.Value);
f.ShowDialog(this);
f.Dispose();
f = null;
};
b.AutoSize = true;
flow.Controls.Add(b);
}


It seemed all was fine when I clicked the last button, but later when I needed to click one of the earlier buttons I noticed that the same Form was appearing for every button. Looking things over a bit I decided to try changing the code to this:

foreach (KeyValuePair<string, Type> pair in s_tests)
{
b = new Button();
b.Text = pair.Key;
Type t = pair.Value;
b.Click += delegate(object sender, EventArgs e)
{
Form f = (Form)Activator.CreateInstance(t);
f.ShowDialog(this);
f.Dispose();
f = null;
};
b.AutoSize = true;
flow.Controls.Add(b);
}

It's a very simple change, hardly noticeable really, but sure enough it did the job and my problem was solved. So what happened? Well I'd have to look into things a bit more to give a truly accurate, in depth answer, but from my initial impression it seems that the value of "pair" (that was referenced by all of the anonymous delegates created) was updated (as expected) through each iteration in the loop. When a delegate would finally get called the loop had finished iterating and pair was the value of the last item in the Dictionary. I should probably look into it a little further to identify whether this treatment is related to the fact that pair was a Value Type or if the declaration of the variable "t" inside the scope of the loop is the only reason it was solved (t being a Reference Type). I'm just curious because I don't recall all of the nuances of anonymous methods in .NET, though I'd venture a guess that the second possibility is more likely (though I would, so much like to have another reason to hate KeyValuePair being a Value Type).

Alas, there is never enough time and I must run, but I'll be back soon to discuss some developments in my research (as promised),

-TheXenocide

1 comment:

TheXenocide said...

A brief look at the not so distant past and it's amazing to me that I didn't understand closures at this point in my dev history. I mean I've been using JavaScript for way too long...