C# .NET Delegates : Methods and Properties
The following table is a list of some of the public properties and methods of the MulticastDelegate type. Some of the members are inherited from System.Delegate.
|
Member |
Description |
|
BeginInvoke |
Invokes a delegate asynchronously. |
|
Combine |
Combines delegates into a multicast delegate. This is a static method. |
|
CreateDelegate |
Defines 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 of a delegate that was executed asynchronously. |
|
Invoke |
Executes a delegate, which calls all functions contained in the delegate. |
|
Method |
This is a property that returns the MethodInfo type of the last function in the invocation list. MethodInfo provides a description of a function. |
|
Remove |
Removes a delegate from a multicast delegate. This is a static method. |
|
RemoveAll |
Removes the invocation list of a delegate from the invocation list of another delegate. The result is returned. This is a static method. |
|
Target |
This is a property that returns the instance of the last function in the invocation list. For static functions, Target is null. |
C# .NET Delegates : Invocation List
Multicast delegates maintain an invocation list, which has an entry for each delegate of the multicast delegate. Entries are added to the invocation list in the same order in which delegates are added. GetInvocationList returns the invocation list as an array of delegates.
This is the syntax of the GetInvocationList method:
-
delegate [] GetInvocationList()
This code retrieves the list of delegates in a multicast delegate, in which each delegate is a wrapper of a function. The name of each function is displayed. The Delegate.Method property returns a MethodInfo type, which encapsulates the function abstracted by the delegate.
using System;
namespace Example.Delegates
{
public delegate void DelegateClass();
public class Program
{
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.");
}
Console.ReadKey();
}
public static void MethodA()
{
Console.WriteLine("MethodA...");
}
public static void MethodB()
{
Console.WriteLine("MethodB...");
}
}
}
The delegate signature can contain reference parameters. When invoked, the reference is passed to the called functions. Reference parameters are a way for functions on an invocation list to share state. Value parameters are not shareable across the functions. Because the invocation list is called in sequence, beginning with the first delegate and function, each successive entry in the chain can view changes in the reference parameter. The next item in the chain can view those changes and possibly change the state again. In this way, when the multicast delegate is invoked, the state information is propagated along the invocation list. Furthermore, functions in invocation lists that have the same target object or class share the state of the object or class. The following code uses the reference parameter of a multicast delegate as a counter. The delegate has a value and reference parameter. The value parameter is lost between function calls in the invocation list. However, the reference parameter persists.
using System;
namespace Example.Delegates
{
public delegate void DelegateClass(int valCount, ref int refCount);
public class Program
{
public static void Main()
{
DelegateClass del = (DelegateClass)AddOne + (DelegateClass)AddTwo + (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); // 4
Console.ReadKey();
}
public static void AddOne(int valCount, ref int refCount)
{
++valCount;
++refCount;
}
public static void AddTwo(int valCount, ref int refCount)
{
valCount += 2;
refCount += 2;
}
}
}
You can access the invocation list to execute each delegate and function therein directly. There are two reasons to invoke the invocation list directly. First, invoke the delegates explicitly to obtain the return of each delegate. When the invocation list is invoked implicitly, only the return of the last function is garnered. Second, invoke the invocation list directly in special circumstances (for example, to modify how exceptions are handled in a multicast delegate, which is described later).
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 combined into a multicast delegate. The foreach loop iterates the invocation list, multiplying the results of each function to calculate a factorial.
using System;
namespace Example.Delegates
{
public delegate int IncrementDelegate(ref short refCount);
public class Program
{
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);
Console.ReadKey();
}
public static int Incrementer(ref short refCount)
{
return refCount++;
}
}
}
C# .NET Delegates : 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 pointers or delegates can be dropped into the basket. The list of delegates is stored in an invocation list, which can even include multiple instances of the same delegate. When you execute the contents of the basket, the function pointers of the delegates are called FIFO. 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 += assignment operator. Combine is a static method. In C#, the Combine method is called with two delegate parameters or an array of delegates. The method returns a reference to the combined delegate.
The following code combines two delegates separately wrapping 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 += assignment operators are shown, with the Combine statement commented to avoid duplication.
using System;
namespace Examples.Delegates
{
public delegate void DelegateClass();
public class Program
{
public static void Main()
{
DelegateClass del = MethodA;
del += MethodB;
//del=(DelegateClass) DelegateClass.Combine(
// new DelegateClass [] {MethodA, MethodB});
del();
Console.ReadKey();
}
public static void MethodA()
{
Console.WriteLine("MethodA...");
}
public static void MethodB()
{
Console.WriteLine("MethodB...");
}
}
}
Removing delegates
To remove delegates from a multicast delegate, use the Remove method, the minus operator (-), or the -= assignment operator. Remove is a static method that accepts the source delegate as the first parameter and the delegate to remove as the second parameter. Be careful not to inadvertently remove all delegates from a multicast delegate, which causes a run-time error when invoked. The following code removes MethodB from a multicast delegate. When the delegate is invoked, MethodA is invoked, but not MethodB. The three methodologies are shown, with the Remove statement and operator minus commented to avoid duplication.
using System;
namespace Examples.Delegates
{
public delegate void DelegateClass();
public class Program
{
public static void Main()
{
DelegateClass del = MethodA;
del += MethodB;
del += MethodC;
del = del - MethodB;
// del = (DelegateClass) DelegateClass.Remove(del, (DelegateClass) MethodB);
// del -=MethodB;
del();
Console.ReadKey();
}
public static void MethodA()
{
Console.WriteLine("MethodA...");
}
public static void MethodB()
{
Console.WriteLine("MethodB...");
}
public static void MethodC()
{
Console.WriteLine("MethodC...");
}
}
}
C# .Net Delegates : Arrays of Delegates
Arrays of delegates can be the impetus of elegant solutions that would otherwise be unavailable. Creating an array of delegates is the same as creating an array of any type. Selecting from a set of tasks at run time is an example of when an array of delegates is essential to an elegant solution. The switch statement is one solution, in which each case of the switch statement is assigned to a task. Suppose that there are about three lines of code associated with each task. In the next revision of the application, 30 additional tasks are added, which results in an additional 90 lines of code. This solution provides linear growth and is not particularly scalable. With an array of delegates, regardless of the number of tasks, the solution is one line of code. Now and in the future, this solution remains one line of code. 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:
|
using System;
namespace Examples.Delegates { public delegate void Task();
public class Program { 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”); } } } |
C# .NET Delegates : Contravariance and Covariance
When are delegates compatible or similar? The parameters of the function pointer can be derivations of the parameters indicated in the delegate signature—this is called contravariance. Because the delegate parameters refine that of the function pointer, any input from the target method is acceptable to the delegate. Conversely, the return type of the delegate must be a derivation of the return type of the function pointer. The return of the function pointer can refine the return of the delegate. Therefore, any return from the delegate is compatible with that of the function pointer—this is called covariance. Contravariance and covariance expand the set of methods assignable to a delegate while maintaining type-safeness. Here is an example of both contravariance and covariance:
|
using System;
namespace Examples.Delegates { delegate ZClass DelegateClass(BClass obj); public class Program { 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); |
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 okay 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). This is covariance.
C# .NET Delegates : Define and Create
Define a Delegate
The delegate keyword is for defining new delegates. The delegate statement looks like a function signature. Rather, 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 called through the delegate. This code defines a new delegate, which internally becomes a class named DelegateClass:
|
public delegate int DelegateClass(string info); |
This is the syntax for defining a new delegate class:
-
accessibility delegate return delegatename(parameterslist)
Accessibility 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. delegatename is the name of the delegate classification.
Because defining a delegate creates a new class, delegates can be defined anywhere a class is appropriate. A delegate can be defined in a namespace as a namespace member and within a class as a nested class, but not as a class field or a local variable within a method.
Create a Delegate
As a class, use the new keyword to create an instance of a delegate. Delegates are derived from the System.MulticastDelegate reference type. Multicast delegates are repositories of zero or more function pointers. The list of pointers in a multicast delegate is called the invocation list. When a delegate hosts multiple function pointers, the functions are called on a first-in, first-out (FIFO) basis.
The delegate constructor is not overloaded and has a single parameter, which is the target method. For an instance method, use the object.method format. If static, use the class.method format. If the method and delegate are contained in the same class, neither the object nor class name is required. The following code initializes a delegate in a variety of ways:
|
using System;
namespace Examples.Delegates { 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 pointer directly to a delegate and omit the new operator, which is called delegate inference. Delegate inference infers a delegate signature from the function pointer, creates a new delegate, initializes the source delegate with the function pointer in the constructor, and assigns the source delegate to the target delegate. The source delegate and the target delegate should have compatible signatures. Although the code is more concise, the intent is not as obvious. Here is the previous code, changed for delegate inference:
|
DelegateClass del1 = Constructors.MethodA; DelegateClass del2 = MethodA;
ZClass obj = new ZClass(); DelegateClass del3 = obj.MethodB; |
The signatures of delegates are not entirely rigid. Through contravariance and covariance, there is some flexibility. The signature of the function pointer does not have to match the delegate signature exactly.
C# .NET Delegates : How to use
The common blueprint for using delegates includes steps to define, create, and invoke a delegate.
1. Define a classification of a delegate using the delegate keyword:
|
public delegate int DelegateClass(string info); |
2. Create an instance of a delegate using the new keyword. In the constructor, initialize the delegate with a function pointer. You can also create and initialize a delegate implicitly without the new keyword:
|
DelegateClass obj = new DelegateClass(MethodA); DelegateClass obj2 = MethodA; // implicit |
3. Invoke the delegate with the call operator “()”. The call operator is convenient, but makes it harder to discern a delegate invocation from a regular function call. Alternatively, invoke the delegate with the Invoke method, which clearly distinguishes invoking a delegate from a normal function call:
|
obj(“1″); obj.Invoke(“2″); // Alternative. |
4. As with any object, when the delegate is no longer needed, set the delegate to null:
|
obj=null; |
The complete list of the example application follows:
|
using System;
namespace Examples.Delegates { public delegate int DelegateClass(string info);
public class Steps { public static void Main() { DelegateClass obj = new DelegateClass(MethodA); DelegateClass obj2 = MethodA; // implicit int i = obj(“1″); Console.WriteLine(i.ToString()); i = obj.Invoke(“2″); // Alternative Console.WriteLine(i.ToString()); obj = null; obj2 = null;
Console.ReadKey(); }
public static int MethodA(string info) { Console.WriteLine(“Steps.MethodA”); return int.Parse(info); } } } |
C# .NET Delegates : Definition and Purpose
A delegate is an abstraction of one or more function pointers. Delegates are derived from System.MulticastDelegate, which is a reference type. System.MulticastDelegate is derived from System.Delegate. The delegate classes offer a public interface for initializing, adding, removing, and invoking delegates. An instance of a delegate is an object that abstracts the semantics of a function pointer. The object 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 pointer dropped into the delegate must have a compatible signature, which makes the function pointer type-safe. The return type should also match. Delegate covariance provides some flexibility with the signature. You assign function pointers to delegates based on signature, not type. Regardless of the object or type that binds the function, it is assignable to a delegate of the same signature.
Functions called with a delegate are given the security context of the caller, which prevents a delegate from performing a task not available to a lower-privilege caller. Delegates can be initialized with pointers to functions that are implemented anywhere. The only limitation is the signature. Callers need to be careful when invoking delegates containing function pointers to unknown sources, where there could be unexpected implementation. Use code access security to protect delegates.
Delegates are useful as general function pointers, callbacks, events, and threads. As a general function pointer, a delegate can be a method parameter, function return, class member, or local variable. Callbacks are valuable in many scenarios, including promoting peer-to-peer relationships, in which objects swap function pointers to send bilateral messages. Events support a publisher/subscriber model. The publisher notifies subscribers of events, whereas the subscriber registers functions to be called when the event occurs. Finally, a delegate is a path of execution for a thread, which is an asynchronous function call.
C# .NET Iterator Example : Complex Iteration
Link lists are complex data structures. Iterators are particularly useful when iterating complex data structures, such as a link list. Each item in the list is considered a node. Nodes maintain a reference to the previous and next node. From any node, you can walk the link list either forward or backward. For several reasons, the iteration is more complex. First, data structure must be iterated forward and backward during the same iteration. Second, fence posts are not as definable. Fence posts in arrays, stacks, queues, and other sequenced containers are easily found, which helps avoid fence post error exceptions. Finally, the iteration can start from any node in the link list, not necessarily just the beginning or end of the list.
Below is a partial listing of the Node class. The key code is in the GetEnumerator method. In the first while loop, the link list is iterating in reverse—from the current node to the beginning of list. This is accomplished by walking the prevNode member of the node class. The current node is enumerated next. Finally, the second while loop iterates the link list going forward from the current node to the end of the list. The nextNode members are walked.
|
public class Node<T> { public Node(Node<T> node, T data) { m_Info = data; if (node == null) { if (firstNode != null) { Node<T> temp = firstNode; this.nextNode = temp; temp.prevNode = this; firstNode = this; return; } prevNode = null; nextNode = null; firstNode = this; return; }
this.prevNode = node; this.nextNode = node.nextNode; node.nextNode = this; if (node.nextNode == null) { lastNode = null; } }
public void AddNode(T data) { this.nextNode = new Node<T>(this, data); }
public IEnumerator<T> GetEnumerator() { Node<T> temp = prevNode; while (temp != null) { yield return temp.m_Info; temp = temp.prevNode; } yield return m_Info; temp = nextNode; while (temp != null) { yield return temp.m_Info; temp = temp.nextNode; } }
private T m_Info; private static Node<T> lastNode = null; private static Node<T> firstNode = null; private Node<T> prevNode = null; private Node<T> nextNode = null; } |
C# .NET Iterator Example : Temporary Collections
Temporary collections are calculated at run time and are useful in a variety of circumstances. The list of prime numbers, the records of a file, and fields in a dataset are examples of collections that can be calculated. Temporary collections can be populated lazily. You can read a flat file or dataset on demand at run time to hydrate a temporary collection.
The following code enumerates days from the current date until the end of the month, which is calculated using DateTime structure. The method ToEndOfMonth is enumerable by virtue of returning the IEnumerable interface and possessing a yield statement. Each repeat of the while loop extrapolates the next day until the end of the month is reached. The yield statement iterates the days as they are calculated.
|
using System; using System.Collections;
namespace Examples.Iterators { public class Program { public static void Main() { foreach (string day in ToEndOfMonth()) { Console.WriteLine(day); }
Console.ReadKey(); }
public static IEnumerable ToEndOfMonth() { DateTime date = DateTime.Now;
int currMonth = date.Month; while (currMonth == date.Month) { string temp = currMonth.ToString() + “/” + date.Day.ToString(); date = date.AddDays(1); yield return temp; } } } } |
-
Archives
- February 2009 (1)
- November 2008 (6)
- October 2008 (4)
- September 2008 (13)
- August 2008 (11)
- July 2008 (29)
- June 2008 (19)
- May 2008 (8)
-
Categories
-
RSS
Entries RSS
Comments RSS

