Chapter 10. Delegates and Events

A delegate is an abstraction of one or more function pointers. With a delegate, you can treat a function as data, which includes using a delegate as a variable or data member. Delegates allow functions to be passed as parameters, returned from a function as a value, and even stored in an array. Unlike function pointers in other languages, particularly C and C++, delegates are type-safe, object-oriented, and secure, which reduces common problems associated with using function pointers.

Delegates are derived from the System.MulticastDelegate class, which itself is derived from System.Delegate. System.MulticastDelegate is a reference type and is an interface for managing and executing delegates. An instance of a delegate encapsulates a function pointer and a target object. When a delegate is invoked, the delegate calls the function on that object.

Delegates have a signature and a return type. A function that is added to the delegate must be compatible with this signature, which helps make the delegate type-safe. The signature of the function is more important than the type. Regardless of the object or type that binds the function, it is assignable to any delegate with the same signature.

Functions called through a delegate have the security context of the caller, which prevents a delegate from performing a task not available to a lower-privilege client. Invoke only delegates from a known source. Executing code from an unknown source (including a delegate) is always dangerous. Use code access security to protect your code from misbehaving delegates.

In addition to general purposes, delegates can be used for callbacks, events, and threads. Callbacks are valuable in many scenarios, such as peer-to-peer relationships. In a peer-to-peer relationship between objects, peers can exchange delegates and send bilateral messages. Events support a publisher/subscriber model. A publisher exposes events. The underlying type of an event is a delegate. A subscriber registers functions (event handlers) to be called when the event occurs. Finally, the function reference in a delegate can be an entry point for a thread. This chapter focuses on general function pointers, callbacks, and events. Threads are mentioned but only as related to these other topics.

Delegates

There are several standard steps to define, create, and then invoke a delegate:

  1. Define a delegate with the delegate keyword. The signature of functions referenced by the delegate must match the delegate signature. For example:

    public delegate int DelegateClass(string info);
  2. Create an instance of a delegate using the new keyword. You can pass a function reference to the constructor. Delegates also can be initialized implicitly without a constructor. Here are two different examples of initializing a delegate:

    DelegateClass obj1 = new DelegateClass(MethodA);
    DelegateClass obj2 = MethodA; // implicit
  3. Invoke a delegate with the call operator ( ). The call operator is convenient, but makes it harder to discern delegate invocation from a regular function call. Alternatively, invoke the delegate with the Invoke method, which clearly distinguishes delegate invocation from a normal function call:

    obj1("1");
    obj2.Invoke("2"); // Explicit delegate invocation alternative
  4. As with any object, when the delegate is no longer needed, set the delegate instance to null:

    obj = null;

The complete list of the example application is shown in the following code. There are two instances of delegates. The MethodA function is added to both delegates, and then they are both invoked. This calls MethodA twice—once for each delegate:

using System;

namespace Donis.CSharpBook {

    public delegate int DelegateClass(string info);

    public class Steps {

        public static void Main() {
            DelegateClass obj1 = new DelegateClass(MethodA);
            DelegateClass obj2 = MethodA; // implicit
            obj1("1");
            obj2.Invoke("2"); // Explicit delegate invocation alternative
            obj1 = null;
            obj2 = null;
        }

        public static int MethodA(string info) {
            Console.WriteLine("Steps.MethodA");
            return int.Parse(info);
        }
    }
}

Defining a Delegate

The delegate keyword defines a new delegate. A delegate definition looks like a function signature, but it defines a new delegate type. Function pointers matching the delegate signature and return type can be stored into the delegate. Therefore, only functions with similar signatures can be added and then called through the delegate. The following code defines a new delegate. Internally this generates a class named DelegateClass. Because defining a delegate creates a new class, delegates can be declared anywhere a class declaration is appropriate. A delegate can be defined in a namespace as a namespace member and within a class as a nested class. A delegate cannot be used as a data member of a class or as a local variable within a method.

public delegate int DelegateClass(string info);

This is the syntax for defining a new delegate class:

