an unexpected absence of fail ... using anonymous actions in a WinForm EventHandler
-
I'm am puzzled by this: 1. I have a Dictionary whose Keys are Enumeration-Values, and whose Values are of Type Action<Control1, Control2> 2. At run-time I am calling a method that adds a LocationChanged EventHandler which is constructed as a lambda on-the-fly ... to Control1. This LocationChanged EventHandler will execute the Action described in #1 which will change the other Control, Control2. When Control2 refers to a Form, and, at run-time, that Form has been closed using its Close Button: my impression, up to now, has been that references to that Form would be garbage collected, so if Control1 (another Form) was moved, and the LocationChanged event was triggered which attempted to modify Control1 (closed Form), I would expect a null reference error. But, there is no run-time error thrown; I infer from that there must be valid references to the closed Form "still around." I think I better illustrate this with a dumbed-down bare-bones code example: a WinForms project with a MainForm 'Form1, and second Form, 'Form2:
enum SomeEnum
{
whatever1,
whatever2
}private Dictionary<SomeEnum, Action<Control, Control>> EnumToAction;
private void Form1_Load(object sender, EventArgs e)
{
EnumToAction = new Dictionary<SomeEnum, Action<Control, Control>>
{
{
SomeEnum.whatever1, (control, control1) =>
{
control1.Top = control.Top;
}
},
{
SomeEnum.whatever2, (control, control1) =>
{
control1.Left = control.Right;
}
}
};Form2 f2 = new Form2(); f2.Show(); SetAction(this, f2, SomeEnum.whatever1);
}
private void SetAction(Control bControl, Control dControl, SomeEnum someenum)
{
bControl.LocationChanged += (sender, args) =>
{
EnumToAction[someenum](bControl, dControl);
};// execute once to set initial alignment EnumToAction\[someenum\](bControl, dControl);
}
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid. I guess I could keep a Dictionary of Control, EventHandler around to deal with this ... to explicitly remove EventHandlers ... but, then, you've got different types of EventHandlers (differences in parameters) to deal with. Ideas ?
-
I'm am puzzled by this: 1. I have a Dictionary whose Keys are Enumeration-Values, and whose Values are of Type Action<Control1, Control2> 2. At run-time I am calling a method that adds a LocationChanged EventHandler which is constructed as a lambda on-the-fly ... to Control1. This LocationChanged EventHandler will execute the Action described in #1 which will change the other Control, Control2. When Control2 refers to a Form, and, at run-time, that Form has been closed using its Close Button: my impression, up to now, has been that references to that Form would be garbage collected, so if Control1 (another Form) was moved, and the LocationChanged event was triggered which attempted to modify Control1 (closed Form), I would expect a null reference error. But, there is no run-time error thrown; I infer from that there must be valid references to the closed Form "still around." I think I better illustrate this with a dumbed-down bare-bones code example: a WinForms project with a MainForm 'Form1, and second Form, 'Form2:
enum SomeEnum
{
whatever1,
whatever2
}private Dictionary<SomeEnum, Action<Control, Control>> EnumToAction;
private void Form1_Load(object sender, EventArgs e)
{
EnumToAction = new Dictionary<SomeEnum, Action<Control, Control>>
{
{
SomeEnum.whatever1, (control, control1) =>
{
control1.Top = control.Top;
}
},
{
SomeEnum.whatever2, (control, control1) =>
{
control1.Left = control.Right;
}
}
};Form2 f2 = new Form2(); f2.Show(); SetAction(this, f2, SomeEnum.whatever1);
}
private void SetAction(Control bControl, Control dControl, SomeEnum someenum)
{
bControl.LocationChanged += (sender, args) =>
{
EnumToAction[someenum](bControl, dControl);
};// execute once to set initial alignment EnumToAction\[someenum\](bControl, dControl);
}
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid. I guess I could keep a Dictionary of Control, EventHandler around to deal with this ... to explicitly remove EventHandlers ... but, then, you've got different types of EventHandlers (differences in parameters) to deal with. Ideas ?
Everyone knows that anonymous delegates cannot be removed so obviously there are places where they should not be used! But what if? What if the add and remove mechanisms are set up at the same time? Sure the delegate code is anonymous but, as you say in your message, a reference can be kept. I have lambdaphobia so here is the great idea in old school code.
public class Form1 : Form {
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
this.Text = "One";Form f = new Form(); f.Text = "Two"; f.Show(); ISeeAWitch(this, f);
}
private void ISeeAWitch(Form bControl, Form dControl) {
// I don't know who you are but I can point a finger at you
EventHandler locationChanged = new EventHandler(delegate(Object sender, EventArgs e) {
dControl.Left = bControl.Right;
dControl.Top = bControl.Top;
});bControl.LocationChanged += locationChanged; // I don't know who you are either but I can see that you are pointing at that other guy too dControl.FormClosed += new FormClosedEventHandler(delegate(Object sender, FormClosedEventArgs e) { if (bControl != null) { bControl.LocationChanged -= locationChanged; MessageBox.Show("I'm off"); } }); locationChanged(null, null);
}
}Alan.
-
I'm am puzzled by this: 1. I have a Dictionary whose Keys are Enumeration-Values, and whose Values are of Type Action<Control1, Control2> 2. At run-time I am calling a method that adds a LocationChanged EventHandler which is constructed as a lambda on-the-fly ... to Control1. This LocationChanged EventHandler will execute the Action described in #1 which will change the other Control, Control2. When Control2 refers to a Form, and, at run-time, that Form has been closed using its Close Button: my impression, up to now, has been that references to that Form would be garbage collected, so if Control1 (another Form) was moved, and the LocationChanged event was triggered which attempted to modify Control1 (closed Form), I would expect a null reference error. But, there is no run-time error thrown; I infer from that there must be valid references to the closed Form "still around." I think I better illustrate this with a dumbed-down bare-bones code example: a WinForms project with a MainForm 'Form1, and second Form, 'Form2:
enum SomeEnum
{
whatever1,
whatever2
}private Dictionary<SomeEnum, Action<Control, Control>> EnumToAction;
private void Form1_Load(object sender, EventArgs e)
{
EnumToAction = new Dictionary<SomeEnum, Action<Control, Control>>
{
{
SomeEnum.whatever1, (control, control1) =>
{
control1.Top = control.Top;
}
},
{
SomeEnum.whatever2, (control, control1) =>
{
control1.Left = control.Right;
}
}
};Form2 f2 = new Form2(); f2.Show(); SetAction(this, f2, SomeEnum.whatever1);
}
private void SetAction(Control bControl, Control dControl, SomeEnum someenum)
{
bControl.LocationChanged += (sender, args) =>
{
EnumToAction[someenum](bControl, dControl);
};// execute once to set initial alignment EnumToAction\[someenum\](bControl, dControl);
}
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid. I guess I could keep a Dictionary of Control, EventHandler around to deal with this ... to explicitly remove EventHandlers ... but, then, you've got different types of EventHandlers (differences in parameters) to deal with. Ideas ?
Quote:
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid.
What you are seeing is by design as the garbage collector makes the following guarantees: 1. Every object will be destroyed, and its destructor will be run. When a program ends, all remaining objects will be destroyed. 2. Every object will be destroyed exactly once. 3. Every object will be destroyed only when it becomes unreachable, i.e. when there are no references to the object in the process running your application. The garbage collector works on its own thread and can execute only at certain times, while it runs other threads in you application are temporarily halted in case it needs to move obects around and update references. Generally the following steps are taken by the garbage collector: 1. builds a map of the reachable objects 2. checks if any unreachable objects have a destructor that needs to be run (i.e finalization, if any are found they are moved to a special queue) 3. Deallocation of remaining unreachable objects, and moves reachable objects down the heap, updates any references to the reachable objects 4. allows other threads to resume 5. Runs the finalized methods from number 2
-
Everyone knows that anonymous delegates cannot be removed so obviously there are places where they should not be used! But what if? What if the add and remove mechanisms are set up at the same time? Sure the delegate code is anonymous but, as you say in your message, a reference can be kept. I have lambdaphobia so here is the great idea in old school code.
public class Form1 : Form {
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
this.Text = "One";Form f = new Form(); f.Text = "Two"; f.Show(); ISeeAWitch(this, f);
}
private void ISeeAWitch(Form bControl, Form dControl) {
// I don't know who you are but I can point a finger at you
EventHandler locationChanged = new EventHandler(delegate(Object sender, EventArgs e) {
dControl.Left = bControl.Right;
dControl.Top = bControl.Top;
});bControl.LocationChanged += locationChanged; // I don't know who you are either but I can see that you are pointing at that other guy too dControl.FormClosed += new FormClosedEventHandler(delegate(Object sender, FormClosedEventArgs e) { if (bControl != null) { bControl.LocationChanged -= locationChanged; MessageBox.Show("I'm off"); } }); locationChanged(null, null);
}
}Alan.
Thanks Alan, for this delightful code sample, and your comments. I entertained the idea that the act of closing a Form at run-time might be somehow qualitatively different ... vis-a-vis garbage-collection ... than the act of disposing other objects. I'd say that was a poor choice of entertainment :) I really like using Dictionaries whose keys are Enums and Values are executable code. cheers, Bill
«I want to stay as close to the edge as I can without going over. Out on the edge you see all kinds of things you can't see from the center» Kurt Vonnegut.
-
Quote:
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid.
What you are seeing is by design as the garbage collector makes the following guarantees: 1. Every object will be destroyed, and its destructor will be run. When a program ends, all remaining objects will be destroyed. 2. Every object will be destroyed exactly once. 3. Every object will be destroyed only when it becomes unreachable, i.e. when there are no references to the object in the process running your application. The garbage collector works on its own thread and can execute only at certain times, while it runs other threads in you application are temporarily halted in case it needs to move obects around and update references. Generally the following steps are taken by the garbage collector: 1. builds a map of the reachable objects 2. checks if any unreachable objects have a destructor that needs to be run (i.e finalization, if any are found they are moved to a special queue) 3. Deallocation of remaining unreachable objects, and moves reachable objects down the heap, updates any references to the reachable objects 4. allows other threads to resume 5. Runs the finalized methods from number 2
Thanks for your reply Member 11136461, As I wrote in reply to Alan's comments: "I entertained the idea that the act of closing a Form at run-time might be somehow qualitatively different ... vis-a-vis garbage-collection ... than the act of disposing other objects. I'd say that was a poor choice of entertainment" :) cheers, Bill
«I want to stay as close to the edge as I can without going over. Out on the edge you see all kinds of things you can't see from the center» Kurt Vonnegut.
-
I'm am puzzled by this: 1. I have a Dictionary whose Keys are Enumeration-Values, and whose Values are of Type Action<Control1, Control2> 2. At run-time I am calling a method that adds a LocationChanged EventHandler which is constructed as a lambda on-the-fly ... to Control1. This LocationChanged EventHandler will execute the Action described in #1 which will change the other Control, Control2. When Control2 refers to a Form, and, at run-time, that Form has been closed using its Close Button: my impression, up to now, has been that references to that Form would be garbage collected, so if Control1 (another Form) was moved, and the LocationChanged event was triggered which attempted to modify Control1 (closed Form), I would expect a null reference error. But, there is no run-time error thrown; I infer from that there must be valid references to the closed Form "still around." I think I better illustrate this with a dumbed-down bare-bones code example: a WinForms project with a MainForm 'Form1, and second Form, 'Form2:
enum SomeEnum
{
whatever1,
whatever2
}private Dictionary<SomeEnum, Action<Control, Control>> EnumToAction;
private void Form1_Load(object sender, EventArgs e)
{
EnumToAction = new Dictionary<SomeEnum, Action<Control, Control>>
{
{
SomeEnum.whatever1, (control, control1) =>
{
control1.Top = control.Top;
}
},
{
SomeEnum.whatever2, (control, control1) =>
{
control1.Left = control.Right;
}
}
};Form2 f2 = new Form2(); f2.Show(); SetAction(this, f2, SomeEnum.whatever1);
}
private void SetAction(Control bControl, Control dControl, SomeEnum someenum)
{
bControl.LocationChanged += (sender, args) =>
{
EnumToAction[someenum](bControl, dControl);
};// execute once to set initial alignment EnumToAction\[someenum\](bControl, dControl);
}
Running the above code, then closing 'Form2 by clicking its Form Close Button, and then moving 'Form1 does not then throw an error which makes me believe that the reference to 'Form2 in that created-lambda EventHandler must still be valid. I guess I could keep a Dictionary of Control, EventHandler around to deal with this ... to explicitly remove EventHandlers ... but, then, you've got different types of EventHandlers (differences in parameters) to deal with. Ideas ?
I suspect the key is to look at what the compiler's doing behind the scenes. Your anonymous
LocationChanged
handler closes over three parameters and an instance field, so the compiler will generate something like this:private sealed class SetActionClosure
{
public Form1 @this;
public Control bControl;
public Control dControl;
public SomeEnum someenum;public void AnonymousLocationChangedHandler(object sender, EventArgs e) { @this.EnumToAction\[someenum\](bControl, dControl); }
}
private void SetActive(Control bControl /* == this */, Control dControl /* == f2 */, SomeEnum someenum)
{
SetActionClosure closure = new SetActionClosure();
closure.@this = this;
closure.bControl = bControl; // == this
closure.dControl = dControl; // == f2
closure.someenum = someenum;bControl /\* == this \*/ .LocationChanged += closure.AnonymousLocationChangedHandler; ...
}
The instance of the closure class will remain alive as long as the event handler still has a reference to it - ie: until you unsubscribe the handler, or close the
Form1
instance. That closure class instance has a reference to theForm2
instance "f2
". ThatForm2
instance will remain in alive for as long as the closure class instance remains alive.
"These people looked deep within my soul and assigned me a number based on the order in which I joined." - Homer