Anonymous methods problem
-
I have two objects. Object A holds an event. Object B assigns an anonymous delegate to Object A's event. I want to access Object A from the code in the anonymous method.
foreach (Entity target in Entities)
{
[...]
target.OnTouch += new delegate()
{
if (Character == '+')
{
Character = '/';
passableBy = PassableFlags.normal;
}
return;
};
}Character and passableBy are properties of [target]. It won't compile, it says that Character and passableBy do not exist in the current context. How can I access properties of the event-holding object? [Edit] Note: This code is in Object B, and [target] point to Object A, for clarification.
modified on Tuesday, June 30, 2009 4:19 PM
-
I have two objects. Object A holds an event. Object B assigns an anonymous delegate to Object A's event. I want to access Object A from the code in the anonymous method.
foreach (Entity target in Entities)
{
[...]
target.OnTouch += new delegate()
{
if (Character == '+')
{
Character = '/';
passableBy = PassableFlags.normal;
}
return;
};
}Character and passableBy are properties of [target]. It won't compile, it says that Character and passableBy do not exist in the current context. How can I access properties of the event-holding object? [Edit] Note: This code is in Object B, and [target] point to Object A, for clarification.
modified on Tuesday, June 30, 2009 4:19 PM
Where do you expect to get the scope for these properties from ? target.Character and target.passableBy will work, because the anonymous method has the scope of the spot it was created.
Christian Graus Driven to the arms of OSX by Vista. "! i don't exactly like or do programming and it only gives me a headache." - spotted in VB forums. I can do things with my brain that I can't even google. I can flex the front part of my brain instantly anytime I want. It can be exhausting and it even causes me vision problems for some reason. - CaptainSeeSharp
-
Where do you expect to get the scope for these properties from ? target.Character and target.passableBy will work, because the anonymous method has the scope of the spot it was created.
Christian Graus Driven to the arms of OSX by Vista. "! i don't exactly like or do programming and it only gives me a headache." - spotted in VB forums. I can do things with my brain that I can't even google. I can flex the front part of my brain instantly anytime I want. It can be exhausting and it even causes me vision problems for some reason. - CaptainSeeSharp
previously, and it doesn't work. It always affects whatever [target] points to at the calling moment, ie. the wrong object. And I cannot... wait, that's an idea. Is there some way to pass over a pointer to itself together with the anonymous method? Or should I just give it up and think of something else? -
previously, and it doesn't work. It always affects whatever [target] points to at the calling moment, ie. the wrong object. And I cannot... wait, that's an idea. Is there some way to pass over a pointer to itself together with the anonymous method? Or should I just give it up and think of something else?
The method should have the scope of hte method that called it. If you created seperate variables for each object, or if you passed in a variable that was the index into the array and then looked up the object in the array in the method, that would work.
Christian Graus Driven to the arms of OSX by Vista. "! i don't exactly like or do programming and it only gives me a headache." - spotted in VB forums. I can do things with my brain that I can't even google. I can flex the front part of my brain instantly anytime I want. It can be exhausting and it even causes me vision problems for some reason. - CaptainSeeSharp
-
previously, and it doesn't work. It always affects whatever [target] points to at the calling moment, ie. the wrong object. And I cannot... wait, that's an idea. Is there some way to pass over a pointer to itself together with the anonymous method? Or should I just give it up and think of something else?
-
Where do you expect to get the scope for these properties from ? target.Character and target.passableBy will work, because the anonymous method has the scope of the spot it was created.
Christian Graus Driven to the arms of OSX by Vista. "! i don't exactly like or do programming and it only gives me a headache." - spotted in VB forums. I can do things with my brain that I can't even google. I can flex the front part of my brain instantly anytime I want. It can be exhausting and it even causes me vision problems for some reason. - CaptainSeeSharp
Christian Graus wrote:
target.Character and target.passableBy will work, because the anonymous method has the scope of the spot it was created
How is that possible? In his code, target is just the loop variable and the anonymous method - the event handler - might run a year after the loop has finished. The handler will exist as long as delegates referring to it does, but the local variable "target" goes out of scope once the loop finishes, and it's value is changed on each loop iteration. The anonymous method is still a method and it seems to me it should have it's own scope. (I see that it makes sense for things where the anonymous method actually *executes* in the containing methods, like when using List<T>.Find(Predicate<T> match), but here we're attaching an event handler.) I've seen many of your posts and have the impression you're in the know, so it would be nice if you can explain how this works with anonymous methods as event handlers. I'm also wondering if doing it like the OP did would lead to multiple anonymous methods? It seems it would have to be so if the method shares scope with it's containing method. To return to the OP's issue, surely the normal way to do this is to use a single event handler, attach it to all the targets, and pass a target to the eventhandler as "sender" in the EventHandler delegate?
public class Thing
{
public event EventHandler Touched;public void Touch()
{
//...
if (Touched != null) Touched(this, EventArgs.Empty);
}
}public class OtherThing
{
List Things = App.GetThings(); // Assume we have things...
public void Foo()
{
foreach (Thing t in Things)
t.Touched += new EventHandler(thing_touched);
}static void thing_touched(object sender, EventArgs e)
{
Thing source = (Thing)sender;
// ... work with the Thing ...
}
}(I believe it is a good practice to make all non-virtual methods that depend only on their parameters static. It works with an instance method as well, but there is no reason for it to be an instance method.) I hope you will comment on this as I'm now confused about the scoping of anonymous methods.
-
Christian Graus wrote:
target.Character and target.passableBy will work, because the anonymous method has the scope of the spot it was created
How is that possible? In his code, target is just the loop variable and the anonymous method - the event handler - might run a year after the loop has finished. The handler will exist as long as delegates referring to it does, but the local variable "target" goes out of scope once the loop finishes, and it's value is changed on each loop iteration. The anonymous method is still a method and it seems to me it should have it's own scope. (I see that it makes sense for things where the anonymous method actually *executes* in the containing methods, like when using List<T>.Find(Predicate<T> match), but here we're attaching an event handler.) I've seen many of your posts and have the impression you're in the know, so it would be nice if you can explain how this works with anonymous methods as event handlers. I'm also wondering if doing it like the OP did would lead to multiple anonymous methods? It seems it would have to be so if the method shares scope with it's containing method. To return to the OP's issue, surely the normal way to do this is to use a single event handler, attach it to all the targets, and pass a target to the eventhandler as "sender" in the EventHandler delegate?
public class Thing
{
public event EventHandler Touched;public void Touch()
{
//...
if (Touched != null) Touched(this, EventArgs.Empty);
}
}public class OtherThing
{
List Things = App.GetThings(); // Assume we have things...
public void Foo()
{
foreach (Thing t in Things)
t.Touched += new EventHandler(thing_touched);
}static void thing_touched(object sender, EventArgs e)
{
Thing source = (Thing)sender;
// ... work with the Thing ...
}
}(I believe it is a good practice to make all non-virtual methods that depend only on their parameters static. It works with an instance method as well, but there is no reason for it to be an instance method.) I hope you will comment on this as I'm now confused about the scoping of anonymous methods.
I've decided to just use EventArgs containing a reference to the appropiate object whenever it is called. Probably not very elegant, but it works, at least. It seems that anonymous methods have the scope of the creation place, but I'd have to do some research to say for certain...
-
I've decided to just use EventArgs containing a reference to the appropiate object whenever it is called. Probably not very elegant, but it works, at least. It seems that anonymous methods have the scope of the creation place, but I'd have to do some research to say for certain...
That is probably the sensible solution. But I still hope someone can help shed light on this; I've discovered something I'm unsure about and that is always an opportunity to learn. I could probably google it, but if someone has the answer at hand...
-
That is probably the sensible solution. But I still hope someone can help shed light on this; I've discovered something I'm unsure about and that is always an opportunity to learn. I could probably google it, but if someone has the answer at hand...
I've checked it. It always has the scope of the place where it was created.
public void init()
{
int i = 30;
foo(delegate()
{
i = 10;
});
Console.WriteLine("i = " + i.ToString());
Console.ReadKey();
}public void foo(EventHandler awesome)
{
awesome();
}This would return "i = 10". This is somehow creepy, because it's a variable local in another method... The same happens if foo is in another class. Additionally, if the creator object doesn't exist anymore... nothing happens:
using System;
using System.Collections.Generic;
using System.Text;namespace test2
{
class Program
{
static void Main(string[] args)
{
Thing1 thing1 = new Thing1();
Thing2 thing2 = thing1.Get2();
thing1 = null;
GC.Collect(); // Force Garbage Collection to be sure that thing1 doesn't exist anymore.
thing2.awesome(null, null);
return;
}
}public class Thing1 { public Thing2 Get2() { Thing2 thing2 = new Thing2(); thing2.awesome = delegate(object sender, EventArgs args) { thing2 = null; }; return thing2; } } public class Thing2 { public EventHandler awesome; }
}
Cleary, thing1 created the anonymous delegate. I've named the Thing2 variable the same everywhere, to be sure. Main.thing2 didn't turn into null, therefore it affected the non-existing thing1, ie. nothing happened. Also, no crash nor anything of that sort. It obviously has the scope of Thing1.Get2(), again the creator scope. [Edit] I've just noticed. The Garbage Collection doesn't take thing1! The anonymous delegate points to it after all...
modified on Wednesday, July 1, 2009 2:20 PM
-
I've checked it. It always has the scope of the place where it was created.
public void init()
{
int i = 30;
foo(delegate()
{
i = 10;
});
Console.WriteLine("i = " + i.ToString());
Console.ReadKey();
}public void foo(EventHandler awesome)
{
awesome();
}This would return "i = 10". This is somehow creepy, because it's a variable local in another method... The same happens if foo is in another class. Additionally, if the creator object doesn't exist anymore... nothing happens:
using System;
using System.Collections.Generic;
using System.Text;namespace test2
{
class Program
{
static void Main(string[] args)
{
Thing1 thing1 = new Thing1();
Thing2 thing2 = thing1.Get2();
thing1 = null;
GC.Collect(); // Force Garbage Collection to be sure that thing1 doesn't exist anymore.
thing2.awesome(null, null);
return;
}
}public class Thing1 { public Thing2 Get2() { Thing2 thing2 = new Thing2(); thing2.awesome = delegate(object sender, EventArgs args) { thing2 = null; }; return thing2; } } public class Thing2 { public EventHandler awesome; }
}
Cleary, thing1 created the anonymous delegate. I've named the Thing2 variable the same everywhere, to be sure. Main.thing2 didn't turn into null, therefore it affected the non-existing thing1, ie. nothing happened. Also, no crash nor anything of that sort. It obviously has the scope of Thing1.Get2(), again the creator scope. [Edit] I've just noticed. The Garbage Collection doesn't take thing1! The anonymous delegate points to it after all...
modified on Wednesday, July 1, 2009 2:20 PM
Thanks for that! But here too the delegate executes before the containing function returns. It's an EventHandler delegate, but it's used as any other delegate - there's no event. Using an anonymous method for an event handler isn't necessarily weird, but accessing variables that are scoped to the containing method from the event handler is very weird indeed. I did a quick test to see how this behaved with a windows forms app. In the constructor I go into a loop to make 5 buttons, and I attach an anonymous method to the Click event. In the handler I show a messagebox displaying the value of the loop variable. I figured if the method was supposed to somehow "see" the external variable when I press a button, which I think we can agree won't happen until after the constructor has finished, then it would mean that five anonymous methods and five copies of this external variable had to be created. What actually happens though is that clicking any button shows the value 5, so presumably there is only one copy of the method. What I am now wondering is where the heck this external variable lives. Normally local variables live on the stack, but if this one did it would have died when the constructor returned. So I presume it's on the heap somewhere. I'm not sure I like this. My test code:
public Form1()
{
InitializeComponent();
for (int i = 0; i < 5; i++)
{
Button b = new Button() { Text = "button" + i, Top = 30*(i+1), Left = 50 };
Controls.Add(b);
b.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show("variable i (scoped to Form1 constructor!) = " + i);
};
}
} -
Thanks for that! But here too the delegate executes before the containing function returns. It's an EventHandler delegate, but it's used as any other delegate - there's no event. Using an anonymous method for an event handler isn't necessarily weird, but accessing variables that are scoped to the containing method from the event handler is very weird indeed. I did a quick test to see how this behaved with a windows forms app. In the constructor I go into a loop to make 5 buttons, and I attach an anonymous method to the Click event. In the handler I show a messagebox displaying the value of the loop variable. I figured if the method was supposed to somehow "see" the external variable when I press a button, which I think we can agree won't happen until after the constructor has finished, then it would mean that five anonymous methods and five copies of this external variable had to be created. What actually happens though is that clicking any button shows the value 5, so presumably there is only one copy of the method. What I am now wondering is where the heck this external variable lives. Normally local variables live on the stack, but if this one did it would have died when the constructor returned. So I presume it's on the heap somewhere. I'm not sure I like this. My test code:
public Form1()
{
InitializeComponent();
for (int i = 0; i < 5; i++)
{
Button b = new Button() { Text = "button" + i, Top = 30*(i+1), Left = 50 };
Controls.Add(b);
b.Click += delegate(object sender, EventArgs e)
{
MessageBox.Show("variable i (scoped to Form1 constructor!) = " + i);
};
}
}