accessibility delegate return delegatename(parameterslist)

Accessibility of a delegate is limited to the valid accessibility of classes, such as public or private. The remainder of the syntax defines the signature and return type of the delegate. The delegate type is named delegatename.

Creating a Delegate

As a class, use the new keyword to create an instance of a delegate. As mentioned, delegates are derived from the System.MulticastDelegate type. Multicast delegates are repositories of zero or more function references. The list of functions referenced in a multicast delegate is called the invocation list. When multiple function references have been added to a delegate, the functions are called on a first-in, first-out (FIFO) basis.

The delegate constructor has a single parameter, which is the first method added to the delegate. For an instance method, use the object.method format. For a static method, use the class.method format. If the static method and delegate are in the same class, the class name is not required. You can simply use the function name as the parameter. The following code initializes a delegate in a variety of ways:

using System;

namespace Donis.CSharpBook {

    public delegate void DelegateClass();

    public class Constructors {

        public static void Main() {
            DelegateClass del1 = new
                DelegateClass(Constructors.MethodA);
            DelegateClass del2 = new DelegateClass(MethodA);

            ZClass obj = new ZClass();
            DelegateClass del3 = new DelegateClass(obj.MethodB);
        }

        public static void MethodA() {
        }
    }
    public class ZClass {

        public void MethodB() {
        }
    }
}

You can assign a function reference directly to a delegate and omit the new operator. This is called delegate inference. Delegate inference infers a delegate signature from the function reference. Assigning the function reference creates and initializes the delegate. Although delegate inference is more concise, the intent is not as obvious. Here is the previous code, changed to use delegate inference:

DelegateClass del1 = Constructors.MethodA;
DelegateClass del2 = MethodA;

ZClass obj = new ZClass();
DelegateClass del3 = obj.MethodB;

As previously mentioned, the signature of a function reference must match the signature of a target delegate. Through contravariance and covariance, this matching can be expanded.

Contravariance and Covariance

When is a function reference compatible with a delegate? Each parameter of the function reference must be the same type or a derivation type of the corresponding parameter type of the delegate—this is called contravariance. In this manner, the parameters of the delegate can refine that of the function reference. The return value of the delegate must be the same or a derivation of the return type of the function reference—this is called covariance. Contravariance and covariance expand the set of methods assignable to a delegate while maintaining type-safety. Here is an example of both contravariance and covariance:

using System;

namespace Donis.CSharpBook {
    delegate ZClass DelegateClass(BClass obj);
    public class Starter {
        public static void Main() {
            DelegateClass del = MethodA;
        }

        public static YClass MethodA(AClass obj) {
            return null;
        }
    }

    public class ZClass {
    }

    public class YClass : ZClass {
    }

    public class AClass {
    }

    public class BClass : AClass {
    }
}

In the preceding code, this is the signature of the delegate:

delegate ZClass DelegateClass(BClass obj);

And this is the signature of the function pointer:

public static YClass MethodA(AClass obj)

The signature of the delegate and function pointer are not exactly the same. This is all right because the parameter of the delegate (BClass) refines the parameter of the function (AClass), which is contravariance. In addition, the return type of the function (YClass) refines the return type of the delegate (ZClass), which is covariance.

Invoking a Delegate

Delegates are invoked through the call operator ( ) or through the Invoke method. The C# compiler translates the call operator into an Invoke method, which has the same signature as the delegate and calls the function references aggregated by the delegate. If the delegate contains four function references, invoking the delegate will call all four functions in the delegate. They are called in the same order as they are placed in the delegate. The parameters of the delegate are input arguments for the function calls. Delegates can have non-void returns, which means all functions contained in the delegate must return a value. The return from the last function becomes the return value of the delegate.

Arrays of Delegates

