Delegates/Events questions
Dizzy Egg

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

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)":

  1. You can actually just write "dealWithString", and the "new myEventHandler(...)" part will be generated for you.

  2. Either way, you are capturing the 'this' pointer at the time (it is part of the delegate instance you just made), so yes, it will be called on your main class instance later on.

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:
myClassInstance.onGetString += (source, e) => { ...code goes here... };
(You can write explicit parameter types too if you want to.) Be aware that if you do this, the relevant locals get compiled as references into a temporary object instantiated each time their scope is entered (this is true both inside and outside the lambda). [EDIT: Also, beware of the lambda foreach bug - the 'foreach' iterator variable is instantiated once, so if you use it in a lambda inside the loop, then all instances of the lambda ultimately see the last value it took on during the iteration. However, they fixed this in some new version of C# by changing the way 'foreach' is compiled.]

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

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

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

That's awesome thank you for helping Bruce.

bamccaig

The usual pattern is as follows (note, C# usually uses upper-camel-case, not lower-camel-case as a style rule):

#SelectExpand
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!

Bruce Perry

I believe the newest C# lets you replace this:

if(this.Over9000 != null) 
{
    this.Over9000(this, args); 
}

with this:

this.Over9000?(this, args);

bamccaig

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.

Dizzy Egg

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
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.

Dizzy Egg

Excellent, thanks bam. I like the overloaded function creating the EventArgs, that's very handy!

Thread #616635. Printed from Allegro.cc