![]() |
|
Delegates/Events questions |
Dizzy Egg
Member #10,824
March 2009
![]() |
Hello guys, I've started teaching myself C#, and am struggling to get my head around some code, specifically relating to delegates/events, and am looking for some advice on if I'm understanding them... So I have this line: public delegate void myEventHandler(object source, myEventArgs e); Which I understand defines the shape of a method, that can be used as a pointer to a function. I also understand the myEventArgs part, so wont type that out! Then, later in the code, I have a class that has this code: class myClass { ... public event myEventHandler onGetString; ... public void receiveString(string aString) { onGetString(this, new myEventArgs(aString)); } ... Now, I understand this as saying whenever the receiveString functon is called, it will fire the onGetString event (where some stuff happens in myEventArgs class). HOWEVER....this is where my brain stops working....so, in my main class, I have this code: myClass myClassInstance = new myClass(); myClassInstance.onGetString += new myEventHandler(dealWithString); public void dealWithString(object source, myEventArgs e) { printString(e.getString()); } NOW, my problem is this; does that mean that, whenever myClass calls onGetString (when onReceiveString is called), it will also call dealWithString in the main class? I hope that makes sense? I didn't write this code, I'm just trying to understand it!! Does it mean I can basically use += to keep adding custom methods on top of onGetString? So whenever onGetString gets called in myClass, all other classes that have added using += will also get their attached method called? Am I making ANY sense??
---------------------------------------------------- |
Bruce Perry
Member #270
April 2000
|
The 'event' keyword is some magic that's worth describing separately. A field marked as 'event' will allow only += and -= operations from any location outside the class (i.e. calling it or otherwise taking its value will fail as if the field is declared private). I'm not sure if operations like directly assigning it are allowed inside the class. You can also declare explicit 'add' and 'remove' handlers (much like 'get' and 'set' for properties) if you want to. There are two things to explain with "new myEventHandler(dealWithString)":
The next two things you might find interesting: Lambda functions are like inline delegates, but these are capable of capturing local variables in addition to 'this'. So you can write something like: If you choose not to use 'event', then the code will still work but through a different mechanism. The operators '+' and '-' are supported for delegates, but they create new instances, just as with strings. So, if you write "x += y", then it compiles into a read operation on 'x', a combine operation which makes a new delegate object without disturbing the old ones, and then an assignment operation (just as with strings). A delegate contains a list of methods, which is used if you later use the '-' or '-=' operators. Calling a delegate entails calling the methods in sequence. Perhaps an event field is implemented this way too, but in a more wrapped fashion (the class does it itself), I'm not sure - and of course you have the option to override this, which you don't if you don't use the 'event' keyword. Hope that helps -- |
Dizzy Egg
Member #10,824
March 2009
![]() |
Thanks Bruce, some helpful points there. Can you clarify one thing for me, in my example, when myClass fires onGetString, it sends the received string (via a new myEventArgs), and obviously in the main class, is prints the string sent...SO, does that mean in the main class when dealWithString is called, it uses the arguments sent by myClass's onGetString? (ie this and new myEventArgs(aString))
---------------------------------------------------- |
Bruce Perry
Member #270
April 2000
|
Yep. That part of the operation is exactly like a function pointer call Or if you want a C++ parallel, then a functor - one which wraps the 'this' and any locals, same way a C++ functor (callable object) can. -- |
Dizzy Egg
Member #10,824
March 2009
![]() |
That's awesome thank you for helping Bruce.
---------------------------------------------------- |
bamccaig
Member #7,536
July 2006
![]() |
The usual pattern is as follows (note, C# usually uses upper-camel-case, not lower-camel-case as a style rule): 1using System;
2
3public class Program
4{
5 public static int Main(string[] argv)
6 {
7 var foo = new Foo();
8
9 foo.Over9000 += (sender, args) => { 10 Console.WriteLine("Wow, we've counted to {0}!", args.Count);
11 };
12
13 for(int i=0,l=9010; i<l; i++)
14 {
15 foo.Increment();
16 }
17
18 return 0;
19 }
20}
21
22public class Foo
23{
24 // Define your event as an EventHandler, either with type argument
25 // or implicitly using EventArgs.
26 public event EventHandler<Over9000EventArgs> Over9000;
27
28 public int Count { get; protected set; }
29
30 public void Increment()
31 {
32 if(++this.Count > 9000)
33 {
34 // When the event occurs you trigger handlers, usually using a
35 // wrapper like this.
36 this.OnOver9000(this.Count); 37 }
38 }
39
40 // To avoid duplicating code you usually define a wrapper to trigger the
41 // event handlers.
42 protected void OnOver9000(Over9000EventArgs args)
43 {
44 if(this.Over9000 != null) 45 {
46 this.Over9000(this, args); 47 }
48 }
49
50 // Often I'll wrap the constructor of the event args type so that the
51 // caller doesn't have to construct the args object itself.
52 protected void OnOver9000(int count)
53 {
54 this.OnOver9000(new Over9000EventArgs(count)); 55 }
56}
57
58// Derive from EventArgs base class if your event has state, otherwise
59// just use the base.
60public class Over9000EventArgs: 61 EventArgs
62{
63 public Over9000EventArgs(int count) { this.Count = count; }
64
65 public int Count { get; protected set; }
66}
Output: Wow, we've counted to 9001! Wow, we've counted to 9002! Wow, we've counted to 9003! Wow, we've counted to 9004! Wow, we've counted to 9005! Wow, we've counted to 9006! Wow, we've counted to 9007! Wow, we've counted to 9008! Wow, we've counted to 9009! Wow, we've counted to 9010!
-- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Bruce Perry
Member #270
April 2000
|
I believe the newest C# lets you replace this: if(this.Over9000 != null) { this.Over9000(this, args); } with this: this.Over9000?(this, args); -- |
bamccaig
Member #7,536
July 2006
![]() |
Ah, that's cool. I knew they were introducing a null-safe dot operator. Either "?." or ".?". I forget. I haven't gotten to use those features yet because we weren't getting Visual Studio upgrades... Though now I think that we're all running the free community edition so we probably could take advantage of those features now. C# events happen to come up relatively rarely in my professional work because it's almost all Web-based. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Dizzy Egg
Member #10,824
March 2009
![]() |
Hello bam - couple of questions, 1) why do you use 2 versions of void OnOver9000? One with EventArgs and one with an int? 2) why do you not use a delegate? 3) Why doesn't your definition of void OnOver9000 have an object as an argument, yet your call to += does?
---------------------------------------------------- |
bamccaig
Member #7,536
July 2006
![]() |
Dizzy Egg said: Hello bam - couple of questions, 1) why do you use 2 versions of void OnOver9000? One with EventArgs and one with an int? 2) why do you not use a delegate? 3) Why doesn't your definition of void OnOver9000 have an object as an argument, yet your call to += does? 1) The OnOver9000 overload is an optional convenience. If it so happens that the event can occur in many places. Putting this into a method saves the redundant task of constructing the object with the integer parameter. On the other hand, having both makes it trivial and efficient to pass an existing Over9000EventArgs instance as the argument rather than forcing yourself to construct a new one just because your API is limited. The int-overload's job is just constructing the args object and passing it on to the other one that actually does the work. 2) I am using delegates, but it's being hidden with syntactic-sugar and API-sugar. The standard EventHandler<T> type is a delegate. This is a generic definition for the shape of an event. This saves you the redundant job of defining a delegate type for each event. EventHandler<X> // is the same as XEventHandler // where delegate void EventHandler<T>(object sender, T args); delegate void XEventHandler(object sender, X args); There is an additional version too for when your event takes no real arguments: delegate void EventHandler(object sender, EventArgs args); It just accepts the stateless base class to fit the event interface pattern. 3) There are two OnOver9000 methods. One accepts an int, the other accepts the derived EventArgs object. The int override constructs the derived object from the int parameter and passes that along to the object override. The object override actually invokes the delegate with the EventArgs parameter. It's just a bit of forward-thinking code-reuse. Completely optional and not at all necessary here. -- acc.js | al4anim - Allegro 4 Animation library | Allegro 5 VS/NuGet Guide | Allegro.cc Mockup | Allegro.cc <code> Tag | Allegro 4 Timer Example (w/ Semaphores) | Allegro 5 "Winpkg" (MSVC readme) | Bambot | Blog | C++ STL Container Flowchart | Castopulence Software | Check Return Values | Derail? | Is This A Discussion? Flow Chart | Filesystem Hierarchy Standard | Clean Code Talks - Global State and Singletons | How To Use Header Files | GNU/Linux (Debian, Fedora, Gentoo) | rot (rot13, rot47, rotN) | Streaming |
Dizzy Egg
Member #10,824
March 2009
![]() |
Excellent, thanks bam. I like the overloaded function creating the EventArgs, that's very handy!
---------------------------------------------------- |
|