Creating an array of delegates is similar to declaring arrays of any type. Arrays of delegates can provide elegant solutions that would otherwise be unavailable. For example, a sample program presents the user with ten tasks. Users then can choose specific tasks at run time. A switch statement is one solution, where each case of the switch statement executes a chosen task. However, this approach is not extensible. Suppose three lines of code are required to call each task. Let us assume the next revision of the application goes from ten tasks to 20. The total amount of code doubles. This solution requires linear growth and is not particularly scalable. With an array of delegates, regardless of the number of tasks, the solution is simply a change to the single line of code that declares the delegate array—whether you are adding one task or a thousand. Even if additional tasks are added, the number of lines of code remains constant. This solution is more scalable than the switch statement approach. In the following code, an array of delegates facilitates invoking a task from a menu of choices. An array of delegates is created, one for each of the three tasks. When the user selects a task, an index into the delegate array is set. Executing the delegate takes two lines of code. You could add an additional 100 tasks and the number of lines of code would remain constant. It would take the same two lines of code to execute any of the 100 tasks:

using System;

namespace Donis.CSharpBook {

    public delegate void Task();

    public class Starter {

        public static void Main() {
            // array of delegates
            Task[] tasks = { MethodA, MethodB, MethodC };
            string resp;
            do {
                Console.WriteLine("TaskA - 1");
                Console.WriteLine("TaskB - 2");
                Console.WriteLine("TaskC - 3");
                Console.WriteLine("Exit - x");
                resp = Console.ReadLine();
                if (resp.ToUpper() == "X") {
                    break;
                }
                try {
                    int choice = int.Parse(resp) - 1;
                    // as promised, one line of code to invoke
                    // the correct method.
                    tasks[choice]();
                }
                catch {
                    Console.WriteLine("Invalid choice");
                }
            } while (true);
        }

        public static void MethodA() {
            Console.WriteLine("Doing TaskA");
        }

        public static void MethodB() {
            Console.WriteLine("Doing TaskB");
        }

        public static void MethodC() {
           Console.WriteLine("Doing TaskC");
        }
    }
}

System.MulticastDelegate Class

Delegates default to multicast delegates, which inherit from the System.MulticastDelegate class. A multicast delegate is similar to a basket. Multiple function references can be dropped into the basket. The list of functions is stored in an invocation list, which can even include multiple instances of the same function. When you invoke the delegate, the contents of the basket, which contains function references, is called in FIFO order. Multicast delegates are useful for invoking a chain of functions.

Combining delegates

Multiple delegates are combined using the Combine method, the plus operator (+), or the += compound assignment operator. Combine is a static method. The Combine method is called with either two delegate parameters or an array of delegates. The method returns the combined delegate. After combining, the function references of both delegates are in a single multicast delegate.

The following code combines two delegates that separately hold functions MethodA and MethodB. When the Combine delegate is invoked, MethodA is run first and MethodB second. This is the order in which the function pointers are added to the multicast delegate. Both the Combine method and the += operators are shown in this code, with the Combine statement commented out to avoid duplication:

using System;

namespace Donis.CSharpBook {
 public delegate void DelegateClass();

     public class Starter {
         public static void Main() {
             DelegateClass del = MethodA;
             del += MethodB;
             // del = (DelegateClass) DelegateClass.Combine(
             //     new DelegateClass[] { MethodA, MethodB });
             del();
         }
         public static void MethodA() {
             Console.WriteLine("MethodA...");
         }

         public static void MethodB() {
             Console.WriteLine("MethodB...");
         }
     }
}

Removing delegates

To remove function references from a multicast delegate, use the Remove method, the minus operator (-), or the -= compound assignment operator. Remove is a static method that accepts the multicast delegate as the first parameter and a delegate as the second parameter. The second delegate contains the function reference to be removed. Be careful not to remove all delegates inadvertently from a multicast delegate, which will cause a runtime error when the delegate is invoked. The following code removes MethodB from a multicast delegate that contains MethodA and MethodB. When the delegate is invoked, MethodA is invoked, but not MethodB. The three ways to remove a delegate are shown, with the Combine statement and minus operator commented out to avoid duplication:

using System;

namespace Donis.CSharpBook {

    public delegate void DelegateClass();

