31 07 2010
Yet Another C# Events Tutorial
I had a requirement recently where I needed to somehow inform my main application class of a change of state in a class instance, and I figured Events were the way to go, but this a small black hole in my C# knowledge, so I set out (as you do) onto the Google/Bing-land to find out more. And there is a lot. But I found myself swamped in overly-complex explanations as to how to create an event in a class which could be handled by my main application class. All of the examples I found used 3 classes: a main application class, an “Event Handler” class and the class which raises the event itself. This I couldn’t get my head around but I managed to create the behaviour I needed with only two classes. And here’s how I did it.
Again, the requirement was to have one class enter into a state which required some processing at an arbitrary time by my main application.
This example works in any type of application, Windows Forms, Console, Class Library, even Silverlight. What we start with though is a separate class which performs our application logic, for example processing some business logic, or even some data access. In this example I’ll have a class which takes in a number and loops up to this given number, printing out prime numbers as it goes on it’s way.
Our implementation of this class could look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
namespace EventsExample { class PrimeNumberCalculator { private int _finishNumber; public PrimeNumberCalculator(int finish) { _finishNumber = finish; } public void Go() { int j; bool cur = false; for (int i = 2; i <= _finishNumber; i++) { for (j = 2; j < i; j++) { if (i % j == 0) { cur = true; break; } } if (cur == false) { // Do something as we're at a prime number } else cur = false; } } } } |
And our Program code to run this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
namespace EventsExample { class Program { static void Main(string[] args) { Console.Write("Please enter a whole number: "); int finishingNumber = Int32.Parse(Console.ReadLine()); PrimeNumberCalculator prime = new PrimeNumberCalculator(finishingNumber); Console.WriteLine("Press enter to continue..."); Console.ReadLine(); } } } |
Now, if we run this code, we get a prompt, we enter a number and it ends with no output. What we need to do is to raise an event when a prime number is reached so we can handle this in any way we want in our Program class. We can start this by declaring a namespace-scoped delegate above the class declaration for PrimeNumberCalculator, like this:
1 |
public delegate void PrimeReached(object o, int aPrime); |
Delegates are integral to how this works, so I recommend having a quick Google round about delegates, reading up on sites like MSDN after reading this. Basically what we’ve done is create a delegate method type which can be declared later, but must take the parameters of an object and an integer. This integer will be the prime number we send back and have our event handler “Handle” it in some way. Next, near the class fields definitions, declare an event of type “PrimeReached”, like this (make sure it’s public):
1 |
public event PrimeReached OnPrimeReached; |
Lastly, all we need to do is to call this event at the state in which we want. In our example, it’s inside the cur==false if test, where we have our comment in the code block above. Have that if-test look like this:
1 2 3 4 5 6 |
if (cur == false) { OnPrimeReached(this, j); } else cur = false; |
This makes a call to our delegate method. Notice that we don’t we have an implementation of the method, we “delegate” this functionality to whatever instantiates this class. Which is exactly what we’re gonna do next. Open up the Program class, and declare an event handler, and tell the prime class to “Go” while we’re at it.
1 2 |
prime.OnPrimeReached += new PrimeReached(prime_OnPrimeReached); prime.Go(); |
If you’re in Visual Studio, when you type the “+=” and a space, it’ll prompt you to press “TAB” to autocomplete, go ahead and let it do this, and press TAB again to produce the event handler stub, which looks like this:
1 2 3 4 |
static void prime_OnPrimeReached(object o, int aPrime) { throw new NotImplementedException(); } |
Delete the “throw” statement. Notice we have an int called “aPrime” being passed to us. We can just print this out with some explanatory text, and hey presto!
1 2 3 4 |
static void prime_OnPrimeReached(object o, int aPrime) { Console.WriteLine("The prime number was found: {0}", aPrime); } |
Below follows full code listing plus sample output text: Program.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EventsExample { class Program { static void Main(string[] args) { Console.Write("Please enter a whole number: "); int finishingNumber = Int32.Parse(Console.ReadLine()); PrimeNumberCalculator prime = new PrimeNumberCalculator(finishingNumber); prime.OnPrimeReached += new PrimeReached(prime_OnPrimeReached); prime.Go(); Console.WriteLine("Press enter to continue..."); Console.ReadLine(); } static void prime_OnPrimeReached(object o, int aPrime) { Console.WriteLine("The prime number was found: {0}", aPrime); } } } |
PrimeNumberCalculator.cs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 |
using System; using System.Collections.Generic; using System.Linq; using System.Text; namespace EventsExample { public delegate void PrimeReached(object o, int aPrime); class PrimeNumberCalculator { private int _finishNumber; public event PrimeReached OnPrimeReached; public PrimeNumberCalculator(int finish) { _finishNumber = finish; } public void Go() { int j; bool cur = false; for (int i = 2; i <= _finishNumber; i++) { for (j = 2; j < i; j++) { if (i % j == 0) { cur = true; break; } } if (cur == false) { OnPrimeReached(this, j); } else cur = false; } } } } |
Output:
Been Quiet? SharePoint Search API Gotchas
This was the first digestible events tutorial so far. Thanks!