Project 5 - Delegates and Event Handling In this project you will learn what delegates are, how they improve the ‘robustness’ of your code, and how to create an event publisher that will fire an event to its subscribers. Reading all information is important to your understanding of the material at hand. Skipping ahead to complete the project will not further your understanding of how things are working concurrently with each other. Delegates: Delegates are used to specify a method to be called indirectly. In most programs, we specify a method within a class, call it, and the code runs. This is a good iterative approach to breaking up code, but what if we want to break that process into another thread, or maybe call a non-static member from another class within a static context? Not sure what I mean? Here’s an example of the simple iterative approach: using System; namespace Examples.410.NoDelegate { public class ProcessToRun { public void Process() { Console.WriteLine("Process() start."); Console.WriteLine("Process() end."); } } public class application { public static void Main() { ProcessToRun myProc = new ProcessToRun(); myProc.Process(); } } } What we see here is a way to declare multiple classes within a namespace and file, as well as creating a non-static class from which we can declare an object, and utilize the methods of that object. An important note about this approach is that if we create any data from our application and choose to process it with the object we created, that data must be sent to it. This can be a problem when multiple instances of the object have been created. Anyway, what we want to do now is explore delegates. Sometimes we don’t want to call a function directly; rather, we’d just like to say this is the method I want to call; you call it when you want to. Enter the delegate, which becomes phenomenally useful for event-driven applications (i.e. GUIs, services) so that a particular piece of code executes at the click of a button, or information is logged without specifying how. The funny thing about a delegate is that it doesn’t care about the object it references. As long as a methods argument types and return type are matched, it works fine. With this feature, anonymous invocation can occur. There are a couple types of delegates as well (though they are declared the same): single-cast and multicast. A single-cast delegate basically works like this: we declare the delegate which states that now we can use that delegate to reference any method with the return and argument types of that delegate. In the main application, the delegate is used like a class, so we declare a new object instance of the delegate, and we send it the method we wish to call. Later, we can call on this object (of the delegate) which will then call the method we gave it in the declaration. This seems kind of silly of first, but will make more sense later, just bear with me. Here’s what it looks like to declare a delegate: delegate result-type identifier ([parameters]) Where: result-type is the return type of the method identifier is the delegate name (pseudo class name) parameters are the parameters to the method. So now we are ready to see this simple delegate in action: using System; namespace Examples.410.SimpleDelegate { //Create the delegate public delegate void SimpDel(); public class application { //The function that will be called by the delegate public static void MethCall() { Console.WriteLine("Called by delegate..."); } public static void Main() { //Create an instance of the delegate SimpDel simplex = new SimpDel(MethCall); //Call the delegate function simplex(); } } } When compiled and executed, the output shows this: Called by delegate... Pretty neat huh? There still doesn’t seem to be a lot we can do, but let’s take it a little farther. Say we have an application that calls upon another class to perform some tasks, but for error messages to be logged, the class uses a delegate to log the message. So now we need our application to declare an instance of this delegate and then find a function (or build one) that can handle receiving and "logging" a string. Here’s the code: using System; namespace Examples.410.LogHandlerSimple { public class ProcessToRun { // Create a delegate to receive a string public delegate void LogHandler(string message); //This abstract statement declares a method that receives a //delegate of class-global type LogHandler which can be used //e.g. a method to send data to a function that takes a string public void Process(LogHandler logHandler) { //delegate must hold a method to call if (logHandler != null) { logHandler("Process() begin"); } if (logHandler != null) { logHandler ("Process() end"); } } } // Test Application to use the defined Delegate public class application { //A static method that matches the signature required by //the delegate. This is how our application will Log //the error messages received from the process. static void Logger(string s) { Console.WriteLine(s); } static void Main() { ProcessToRun myProc = new ProcessToRun(); //Create a LogHandler delegate and tell it to use the //static function we created. // This delegate will be passed to the Process() function. ProcessToRun.LogHandler myHandler = new ProcessToRun.LogHandler(Logger); myClass.Process(myHandler); } } } What if we want to change this application to log messages to a file? Leave the ProcessToRun class alone, delete the application class, add ‘using System.IO;’ to the beginning of the file, and add the following code: public class FileLogger { FileStream fs; StreamWriter sw; public FileLogger(string filename) { fs = new FileStream(filename, FileMode.Append); sw = new StreamWriter(fs); } //Method used within delegate to write message public void LogMessage(string s) { //sw.WriteLine(s); <- this would log to the file, // but for simplicity use this line: Console.WriteLine(s); } public void Close() { sw.Close(); fs.Close(); } } public class application { static void Main() { FileLogger fl = new FileLogger(“process.log”); ProcessToRun myProc = new ProcessToRun(); //Make a delegate and point it towards the LogMessage method ProcessToRun.LogHandler myLogger = new ProcessToRun.LogHandler(fl.LogMessage); myProc.Process(myLogger); fl.Close(); } } What we’ve done is successfully created a separate way to log messages, without changing any code from our previous ProcessToRun class. Obviously this saves a lot of time in a multiple developer setting; now whenever something needs to be logged, we have a simplified way of doing that. You might be saying to yourself now, this seems like a lot of work when I could have just created another project to do the same thing much more simply. Yes that’s true, but with multicasting, we can tell a delegate to call more than one method. Basically with multicasting, if you not only want to log messages to a file, but also to the screen, you just tell the delegate, "Hey I have another method for you to call." Change your application class in the following way: public class application { static void Logger(string s) { Console.WriteLine(s) } static void Main() { FileLogger fl = new FileLogger("process.log"); ProcessToRun myProc = new ProcessToRun(); //Make a delegate, point it towards the LogMessage method for //both the FileLogger object and this static class. ProcessToRun.LogHandler myLogger = null; myLogger += new ProcessToRun.LogHandler(Logger); myLogger += new ProcessToRun.LogHandler(fl.LogMessage); myProc.Process(myLogger); fl.Close(); } } When you run this you should see two lines for each message that is being “logged”. It should now be obvious how powerful the delegate is. Events: Now what we can do is create an event. This event will occur in the following way: We create a delegate and an event. The event uses the delegates identifier and creates one of those all too familiar OnXXXX methods (which we will create). This will file the event when any method calls our designated OnXXXX method. In turn, the event will call the delegates and the methods associated with those delegates start begin executing. To see how this works, implement and run the following: using System; using System.IO; namespace Examples.410.EventHandler { //Publisher public class ProcessToRun { // Define a delegate named LogHandler, which encapsulates any // method that takes a string and returns nothing public delegate void LogHandler(string message); // Define an Event based on the above Delegate public event LogHandler Log; // Instead of having the Process() function take a delegate // as a parameter, we've declared a Log event. Call the Event, // using the OnXXXX Method, where XXXX is the Event name. public void Process() { OnLog("Process() begin"); OnLog("Process() end"); } // By Default, create an OnXXXX Method, to call the Event protected void OnLog(string message) { if (Log != null) Log(message); } } // The FileLogger class merely encapsulates the file I/O public class FileLogger { FileStream fs; StreamWriter sw; // Constructor public FileLogger(string filename) { fs = new FileStream(filename, FileMode.Append); sw = new StreamWriter(fs); } // Member Function which is used in the Delegate public void Logger(string s) { sw.WriteLine(s); } public void Close() { sw.Close(); fs.Close(); } } // Subscriber // It's now easier and cleaner to merely add instances // of the delegate to the event, instead of having to // manage things ourselves public class application { static void Logger(string s) { Console.WriteLine(s); } static void Main(string[] args) { FileLogger fl = new FileLogger("process.log"); ProcessToRun myProc = new ProcessToRun(); // Subscribe the methods Logger and fl.Logger myProc.Log += new ProcessToRun.LogHandler(Logger); myProc.Log += new ProcessToRun.LogHandler(fl.Logger); // The Event will now be triggered in the Process() Method myProc.Process(); fl.Close(); } } } Only a couple of things have changed in our application. It’s a little cleaner because we can add a method to the Log event, by using our object to add a new LogHandler delegate and give it the name of the method we want to invoke. This all works, but what we have left out are the parameters to the event. The .NET Framework requires that events take two parameters and return void. The first parameter is the source of the event (publishing object) and the second is an object derived from EventArgs. Events are properties of the class publishing the event and the keyword event controls how the event property is accessed by the subscribing classes. It might seem pointless to go through all of this to print a couple of lines on the screen, but think about what we are doing. We can now subscribe any method to an event. If one were so inclined, they could create a way to print out the time every second showing updates. It may seem foolish to use events to accomplish this, but if you have multiple systems that depend upon an event firing at precise increments, this could very easily make all of these systems fire concurrently. Finally, we can split our one large file up into multiple files or even multiple projects. The benefit of doing this is that our ProcessToRun doesn’t care what’s being done in the methods that it calls when the delegate fires, it just knows that it has to call them. The same goes for those methods that know nothing of how they are being called, just that they now can perform their task. This is extremely helpful in an environment where developers may be working on separate projects, but have one common event that ties them together. All code and examples in this project have been adapted from reference 1 below. This site also offers good, alternative explanations. References: 1. Delegates and Events in C# / .NET, Akadia AG, Fichtenweg 10, 3672 Oberdiessbach, http://www.akadia.com/html/publications.html 2. Delegates Tutorial - MSDN Library http://msdn2.microsoft.com/en-us/library/aa288459(VS.71).aspx 3. How to Properly Handle Cross-thread Events and Update a Label with BeginInvoke and BackgroundWorker, http://www.codeproject.com/KB/cs/Cross_thread_Events.aspx. Project Activity Implement the four example programs of this project and make the indicated modifications. Submit hard-copy of the source code with your name, the project example name, course name/number and date in a comment header of each program. NoDelegate SimpleDelegate LogHandlerSimple EventHandler