    public class Starter {
        public static void Main() {
            DelegateClass del = MethodA;
            del += MethodB;
            del += MethodC;
            del = del - MethodB;

            // del = (DelegateClass) DelegateClass.Remove(
            //     del, (DelegateClass) MethodB);
            // del -= MethodB;
            del();
        }
        public static void MethodA() {
            Console.WriteLine("MethodA...");
        }

        public static void MethodB() {
            Console.WriteLine("MethodB...");
        }

        public static void MethodC() {
            Console.WriteLine("MethodC...");
        }
    }
}

Invocation List

Multicast delegates maintain an invocation list, which has an entry for each function reference of the multicast delegate. Entries are added to the invocation list in the same order in which the functions are added. GetInvocationList returns the invocation list as an array of delegates, which contain each function in the multicast delegate.

Here is the syntax of the GetInvocationList method:

delegate[] GetInvocationList()

The following code gets the invocation list from a multicast delegate. The list is then iterated and the name of each function is displayed. The Delegate.Method property returns a MethodInfo object, which provides information on the function reference, including the name:

using System;

namespace Donis.CSharpBook {
    public delegate void DelegateClass();
    public class Starter {
        public static void Main() {
            DelegateClass del = (DelegateClass)
            DelegateClass.Combine(new DelegateClass[]
                { MethodA, MethodB, MethodA, MethodB } );
            del();
            foreach (DelegateClass item in del.GetInvocationList()) {
                Console.WriteLine(item.Method.Name +
                    " in invocation list.");
            }

        }
        public static void MethodA() {
            Console.WriteLine("MethodA...");
        }

        public static void MethodB() {
            Console.WriteLine("MethodB...");
        }
    }
}

A multicast delegate can have reference parameters in the delegate signature. When the delegate is invoked, the reference is passed in sequence to the called functions. In this manner, reference parameters are a way for functions on an invocation list to share state. Parameters that are value types are not shareable across the functions. Because the invocation list is called in order, beginning with the first function, each successive entry in the invocation list can view changes made to a reference parameter by previous functions. Furthermore, functions in invocation lists that have the same target object or class share the state of that object or class. If one reference modifies a field in the target object or class, any other functions that are bound to the same target can view the change. The following code uses the reference parameter of a multicast delegate as a counter. The delegate has a value and reference parameter—both are incremented. Changes to the count for the value parameter is lost between function calls in the invocation list. However, changes to the count for the reference parameter persists:

using System;

namespace Donis.CSharpBook {

    public delegate void DelegateClass(int valCount,
        ref int refCount);

    public class Counter {
        public static void Main() {
            DelegateClass del = (DelegateClass) AddOne +
                (DelegateClass) AddThree + (DelegateClass) AddOne;
            int valCount = 0;
            int refCount = 0;
            del(valCount, ref refCount);
            Console.WriteLine("Value count = {0}",
                valCount); // 0
            Console.WriteLine("Reference count = {0}",
                refCount); // 5
        }

        public static void AddOne(int valCount, ref int refCount) {
            ++valCount;
            ++refCount;
        }

        public static void AddThree(int valCount, ref int refCount) {
            valCount += 3;
            refCount += 3;
        }
    }
}

You can access the invocation list to execute individual functions of the multicast delegate directly. There are two reasons to directly invoke the functions from the invocation list. One reason is to invoke the delegates explicitly so you can obtain the return value of each. When the invocation list is invoked implicitly, the return value of only the last function is obtained. You might also want to invoke the invocation list directly to accommodate special circumstances (for example, to handle exceptions).

The following code uses the invocation list to calculate a factorial. The Incrementer method increments a number, which is a reference parameter. The incremented value is returned from the method. Five delegates are created and initialized with the Increment method and then combined into a multicast delegate. The foreach loop iterates the invocation list and invokes each method with the return value from the previous method. This is how the factorial is calculated:

using System;

namespace Donis.CSharpBook {

    public delegate int IncrementDelegate(
        ref short refCount);

