C# Delegates

.Delegates
C# 1.0 - Named Methods | C# 2.0 - Anonymous Methods | C# 3.0 - Lambda Expressions | Synchronous Method Call | Simple Asynchronous Method Call | Polling Asynchronous Method Call | Asynchronous Method Call with Callback | Multicast Delegates | Generic Delegate Types | Built-In Generic Delegates | C# Events | .NET Events

A .NET delegate is defined as:

1A delegate is a type that represents references to methods with a particular parameter list and return type. When you instantiate a delegate, you can associate its instance with any method with a compatible signature and return type. You can invoke (or call) the method through the delegate instance.

In .NET delegates are classes whose instantiated object is given the responsibility for executing a method. The .NET delegates have built-in support for multicasting and asynchronous method invocation. The delegates are "type-safe", which eliminates the problem of incompatible arguments or return types which can cause runtime bugs or crashes. Delegates have the following properties:

  1. Delegate types are implicitly sealed, so it is not permissible to derive any type from a delegate type.
  2. Delegates decouple the class that declares the method call from the class that actually invokes the method.



Delegate Objects

An instantiated delegate is an object containing a reference to a method with its associated information. That is, a delegate object maintains three pieces of information:

  1. The address of the target method it calls.
  2. The return type of the target method.
  3. The parameters of the target method.