    public class Factorial {
        public static void Main() {

            IncrementDelegate[] values =
                { Incrementer, Incrementer,
                  Incrementer, Incrementer,
                  Incrementer };
            IncrementDelegate del =
                (IncrementDelegate) IncrementDelegate.Combine(values);
            long result = 1;
            short count = 1;
            foreach (IncrementDelegate number in del.GetInvocationList()) {
                result = result * number(ref count);
            }
            Console.WriteLine("{0} factorial is {1}",
            del.GetInvocationList().Length, result);
        }

        public static int Incrementer(ref short refCount) {
            return refCount++;
        }
    }
}

Methods and properties

Several members of the delegate type, such as GetInvocationList, have already been introduced. Table 10-1 is a list of other public methods and properties of the MulticastDelegate type. Many of these members are inherited from System.Delegate.

Table 10-1. Additional MulticastDelegate members

Member

Description

BeginInvoke

Invokes a delegate asynchronously.

Combine

Combines delegates into a multicast delegate. This is a static method.

CreateDelegate

Creates a delegate at run time. This is a static method.

DynamicInvoke

Dynamically invokes a delegate that was created at run time.

EndInvoke

Requests the results from a delegate that was executed asynchronously.

Invoke

Executes a delegate, which calls all functions contained in the delegate.

Method

This property returns the MethodInfo type for the function reference.

Remove

Removes a delegate from a multicast delegate. This is a static method.

RemoveAll

Removes all function references of a delegate from the invocation list of another delegate. This is a static method.

Target

This is a property that returns the object that is bound to the function reference. If the function is static, null is returned.

Generics and Delegates

A delegate can contain closed generic methods, but open generic methods cannot be added to a delegate. Some existing permutation of the generic method must be compatible with the signature of the delegate. Otherwise, the generic cannot be added to the invocation list. If the generic method contains actual parameters, the type is inferred. However, the return type cannot be inferred and must be specified.

In addition, the delegate itself can be generic and closed when the delegate is created. Functions added to a generic delegate need to match the signature of the closed generic delegate.

Here is an example of a normal delegate initialized with a generic method:

using System;

namespace Donis.CSharpBook {
    public delegate void DelegateClass(int data);
    public class Starter {
        public static void Main() {
            DelegateClass del1 = MethodA<int>;
            del1(5);
            DelegateClass del2 = MethodA;
            del2(10); // inferred
        }

        public static void MethodA<T>(T data) {
            Console.WriteLine("MethodA ({0})", data);
        }
    }
}

Here is an example of a generic delegate initialized with a normal method:

using System;

namespace Donis.CSharpBook {
    public delegate void DelegateClass<T>(T data);
    public class Starter {
        public static void Main() {
            DelegateClass<string> del = MethodA;
            del("text");
        }

        public static void MethodA(string data) {
            Console.WriteLine("MethodA ({0})", data);
        }
    }
}

Asynchronous Invocation

Delegate.Invoke and the delegate call operator ( ) invoke function references synchronously. Delegates also can be invoked asynchronously on a separate worker thread. Function references of a delegate that perform a time-consuming task might benefit by running on a separate thread. This is especially true for Microsoft Windows Forms applications, where the responsiveness of the user interface during a long task is a concern. The user interface could freeze during an extended task and inconvenience the user. Asynchronous delegates are also useful when monitoring hardware devices, communicating with network services waiting for timer routines, and more. Synchronous solutions are simpler. Developers should understand thread synchronization when invoking a delegate asynchronously, which undoubtedly adds complexity. Therefore, use synchronous solutions whenever possible.

Use BeginInvoke and EndInvoke methods for asynchronous invocation.

BeginInvoke Method

BeginInvoke can be called on delegates holding a single function reference. BeginInvoke adds the operation to a thread queue, where the function is invoked asynchronously on a thread from the Common Language Runtime (CLR) thread pool. The thread pool for asynchronous execution has a default maximum of 25 threads per processor.

Here is the syntax of the BeginInvoke method:

IAsyncResult BeginInvoke(arguments, AsyncCallback callback, object asyncState)

The parameters for BeginInvoke are the parameters of the function reference of the delegate followed by an AsyncCallBack parameter and an object parameter. If the function has four parameters, then BeginInvoke begins with those arguments. If the function has no parameters, callback is the first argument. The callback parameter is the callback that is invoked when the asynchronous function has completed. The callback is a delegate. The final parameter, asyncState, is a state object, which is passed into the callback function as a parameter, allowing the calling and called functions of the delegate to communicate and share data. The callback and state objects are optional and either can be set to null. The value returned from BeginInvoke is an IAsyncResult object. The return value allows you to query the status of the asynchronous operation. IAsyncResult has four properties, which are listed in Table 10-2.

Table 10-2. IAsyncResult properties

Property

Description

AsyncState

This is the same state object passed into the asynchronous invocation.

AsyncWaitHandle

Use WaitHandle to block the calling thread until the asynchronous operation is completed.

CompleteSynchronously

Indicates whether the asynchronous operation completed synchronously.

IsCompleted

Indicates whether the asynchronous operation has completed.

The following code is a generic template for invoking a delegate asynchronously. It highlights using the IAsyncResult return value and the state object:

using System;
using System.Threading;

namespace Donis.CSharpBook {
    public delegate void DelegateClass();

    public class Starter {

        public static void Main() {
            DelegateClass del = MethodA;
            DelegateStateBag state = new DelegateStateBag();
            IAsyncResult ar = del.BeginInvoke(Callback, state);
            if (ar.IsCompleted == true) {
                Console.WriteLine("MethodA completed");
            }
            else {
                Console.WriteLine("MethodA not completed");
            }
            ar.AsyncWaitHandle.WaitOne();

            // doing something else

            Thread.Sleep(100);
            lock (state) {
                Console.WriteLine("Back in Main");
                Console.WriteLine(state.message);
            }
        }

        public static void Callback(IAsyncResult ar) {
            DelegateStateBag state =
                (DelegateStateBag) ar.AsyncState;
            lock (state) {
                Console.WriteLine("Callback running");
                ((DelegateStateBag) ar.AsyncState).message =
                    "State object modified in callback.";
            }
        }

        public static void MethodA() {
            Console.WriteLine("MethodA running...");
            Thread.Sleep(200);
        }
    }

    class DelegateStateBag {
        public string message;
    }
}

Here is the output from running the application:

MethodA not completed
MethodA running...
Callback running
Back in Main
State object modified in callback.

Interestingly, the not completed message is received before MethodA has even started. BeginInvoke does not directly invoke the function in the delegate. Rather, it adds a request to the thread pool queue, which is eventually handled. There is a Thread.Sleep statement near the end of the Main method in the sample code. If that were removed, a race condition would exist between Main and the completion routine called Callback. What is the source of the race condition? Note the WaitHandle.WaitOne command in Main, which blocks Main until the asynchronous operation is completed. The callback function is called when the asynchronous operation finishes. When the asynchronous operation finishes, the Main and Callback routines are both started, and the race begins. Callback needs to update the state object before Main displays the contents of the same object. The Thread.Sleep statement is a primitive way of delaying Main and removing the race condition. This allows the callback function to lock the state object before Main.

EndInvoke Method

EndInvoke returns the result of an asynchronous operation. You can call EndInvoke in the callback routine or in the thread that asynchronously executed the delegate. EndInvoke has a similar signature as the delegate minus any value type parameters and an additional parameter for an IAsyncResult object. The IAsyncResult parameter is the same IAsyncResult object that is returned from BeginInvoke and described in the preceding section. The return type of EndInvoke is the same as the return type of the delegate.

Here is the syntax of the EndInvoke method:

returntype EndInvoke(ref_out_arguments, IAsyncResult ar)

Calling EndInvoke before the asynchronous function has finished will block the calling thread. Do not call EndInvoke more than once on the same thread for the asynchronous operation.

Here is generic sample code for the EndInvoke method:

using System;
using System.Threading;

namespace Donis.CSharpBook {
    public delegate int DelegateClass(out DateTime start,
        out DateTime stop);

    public class Starter {

        public static void Main() {
            DelegateClass del = MethodA;
            DateTime start;
            DateTime stop;
            IAsyncResult ar = del.BeginInvoke(out start, out stop,
                null, null);

            ar.AsyncWaitHandle.WaitOne();

            // doing something else

            int elapse = del.EndInvoke(out start, out stop, ar);
            Console.WriteLine("Start time: {0}",
            start.ToLongTimeString());
            Console.WriteLine("Stop time: {0}",
            stop.ToLongTimeString());
            Console.WriteLine("Elapsed time: {0} seconds",
                elapse);
        }


        public static int MethodA(out DateTime start,
                out DateTime stop) {
            start = DateTime.Now;
            Thread.Sleep(5000);
            stop = DateTime.Now;
            return (stop - start).Seconds;
        }
    }
}

Asynchronous Delegate Diagram

The preceding sections explained how to invoke a delegate asynchronously and to obtain the results. Figure 10-1 diagrams the relationship and sequence of the steps in this procedure, including the delegate, the BeginInvoke method, the EndInvoke method, and the function reference invocation.

Asynchronous processing of a delegate

Figure 10-1. Asynchronous processing of a delegate

Delegate Internals

Delegates may appear to developers to work by magic because so much is hidden. The real magician, though, is the C# compiler. This section explains the magic of the compiler. Why should you care about that? Understanding how delegates are implemented internally should help in knowing how, where, and when to use delegates. This knowledge also helps to debug problems if they occur.

The C# compiler builds a class for every delegate. The class has the same name as the delegate and four methods: a constructor, Invoke, BeginInvoke, and EndInvoke. Importantly, the delegate class is derived from System.MulticastDelegate. Here is a typical delegate:

public delegate int ADelegate(int arg1, ref int arg2);

Figure 10-2 shows the ADelegate class for this delegate viewed in the Intermediate Language Disassembler (ILDASM).

ADelegate, a class created for a delegate

Figure 10-2. ADelegate, a class created for a delegate

Members of the delegate class, such as Invoke and BeginInvoke, were described earlier in this chapter. However, it is comforting to actually see them. It is not magic after all. There may be a slight surprise: The constructor of the delegate has two arguments. In C#, the constructor of the delegate has a single parameter, which is the function reference. The C# compiler interprets the single parameter as a target and a function pointer, which become the parameters of the two argument constructor.

Exceptions

What happens if an unhandled exception is raised in a function that is called through a delegate? If the delegate is invoked inside a protected block of code, the exception is trapped. In the case of a multicast delegate, the invocation list is called until an exception occurs. Functions after the failed function in the invocation list are not invoked. Let us assume that the invocation list has references to MethodA, MethodB, MethodC, and MethodD and that they were added to the delegate in that order. If an unhandled exception is raised in MethodB, MethodC and MethodD are not called.

When an exception is raised in an asynchronous function, the exception is routed to the thread that called the function reference through the delegate. The following code demonstrates this:

using System;
using System.Threading;

namespace Donis.CSharpBook {

    public delegate void DelegateClass();

    public class Starter {
        public static void Main() {
            Console.WriteLine("Main: Running on primary thread");
            try {
                DelegateClass del = MethodA;
                IAsyncResult ar = del.BeginInvoke(null, null);
                del.EndInvoke(ar);
            }
            catch(Exception except) {
                Console.WriteLine("Catch: Running on primary thread");
                Console.WriteLine("Exception caught: " +
                    except.Message);
            }
        }

        public static void MethodA() {
            if (Thread.CurrentThread.IsThreadPoolThread == true) {
                Console.WriteLine("MethodA: Running on a thread pool thread");
            }
            else {
                Console.WriteLine("MethodA: Running on primary thread");
            }
            throw new Exception("failure");
        }
    }
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.110.155