A delegate object encapsulates the information needed to perform a method call. When you assign a method to a delegate, you change the address of the target method stored inside the delegate (#1 above). Any method from an accessible class or struct whose signature matches the delegate's signature (with certain variances allowed) can be assigned to the delegate. These variances are defined as:

  1. Covariance - permits a method to have a return type that is more derived than the one defined in the delegate type.
  2. Contravariance - permits a method to have parameter types that are less derived than those defined in the delegate type.

Once a method has been assigned to a delegate object, the object can be passed as parameters to methods or stored in properties.



Delegate Declarations and Uses

"When targeting .NET 3.5 or later, the preferred way to create delegates is with lambda expressions."

Delegates can be declared and used in various ways. A simple use of a delegate is to assign it different method references, depending upon the desired logic to be performed. This allows a delegate to perform any method whose signature string matches the delegate (with certain variances). For example a delegate may be used to call an "add" method and then have its reference changed to call a "subtract" method. This simple way of using delegates is shown in the following example code which illustrates how the declaration of delegates has evolved in C#. However a more common, and complex, use of delegates is to execute methods asynchronously and to use methods as callbacks. One of the most common uses of delegates is in event-driven programming as event-handlers to respond to graphical controls, such as when a button is pressed.

The declaration of delegates has evolved with C#. This has lead to several different ways for declaring delegates in C#:

  1. In C# 1.0, named methods were the only way a delegate could be declared.
    See example code: C# 1.0 - Named Methods below.

  2. In C# 2.0, anonymous methods were introduced as a way to create unnamed inline statement blocks that are executed by the delegate.
    See example code: C# 2.0 - Anonymous Methods below.

  3. In C# 3.0, lambda expressions were introduced which were similar in concept to anonymous methods, but are more expressive and concise.
    See example code: C# 3.0 - Lambda Expressions below.

Also introduced in C# 2.0 was a feature called method group conversion which simplified the syntax used to assign a method to a delegate. The method group conversion allows the assignment of a method to a delegate, with out using the new keyword or explicitly invoking the delegate's constructor:

  • In C# 1.0 method assignment to a delegate had to use the new keyword:

    myDelegate = new TheDelegate(TheAddMethod);
    


  • In C# 2.0 method assignment to a delegate could use method group conversion to simplify the method assignment syntax:

    myDelegate = TheAddMethod;
    

Note: Anonymous methods and lambda expressions are known collectively as anonymous functions. Also, while you can not explicitly derive from a delegate type, when you define a delegate, the compiler is actually defining a new class which inherits from System.MulticastDelegate.




Delegate Invocation



"When a delegate runs a method asynchronously, it uses a thread from the thread pool. Threads in the thread pool are background threads which will not keep the application alive after all foreground threads have exited. The control thread needs to verify the async method has finished to ensure its full execution, or intentionally abort the async method after a period of time (timeout)."

The method assigned to a delegate can be executed synchronously or asynchronously:

  • Synchronous
    • The delegated method runs on the thread that owns the control's underlying window handle. This blocks the calling thread until the delegated method is finished. This blocking action of synchronous calls assures that the called code completes before the calling code exits.
    • Use the Invoke method to perform synchronous execution of the delegated method.
    • Invoke is the default delegate method, so just specifying the delegate name will cause synchronous execution.
    • See example code: Synchronous Method Call below.
  • Asynchronous
    • The delegated async method runs on a pool thread, which does not block the control thread. However, to ensure full execution of the async method, verification of completion of the async method is required before exiting the control thread. The pool thread itself will not keep the application alive.
    • Use the BeginInvoke method to initiate an asynchronous call. BeginInvoke does not wait for the asynchronous call to complete.
    • Optionally, use the EndInvoke method to retrieve the return value of the asynchronous operation obtained thru the IAsyncResult interface. If the asynchronous operation has not completed, EndInvoke will block until the result is available.
    • The IAsyncResult interface stores state information for an asynchronous operation and provides a synchronization object to allow threads to be signaled when the operation completes.
    • Since delegated async method runs on a pool thread (background thread), it can not keep the application alive. If the control thread finished before the async method has completed, the background thread will also die, leaving work unfinished by the asynch method. The control method should always be sure the asynch method has finished, or intentionally abort the async method (timeout), before exiting the control thread.

    • Asynchronous - Potential Block: Call async method, do some work on current thread, then block till async method finishes.
      • Use a delegate to call the async method. Then do some work on current thread, then wait for async method to finish (via EndInvoke method).
      • This is the simplest way, using a delegate, to execute a method asynchronously. After calling the async method, if the current thread has more work to do than the async method, then the current thread will not be blocked. However if the async method has more work to do, then the current thread will be blocked until the async method finishes.
      • See example code: Simple Asynchronous Method Call below.
    • Asynchronous - Polling: Call async method, then cycle between periodically checking if the async method has finished and doing more work on the current thread.
      • Use a delegate to call the async method. Then do some work on current thread. Periodically check if async method has finished, if not do some more work on current thread. Continue this cycle until:
        1. The async method is finished, or
        2. All the work for current thread is completed (then block current thread until the async method is finished).
      • See example code: Polling the Asynchronous Method Call below.
    • Asynchronous - Callback: Do work on current thread until callback notifies that the async method has finished.
      • Use a delegate to call the async method, continue to do work on current thread until the callback method notifies you that the async method has finished (or async method times-out), or the work on the main thread is finished (then wait for async method to finish).
      • See example code: Asynchronous Method with Callback below.


Multicast Delegates



"A Multicast Delegate contains more than one method reference in its invocation list."

A Delegate object has an invocation list containing the references to one or more methods. When the delegate is invoked, the methods in the invocation list are called synchronously in the order in which they appear in the list. When the invocation list contains more than one method reference, it is referred to as a Multicast Delegate.

  • A method reference can be added to the invocation list by using the "+" and "+=" operators.
  • A method reference can be removed from the invocation list by using the "-" and "-=" operators.
  • Only methods matching the method signatures defined by the delegate ... can have their reference added to the invocation list.
  • If the delegated methods have a non-void return type, the value of the last method invoked is returned.
  • All .NET delegates have the ability to be multicast delegates.
  • Delegate.GetInvocationList() returns the invocation list of the delegate.
  • See example code: Multicast Delegates below.




Generic Delegate Types



"Generics provide a higher level of reusability for delegates."

Generic delegates have type parameters that allow a delegate to work with any data type. Generics provide a higher level of reusability for delegates. When using generic delegates, the compiler is able to infer the type of any parameter and the return type. Some recommendations for creating generic delegates are:

  • As of C# 4.0, the delegate parameters can be defined with variance. The following variance recommendations allow conversions to work naturally by respecting the inheritance relationships between types:
    • A type parameter used on the delegate return value should be marked as out covariant.
    • Type parameters used as delegate parameters should be marked as in contravariant.
  • When it is possible, the use of the Built-In Generic Delegates is recommended (see next section). These built-in generic delegates include: Action, Func, and Predicate.
  • See example code: Generic Delegates below.




Built-in Generic Delegates



"Two of the most commonly used built-in generic delegates are Func<T> and Action<T>. Their underlying relationship with lambda expressions allow the passing of method parameters without explicitly creating a delegate."

The .NET framework has defined several generic delegates in the System namespace. Two of the most commonly used are the Func<T> and Action<T> generic delegates because of their relationship with lambda expressions. This relationship allows lambda expressions to be passed to method parameters of the appropriate type without explicitly creating a delegate. The following coding example of the Action<T> delegate shows this relationship with the List<T>.ForEach() method. Also, many LINQ standard query operators accept Func<T> arguments to take advantage of this relationship. Built-in generic delegates in the .NET framework include:

  • Action - for some method that just does something and returns no output.
  • Comparison - for some method that compares two objects of the same type and returns an int to indicate order.
  • Converter - for some method that transforms Obj A into equivalent Obj B.
  • EventHandler - for response/handler to an event raised by some object given some input in the form of an event argument.
  • Func - for some method that takes some parameters, computes something and returns a result.
  • Predicate - for some method that evaluates input object against some criteria and return pass/fail status as bool.

If not taking advantage of the special relationship with the lambda expressions, some will argue that a more meaningful name of a custom generic delegate will make the code more readable. Additionally there are cases where the built-in generic delegates will not work for the design pattern. Those cases include methods with out, ref, or params parameters, or recursive method signatures.

See example code: Built-in Generic Delegates below.




C# Events



"In C#, delegates are the basis for the event system. Events are a layer on top of delegates which make it easier to securely implement the Publish-Subscribe pattern. Events are nicely integrated with Visual Studio."

Events are a language feature used to formalize an implementation of the Observer design pattern called Publish-Subscribe. Publish-Subscribe has only one publisher object which will raise a new event. Then one or more subscriber objects will be notified when the event occurs. Once notified, the subscriber objects will perform an action (i.e. they will "handle the event").

In C#, delegates are the basis for the event system. Events are a layer on top of delegates which make it easier to securely implement the Publish-Subscribe pattern. In fact, the Event keyword is used to create a special kind of multicast delegate which can only be invoked from within the class that declared the event. Further, from outside the class that declared the event (i.e. the Publisher), subscribers can on be added to the delegate's invocation list through event accessors (similar to property accessors). Subscribers can not call the Publisher directly, nor can they access the Publisher's internal members.

However, if the default restrictions of an event are too restrictive, they can be lessened with additional coding. It is sometimes deemed appropriate to invoke an event from classes that are derived from the Publisher. This can be done by creating a protected method for the event. For even greater flexibility the method can be declared as virtual which will allow the derived class to override the method with its own implementation. Additionally you can also implement custom versions of the event accessors. This would allow an object to relay an event instead being the generator of an event (i.e. to act as a proxy).

Events have the following properties:

  • The publisher determines when an event is raised; the subscribers determine what action is taken in response to the event.
  • An event can have multiple subscribers. A subscriber can handle multiple events from multiple publishers.
  • Events that have no subscribers are never raised.
  • Events are commonly used to signal user actions such as button clicks or menu selections in graphical user interfaces.
  • For events with multiple subscribers, by default the subscriber methods (event handlers) are invoked synchronously when an event is raised. To invoke events asynchronously, see Implementing the Event-based Asynchronous Pattern.
  • See example code: C# Events below.




.NET Events



"While the C# language allows events to use any delegate type, the .NET Framework has some stricter guidelines for creating events."

For compatibility with the .NET Framework, events should be published based on the EventHandler delegate. A popular, and recommended option, is to use the generic version called EventHandler<TEventArgs>. The advantage of using EventHandler<TEventArgs> is that you do not need to code your own custom delegate if your event generates event data. You simply provide the type of the event data object as the generic parameter.

If you are passing data with your event, you need to declare an class for passing the data. The class should be declared at a scope that is visible to both the Publisher and Subscribers. The class should be derived from System.EventArgs . If you are absolutely sure the event will never need to carry any data to the event handling method, you can use the EventArgs type directly.

If you need to create delegate for the event (i.e. you are NOT using EventHandler), then it should take exactly two parameters with a void return type:

  1. The first parameter is the "object source" parameter indicating the source of the event, is called "sender".
  2. The second parameter is the class used for passing data to the event (#2 above), and is called "e".

Example : public delegate void CustomEventHandler(object sender, CustomEventArgs e);

Use a protected virtual method to raise each event. (Only applicable to nonstatic events on unsealed classes, not to structs, sealed classes, or static events). The method provides a way for a derived class to handle the event using an override. Overriding is a more flexible, faster, and more natural way to handle base class events in derived classes. The name of the method should start with "On" and be followed with the name of the event. Use one parameter named e, which should be typed as the event argument class.

See example code: C# Event Following .NET Standards below.




C# 1.0 - Named Methods

In C# 1.0, named methods were the only way a delegate could be declared.

/***************************************************************
* C# 1.0 - Delegates could only be created with named methods. *
****************************************************************/
namespace DelegateNamedMethod
{
    class Program
    {
        delegate int TheDelegate(int x, int y);
        public static int TheAddMethod(int x, int y) { return x + y; }
        public static int TheSubMethod(int x, int y) { return x - y; }

        static void Main()
        {
            // Create delegate set to add method
            TheDelegate myDelegate = new TheDelegate(TheAddMethod);
            System.Console.WriteLine("5 + 4 = {0}", myDelegate(5,4)); // Prints: 5 + 4 = 9

            // Change delegate to subtract method
            myDelegate = TheSubMethod;
            System.Console.WriteLine("5 - 4 = {0}", myDelegate(5, 4)); // Prints: 5 - 4 = 1
        }
    }
}

Back | Top




C# 2.0 - Anonymous Methods

In C# 2.0, anonymous methods were introduced as a way to create unnamed inline statement blocks that are executed by the delegate.

/***************************************************************
* C# 2.0 - Delegates could be created with anonymous methods.  *
****************************************************************/
namespace DelegateAnonymousMethod
{
    class Program
    {
        delegate int TheDelegate(int x, int y);

        static void Main()
        {
            // Create delegate with inline code (anonymous method) for add
            TheDelegate myDelegate = delegate(int x, int y) { return x + y; };
            System.Console.WriteLine("5 + 4 = {0}", myDelegate(5, 4)); // Prints: 5 + 4 = 9

            // Change delegate inline code (anonymous method) for subtract
            myDelegate = delegate(int x, int y) { return x - y; };
            System.Console.WriteLine("5 - 4 = {0}", myDelegate(5, 4)); // Prints: 5 - 4 = 1
        }
    }
}

Back | Top




C# 3.0 - Lambda Expressions

In C# 3.0, lambda expressions were introduced which were similar in concept to anonymous methods, but are more expressive and concise. When targeting .NET 3.5 or later, the preferred way to create delegates is with lambda expressions.

/***************************************************************
* C# 3.0 - Delegates could be created with lambda expressions. *
****************************************************************/
namespace DelegateLambda
{
    class Program
    {
        delegate int TheDelegate(int x, int y);

        static void Main()
        {
            // Create delegate with inline code (lambda expression) for add
            TheDelegate myDelegate = (x, y) => { return x + y; };
            System.Console.WriteLine("5 + 4 = {0}", myDelegate(5, 4)); // Prints: 5 + 4 = 9

            // Change delegate inline code (lambda expression) for subtract
            myDelegate = (x, y) => { return x - y; };
            System.Console.WriteLine("5 - 4 = {0}", myDelegate(5, 4)); // Prints: 5 - 4 = 1
        }
    }
}

Back | Top


Synchronous Method Call

.Synchronous Method Call via Delegate


Synchronous Method Call

The delegated method runs on the thread that owns the control's underlying window handle. This blocks the control thread until the delegated method is finished.

In this example there is 5 seconds of work on the control thread and 10 seconds of work for the delegated method. All the work is performed on the control thread (thread 1). Total run time is slightly over 15 seconds.

using System;
using System.Threading;
using System.Diagnostics;

namespace DelegateSynchronous
{
    delegate void TheDelegate(string s);

    static class StatClass
    {
        public static void DelMethod(TheDelegate parmDelegate)
        {
            System.Console.WriteLine("DelMethod: Started on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            /*****************************
             * Synchronous Delegate Call *
             *****************************/
            // Line below same as: parmDelegate.Invoke("I am the Delegate Parameter - Synchronous");
            parmDelegate("I am the Delegate Parameter - Synchronous");

            // 5 Seconds of Work
            for (int i = 0; i < 5; i++)
            {
                System.Console.WriteLine("*** Doing Work on on thread id: {0} at: {1}",
                                          Thread.CurrentThread.ManagedThreadId,
                                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
                System.Threading.Thread.Sleep(1000);
            }
            System.Console.WriteLine("DelMethod: Ended on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }
    }

    class Program
    {
        static void Header()
        {
            System.Console.WriteLine("*********************************************************");
            System.Console.WriteLine("***   Synchronous Method Call with Passed Delegate    ***");
            System.Console.WriteLine("*********************************************************\n");
        }

        static void TheMethod(string s)
        {
            System.Console.WriteLine("TheMethod: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            Console.WriteLine(s);

            // 10 Seconds of Work
            for (int i = 0; i < 10; i++)
            {
                System.Console.WriteLine("*** Doing Work on on thread id: {0} at: {1}",
                                          Thread.CurrentThread.ManagedThreadId,
                                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
                System.Threading.Thread.Sleep(1000);
            }
            System.Console.WriteLine("TheMethod: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }


        static void Main()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();
            Program.Header();
            System.Console.WriteLine("Main: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));

            // Define the delegate
            TheDelegate myDelegate1 = new TheDelegate(TheMethod);

            // Invoke the delegate
            StatClass.DelMethod(myDelegate1);

            System.Console.WriteLine("Main: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            myStopWatch.Stop();
            System.Console.WriteLine("\nTotal run time is: {0}\n", myStopWatch.Elapsed);
        }
    }
}

Back | Top



Simple Asynchronous Method Call

.Simple Asynchronous Method Call via Delegate


Simple Asynchronous Method Call

This is the simplest way to use a delegate to execute an async method. Use a delegate to call the async method. Then do some work on current thread, then wait for async method to finish (via EndInvoke method).

In this example there is 5 seconds of work on the control thread (thread 1) and 10 seconds of work for the thread running the delegated async method (thread 3). Some of the work is being performed by both threads at the same time. The control thread finishes first and waits for the async thread to finish. Total run time is slightly over 10 seconds.

using System;
using System.Threading;
using System.Diagnostics;

namespace DelegateAsynchronous
{
    delegate void TheDelegate(string s);

    static class StatClass
    {
        public static void DelMethod(TheDelegate parmDelegate)
        {
            System.Console.WriteLine("DelMethod: Started on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            /******************************
             * Asynchronous Delegate Call *
             ******************************/
            IAsyncResult asyncRes = parmDelegate.BeginInvoke("I am the Delegate Parameter - Asynchronous", null, null);

            // 5 Seconds of Work
            for (int i = 0; i < 5; i++)
            {
                System.Console.WriteLine("*** Doing Work on on thread id: {0} at: {1}",
                                          Thread.CurrentThread.ManagedThreadId,
                                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
                System.Threading.Thread.Sleep(1000);
            }

            parmDelegate.EndInvoke(asyncRes);
            System.Console.WriteLine("DelMethod: Ended on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }
    }

    class Program
    {
        static void Header()
        {
            System.Console.WriteLine("*********************************************************");
            System.Console.WriteLine("***   Asynchronous Method Call with Passed Delegate   ***");
            System.Console.WriteLine("*********************************************************\n");
        }

        static void TheMethod(string s)
        {
            System.Console.WriteLine("TheMethod: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            Console.WriteLine(s);

            // 10 Seconds of Work
            for (int i = 0; i < 10; i++)
            {
                System.Console.WriteLine("*** Doing Work on on thread id: {0} at: {1}",
                                          Thread.CurrentThread.ManagedThreadId,
                                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
                System.Threading.Thread.Sleep(1000);
            }
            System.Console.WriteLine("TheMethod: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }


        static void Main()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();
            Program.Header();
            System.Console.WriteLine("Main: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));

            // Define the delegate
            TheDelegate myDelegate1 = new TheDelegate(TheMethod);

            // Invoke the delegate
            StatClass.DelMethod(myDelegate1);

            System.Console.WriteLine("Main: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            myStopWatch.Stop();
            System.Console.WriteLine("\nTotal run time is: {0}\n", myStopWatch.Elapsed);
        }
    }
}

Back | Top



Polling Asynchronous Method Call

.Polling Asynchronous Method Call via Delegate


Polling Asynchronous Method Call

Use a delegate to call the async method. Then do some work on current thread. Periodically check if async method has finished, if not do some more work on current thread. Continue this cycle until async method is finished or times out.

In this example there is 5 seconds of work on the control thread (thread 1) and 10 seconds of work for the thread running the delegated async method (thread 3). Every 0.5 seconds the current thread checks if the async method is finished. More work could be performed on the current thread during these intervals. The total run time is slightly over 10.5 seconds.

using System;
using System.Threading;
using System.Diagnostics;

namespace DelegatePolling
{
    delegate void TheDelegate(string s);

    static class StatClass
    {
        public static void DelMethod(TheDelegate parmDelegate)
        {
            System.Console.WriteLine("DelMethod: Started on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            /******************************
             * Asynchronous Delegate Call *
             ******************************/
            IAsyncResult asyncRes = parmDelegate.BeginInvoke("I am the Delegate Parameter - Asynchronous", null, null);

            // 5 Seconds of Work
            for (int i = 0; i < 5; i++)
               System.Threading.Thread.Sleep(1000);
           
            Console.Write("\nWaiting for TheMethod work to finish ");

            // Poll the delegate invoked
            while (asyncRes.IsCompleted == false)
            {
                Console.Write(".");
                Thread.Sleep(500);
            }

            parmDelegate.EndInvoke(asyncRes);
            System.Console.WriteLine("\n\nDelMethod: Ended on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }
    }

    class Program
    {
        static void Header()
        {
            System.Console.WriteLine("*********************************************************");
            System.Console.WriteLine("***       Polling the Asynchronous Method Call       ***");
            System.Console.WriteLine("*********************************************************\n");
        }

        static void TheMethod(string s)
        {
            System.Console.WriteLine("TheMethod: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            Console.WriteLine(s);

            // 10 Seconds of Work
            for (int i = 0; i < 10; i++)
               System.Threading.Thread.Sleep(1000);
        }


        static void Main()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();
            Program.Header();
            System.Console.WriteLine("Main: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));

            // Define the delegate
            TheDelegate myDelegate1 = new TheDelegate(TheMethod);

            // Invoke the delegate
            StatClass.DelMethod(myDelegate1);

            System.Console.WriteLine("Main: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            myStopWatch.Stop();
            System.Console.WriteLine("\nTotal run time is: {0}\n", myStopWatch.Elapsed);
        }
    }
}

Back | Top



Asynchronous Method Call with Callback

.Asynchronous Method via Delegate with Callback


Asynchronous Method Call with Callback

Use a delegate to call the async method, continue to do work on current thread until the callback method notifies you that the async method has finished (or async method times-out), or the work on the main thread is finished (then wait for async method to finish).

In this example there is 5 seconds of work on the control thread (thread 1) and 10 seconds of work for the thread running the delegated async method (thread3). There is an additional 3 seconds of work in the callback method (thread 3). The current thread finished its work first. The current thread then waits for the async method to finish or time-out. If the async method finishes, it causes the execution of the callback method. The current thread continues to wait until the callback method is finished or times-out. The total run-time is a little over 14 seconds.

using System;
using System.Threading;
using System.Diagnostics;


namespace DelegateCallback
{
    delegate void TheDelegate(string s);

    static class StatClass
    {
        public static bool AsychMethodIsFinished { get; set; }
        public static bool CallBackIsFinished { get; set; }

        static StatClass() { CallBackIsFinished = AsychMethodIsFinished = false; }
       
        static void CallWhenFinished(IAsyncResult asyncRes)
        {
            System.Console.WriteLine("CallWhenFinished: Started on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            System.Threading.Thread.Sleep(3000);

            // Passed original delegate in the asyncState parameter
            TheDelegate parmDelegate = (TheDelegate)asyncRes.AsyncState;
            parmDelegate.EndInvoke(asyncRes);

            Console.WriteLine("Asynchronous Call Completed.");
            System.Console.WriteLine("CallWhenFinished: Ended on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            CallBackIsFinished = true;
        }


        public static void DelMethod(TheDelegate parmDelegate)
        {
            System.Console.WriteLine("DelMethod: Started on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            /******************************
             * Asynchronous Delegate Call *
             ******************************/
            // Send delegate object as state parameter (last parm)
            IAsyncResult asyncRes = parmDelegate.BeginInvoke("I am the Delegate Parameter - Asynchronous", new AsyncCallback(CallWhenFinished), parmDelegate);

            // 5 Seconds of Work
            for (int i = 0; i < 5; i++)
                System.Threading.Thread.Sleep(1000);

//            parmDelegate.EndInvoke(asyncRes);
            System.Console.WriteLine("\nDelMethod: Ended on thread id: {0} at: {1}",
                                      Thread.CurrentThread.ManagedThreadId,
                                      DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
        }
    }

    class Program
    {
        static void Header()
        {
            System.Console.WriteLine("*********************************************************");
            System.Console.WriteLine("***        Asynchronous Method with Callback         ***");
            System.Console.WriteLine("*********************************************************\n");
        }

        static void TheMethod(string s)
        {
            System.Console.WriteLine("TheMethod: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            Console.WriteLine(s);

            // 10 Seconds of Work
            int i;
            for (i = 0; i < 10; i++)
                System.Threading.Thread.Sleep(1000);

            System.Console.WriteLine("TheMethod: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            StatClass.AsychMethodIsFinished = true;
        }


        static void Main()
        {
            Stopwatch myStopWatch = new Stopwatch();
            myStopWatch.Start();
            Program.Header();
            System.Console.WriteLine("Main: Started on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));

            // Define the delegate
            TheDelegate myDelegate1 = new TheDelegate(TheMethod);

            // Invoke the delegate
            StatClass.DelMethod(myDelegate1);

            int waitSeconds = 0;
            while ((!StatClass.CallBackIsFinished) && (waitSeconds < 30))
            {
                if (!StatClass.AsychMethodIsFinished)
                    Console.WriteLine("Main: Waiting for asych method to finish");
                else
                    Console.WriteLine("Main: Waiting for callback to finish");
               waitSeconds++;
               System.Threading.Thread.Sleep(1000);
            }
            System.Console.WriteLine("Main: Ended on thread id: {0} at: {1}",
                          Thread.CurrentThread.ManagedThreadId,
                          DateTime.Now.ToString("hh:mm:ss tt", System.Globalization.DateTimeFormatInfo.InvariantInfo));
            myStopWatch.Stop();
            System.Console.WriteLine("\nTotal run time is: {0}\n", myStopWatch.Elapsed);
        }
    }
}

Back | Top



Multicast Delegates

.Multicast Delegate


Multicast Delegate

Any .NET delegate can be a multicast delegate by adding more than one delegate to its invocation list. All the methods represented by the delegates in the invocation list are executed synchronously in the order in which they appear in the list.

namespace DelegateMulticast
{
    delegate void TheDelegate();
    class DelegateClass
    {
        public DelegateClass()
        {
           // Create delegate instance and assign method.
           TheDelegate myDelegate = TheFirstMethod;

           // Call delegate
           myDelegate();

           System.Console.WriteLine("------------------------------");
           // Change delegate invocation list
           myDelegate += TheSecondMethod;
           myDelegate += TheThirdMethod;
           myDelegate -= TheSecondMethod;

           // Call delegate again
           myDelegate();

           // Print method names from invocation list                       
           System.Console.WriteLine("------------------------------");
           System.Console.WriteLine("Method Names in Invocation List:");
           foreach(TheDelegate del in myDelegate.GetInvocationList())
           { System.Console.WriteLine(del.Method.Name); }
           System.Console.WriteLine();

        }
        public void TheFirstMethod()  { System.Console.WriteLine("First Method");  }
        public void TheSecondMethod() { System.Console.WriteLine("Second Method"); }
        public void TheThirdMethod()  { System.Console.WriteLine("Third Method");  }
        static void Header()
        {
            System.Console.WriteLine("******************************");
            System.Console.WriteLine("***   Multicast Delegate   ***");
            System.Console.WriteLine("******************************\n");
        }

        static void Main()
        {
            Header();
            new DelegateClass();
        }
    }
namespace DelegateMulticast
{
    delegate void TheDelegate();
    class DelegateClass
    {
        public DelegateClass()
        {
           // Create delegate instance and assign method.
           TheDelegate myDelegate = TheFirstMethod;

           // Call delegate
           myDelegate();

           System.Console.WriteLine("------------------------------");
           // Change delegate invocation list
           myDelegate += TheSecondMethod;
           myDelegate += TheThirdMethod;
           myDelegate -= TheSecondMethod;

           // Call delegate again
           myDelegate();

           // Print method names from invocation list                       
           System.Console.WriteLine("------------------------------");
           System.Console.WriteLine("Method Names in Invocation List:");
           foreach(TheDelegate del in myDelegate.GetInvocationList())
           { System.Console.WriteLine(del.Method.Name); }
           System.Console.WriteLine();

        }
        public void TheFirstMethod()  { System.Console.WriteLine("First Method");  }
        public void TheSecondMethod() { System.Console.WriteLine("Second Method"); }
        public void TheThirdMethod()  { System.Console.WriteLine("Third Method");  }
        static void Header()
        {
            System.Console.WriteLine("******************************");
            System.Console.WriteLine("***   Multicast Delegate   ***");
            System.Console.WriteLine("******************************\n");
        }

        static void Main()
        {
            Header();
            new DelegateClass();
        }
    }
}

Back | Top



Generic Delegates

.Custom Generic Delegate


Custom Generic Delegate

Generic delegates have type parameters that allow a delegate to work with any data type. Generics provide a higher level of reusability for delegates.

namespace DelegateGeneric
{
    class Program
    {
        // TheDoubleDelegate can only be used with double types
        delegate double TheDoubleDelegate(double x, double y);

        // TheStringDelegate can only be used with string types
        delegate string TheStringDelegate(string x, string y);

        // TheGenericDelegate can be used with ANY TYPE
        delegate TResult TheGenericDelegate<in T1, in T2, out TResult>(T1 x, T2 y);

        static void Header()
        {
            System.Console.WriteLine("*******************************************************");
            System.Console.WriteLine("***    Generic Delegate: Supports all data types    ***");
            System.Console.WriteLine("*******************************************************\n");
        }

        static void Main()
        {
            double arg1d = 5.0;
            double arg2d = 4.0;
            string arg1s = "K";
            string arg2s = "H";

            Header();

            // Use "TheDoubleDelegate" to create delegate for DOUBLE type add
            System.Console.WriteLine("---   Use TheDoubleDelegate for DOUBLE data types   ---\n");
            TheDoubleDelegate myDoubleDelegate = (x, y) => { return x + y; };
            System.Console.WriteLine("{0:N} + {1:N} = {2:N}", arg1d, arg2d, myDoubleDelegate(arg1d, arg2d)); // Prints: 5.0 + 4.0 = 9.0

            // Use "TheStringDelegate" to create delegate for STRING type add
            System.Console.WriteLine("\n---   Use TheStringDelegate for STRING data types   ---\n");
            TheStringDelegate myStringDelegate = (x, y) => { return x + y; };
            System.Console.WriteLine("{0:N} + {1:N} = {2:N}", arg1s, arg2s, myStringDelegate(arg1s, arg2s)); // Prints: K + H = KH

            /****************************************************************
             * Use Generic Delegate (TheGenericDelegate) for any data types *
             ****************************************************************/
            System.Console.WriteLine("\n--- Use Generic Delegate for both DOUBLE and STRING ---\n");

            // Use "TheGenericDelegate" to create delegates for both DOUBLE and STRING type adds
            TheGenericDelegate<double, double, double> myGenericDoubleDelegate = (x, y) => { return x + y; };
            System.Console.WriteLine("{0:N} + {1:N} = {2:N}", arg1d, arg2d, myGenericDoubleDelegate(arg1d, arg2d)); // Prints: 5.0 + 4.0 = 9.0

            TheGenericDelegate<string, string, string> myGenericStringDelegate = (x1, y1) => { return x1 + y1; };
            System.Console.WriteLine("{0} + {1} = {2}\n", arg1s, arg2s, myGenericStringDelegate(arg1s, arg2s)); // Prints: K + H = KH
        }
    }
}

Back | Top



Built-in Generic Delegates

.Built-in Func and Action Generic Delegates


Built-in Func and Action Generic Delegates

The .NET framework has defined several generic delegates in the System namespace. Two of the most commonly used are the Func and Action generic delegates because of their relationship with lambda expressions. This relationship allows lambda expressions to be passed to method parameters of the appropriate type without explicitly creating a delegate.

using System;
using System.Collections.Generic;

namespace BuiltinDelegates
{
    class Program
    {
        // No delegates defined because using built-in FUNC and ACTION generic delegates.

        static void Header()
        {
            System.Console.WriteLine("************************************************************");
            System.Console.WriteLine("***     Built-in FUNC and ACTION Generic Delegates       ***");
            System.Console.WriteLine("************************************************************\n");
        }

        static void Main()
        {
            Header();

            /***************************************************************************
            * Built-in FUNC delegate: used to add two double values and return result. *
            ****************************************************************************/
            Console.WriteLine("\n---  Func<T> used to perform the addition of two values ----\n");
            double arg1d = 5.0;
            double arg2d = 4.0;
            //TheGenericDelegate<double, double, double> myGenericDoubleDelegate = (x, y) => { return x + y; };
            Func<double, double, double> myGenericDelegate = (x, y) => { return x + y; };
            System.Console.WriteLine("{0:N} + {1:N} = {2:N}\n", arg1d, arg2d, myGenericDelegate(arg1d, arg2d)); // Prints: 5.0 + 4.0 = 9.0

            /***************************************************************************
            * Built-in ACTION delegate: used to print the contents of a list.          *
            *                                                                          *
            * Note: An Action<T> delegate is not explicitly instantiated because the   *
            *       signature of the anonymous method matches the signature of the     *
            *       Action<T> delegate that is expected by the List<T>.ForEach method. *
            ****************************************************************************/
            Console.WriteLine("\n---  Action<T> used to print the contents of a list     ----\n");
            List<String> names = new List<String> {"Shadow", "Holly", "Tessa"};
            names.ForEach(s => Console.WriteLine(s));
            Console.WriteLine();
        }
    }
}

Back | Top



C# Events

.C# Event


C# Events

In C#, delegates are the basis for the event system. Events are a layer on top of delegates which make it easier to securely implement the Publish-Subscribe pattern. Publish-Subscribe has only one publisher object which will raise a new event. Then one or more subscriber objects will be notified when the event occurs. Once notified, the subscriber objects will perform an action (i.e. they will "handle the event").

using System;
namespace csharpEvent
{
  /*******************************
   *      Publisher Class        *
   *******************************/
   public class Publisher
   {
      // 1. Create event arguments class
      public EventArgs e = null;

      // 2. Create a delegate for event handler
      public delegate void MyEventHandler(Publisher m, EventArgs e);

      // 3. Create the event
      public event MyEventHandler MyEvent;

      // 4. Create method to start the publisher
      public void Start()
      {
          while (true)
          {
              System.Threading.Thread.Sleep(1000);
              if (MyEvent != null) MyEvent(this, e);
          }
      }
   }

  /*******************************
   *   First Subscriber Class    *
   *******************************/
   public class Subscriber1
   {
       public void Subscribe(Publisher m)
       {
           // 1. Subscribe to the event, define action to take
           m.MyEvent += new Publisher.MyEventHandler(Sub1Action);
       }

       // 2. Define the action to take (event handler)
       private void Sub1Action(Publisher m, EventArgs e)
       {
          System.Console.WriteLine("Subscriber 1: Do Something");
       }
   }

  /*******************************
   *   Second Subscriber Class   *
   *******************************/
   public class Subscriber2
   {
       private long eventCount = 1;

       public void Subscribe(Publisher m)
       {
           // Subscribe to the event, define action to take
           m.MyEvent += new Publisher.MyEventHandler(Sub2Action);
       }

       // Define the action to take (event handler)
       private void Sub2Action(Publisher m, EventArgs e)
       {
           System.Console.WriteLine("Subscriber 2: Do Something Else\n");

           switch (eventCount % 2)
           {
              case 0 :
                 System.Console.WriteLine("*** Event Count is: {0}\n", eventCount);
                 break;
           }
           if (eventCount > 3)
           {
               Console.WriteLine("\nSubscriber 2 is halting the program after {0} events.\n", eventCount);
               Environment.Exit(0);
           }
           eventCount++;
       }
   }


   class Program
   {
      static void Header()
      {
         System.Console.WriteLine("************************************************************");
         System.Console.WriteLine("***  Event: One Publisher, Two Subscribers, No Arguments ***");
         System.Console.WriteLine("************************************************************\n");
      }

      static void Main()
      {
         Header();
         Publisher publisher = new Publisher();        // Create Publisher Object
         Subscriber1 subscriberl = new Subscriber1();  // Create First Subscriber Object
         Subscriber2 subscriber2 = new Subscriber2();  // Create Second Subscriber Object
         subscriberl.Subscribe(publisher);             // Sub1: Subscribe to the Publisher
         subscriber2.Subscribe(publisher);             // Sub2: Subscribe to the Publisher
         publisher.Start();                            // Start the publisher
      }
   }
}

Back | Top



C# Event Following .NET Standards

.C# Event Following .NET Standards


C# Event Following .NET Standards

For compatibility with the .NET Framework, events should be published based on the EventHandler delegate. A popular, and recommended option, is to use the generic version called EventHandler. The advantage of using EventHandler is that you do not need to code your own custom delegate if your event generates event data. You simply provide the type of the event data object as the generic parameter.

using System;
using System.Collections.Generic;

namespace dotNetEvent
{
    // 1. Create class for event arguments at scope
    //    visible to both publisher and subscriber
   /*******************************
    *   EventArgs Derived Class   *
    *******************************/
    public class MyEventArgs : EventArgs
    {
        public MyEventArgs(string s)
        {
            arg1 = s;
        }
        private string arg1;

        public string Arg1
        {
            get { return arg1; }
            set { arg1 = value; }
        }
    }

   /*******************************
    *      Publisher Class        *
    *******************************/
    class Publisher
    {
        // 2. Create the event using EventHandler<T>,
        //    This allows you to skip declaring a delegate.
        public event EventHandler<MyEventArgs> RaiseMyEvent;

        // 3. Create method to do work and call will
        //    call the method that raises the event.
        public void PubProcessing()
        {
            // Note: Work can be performed prior to
            //       or after the event is raised.
            OnRaiseMyEvent(new MyEventArgs("Event arg1"));
        }

        // 4. Declare protected virtual method to allow derived
        // classes to override the event invocation behavior
        protected virtual void OnRaiseMyEvent(MyEventArgs e)
        {
            // Make a temporary copy of the event to avoid possibility of
            // a race condition if the last subscriber unsubscribes
            // immediately after the null check and before the event is raised.
            EventHandler<MyEventArgs> myHandler = RaiseMyEvent;

            // Event is null if there are no subscribers
            if (myHandler != null)
            {
                // Format the string to send inside the MyEventArgs parameter
                e.Arg1 += String.Format(" at {0}", DateTime.Now.ToString());

                // Raise the event.
                myHandler(this, e);
            }
        }
    }

   /*******************************
    *   Subscribers Class         *
    *******************************/
    class Subscriber
    {
        private string id;
        public Subscriber(string ID, Publisher pub)
        {
            id = ID;

            // 1. Subscribe to the event
            pub.RaiseMyEvent += HandleMyEvent;
        }

        // 2. Define Action to take (event handler).
        void HandleMyEvent(object sender, MyEventArgs e)
        {
            Console.WriteLine(id + " argument 1: {0}", e.Arg1);
        }
    }

    class Program
    {
        static void Header()
        {
            Console.WriteLine("************************************************************");
            Console.WriteLine("***  Event: One Publisher, Two Subscribers, One Argument ***");
            Console.WriteLine("***                                                      ***");
            Console.WriteLine("***      1. Conforms to .NET framework guidelines.       ***");
            Console.WriteLine("***      2. Uses MyEventArgs to pass arguments to event. ***");
            Console.WriteLine("***      3. Uses EventHandler<T>.                        ***");
            Console.WriteLine("************************************************************\n");
        }
       
        static void Main(string[] args)
        {
            Header();
            Publisher publisher = new Publisher(); // Create Publisher Object
            Subscriber subscriber1 = new Subscriber("-> subscriber1", publisher); // Create First Subscriber Object
            Subscriber subscriber2 = new Subscriber("-> subscriber2", publisher); // Create Second Subscriber Object
            publisher.PubProcessing();             // Call the method that raises the event.
            Console.WriteLine();
        }
    }
}

Back | Top


Reference Articles

Top