What is a Delegate ?
All of us have been exposed to event driven programming of some sort or the other. C# adds on value to the often mentioned world of event driven programming by adding support through delegates. A delegate in C# is similar to a function pointer in C or C++. Using a delegate allows the programmer to encapsulate a reference to a method inside a delegate object. The delegate object can then be passed to code which can call the referenced method, without having to know at compile time which method will be invoked.
A delegate declaration defines a reference type that can be used to encapsulate a method with a specific signature. A delegate instance encapsulates a static or an instance method. Delegates are roughly similar to function pointers in C++; however, delegates are type-safe and secure. A delegate lets you pass a function as a parameter. The type safety of delegates requires the function you pass as a delegate to have the same signature as the delegate declaration.
Call a Function directly - No Delegate
In most cases, when we call a function, we specify the function to be called directly. If the class MyClass has a function named Process, we'd normally call it like this (SimpleSample.cs):
using System;
namespace Akadia.NoDelegate
{
public class MyClass
{
public void Process()
{
Console.WriteLine("Process() begin");
Console.WriteLine("Process() end");
}
}
public class Test
{
static void Main(string[] args)
{
MyClass myClass = new MyClass();
myClass.Process();
}
}
}
That works well in most situations. Sometimes, however, we don't want to call a function directly - we'd like to be able to pass it to somebody else so that they can call it. This is especially useful in an event-driven system such as a graphical user interface, when we want some code to be executed when the user clicks on a button, or when we want to log some information but can't specify how it is logged.
The very basic Delegate
An interesting and useful property of a delegate is that it does not know or care about the class of the object that it references. Any object will do; all that matters is that the method's argument types and return type match the delegate's. This makes delegates perfectly suited for "anonymous" invocation.
The signature of a single cast delegate is shown below:
delegate result-type identifier ([parameters]);
where:
result-type: The result type, which matches the return type of the function.
identifier: The delegate name.
parameters: The Parameters, that the function takes.
Examples :
A delegate will allow us to specify what the function we'll be calling looks like without having to specify which function to call. The declaration for a delegate looks just like the declaration for a function, except that in this case, we're declaring the signature of functions that this delegate can reference.
There are three steps in defining and using delegates:
- Declaration
- Instantiation
- Invocation
A very basic example (SimpleDelegate1.cs):
using System;
namespace Akadia.BasicDelegate
{
// Declaration
public delegate void SimpleDelegate();
class TestDelegate
{
public static void MyFunc()
{
Console.WriteLine("I was called by delegate ...");
}
public static void Main()
{
// Instantiation
SimpleDelegate simpleDelegate = new SimpleDelegate(MyFunc);
// Invocation
simpleDelegate();
}
}
}
Compile an test:
# csc SimpleDelegate1.cs
# SimpleDelegate1.exe
I was called by delegate ...
Calling Static Functions
For our next, more advanced example (SimpleDelegate2.cs), declares a delegate that takes a single string parameter and has no return type:
using System;
namespace Akadia.SimpleDelegate
{
// Delegate Specification
public class MyClass
{
// Declare a delegate that takes a single string parameter
// and has no return type.
public delegate void LogHandler(string message);
// The use of the delegate is just like calling a function directly,
// though we need to add a check to see if the delegate is null
// (that is, not pointing to a function) before calling the function.
public void Process(LogHandler logHandler)
{
if (logHandler != null)
{
logHandler("Process() begin");
}
if (logHandler != null)
{
logHandler ("Process() end");
}
}
}
// Test Application to use the defined Delegate
public class TestApplication
{
// Static Function: To which is used in the Delegate. To call the Process()
// function, we need to declare a logging function: Logger() that matches
// the signature of the delegate.
static void Logger(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
MyClass myClass = new MyClass();
// Crate an instance of the delegate, pointing to the logging function.
// This delegate will then be passed to the Process() function.
MyClass.LogHandler myLogger = new MyClass.LogHandler(Logger);
myClass.Process(myLogger);
}
}
}
Compile an test:
# csc SimpleDelegate2.cs
# SimpleDelegate2.exe
Process() begin
Process() end
Calling Member Functions
In the simple example above, the Logger( ) function merely writes the string out. A different function might want to log the information to a file, but to do this, the function needs to know what file to write the information to (SimpleDelegate3.cs).
using System;
using System.IO;
namespace Akadia.SimpleDelegate
{
// Delegate Specification
public class MyClass
{
// Declare a delegate that takes a single string parameter
// and has no return type.
public delegate void LogHandler(string message);
// The use of the delegate is just like calling a function directly,
// though we need to add a check to see if the delegate is null
// (that is, not pointing to a function) before calling the function.
public void Process(LogHandler logHandler)
{
if (logHandler != null)
{
logHandler("Process() begin");
}
if (logHandler != null)
{
logHandler ("Process() end");
}
}
}
// The FileLogger class merely encapsulates the file I/O
public class FileLogger
{
FileStream fileStream;
StreamWriter streamWriter;
// Constructor
public FileLogger(string filename)
{
fileStream = new FileStream(filename, FileMode.Create);
streamWriter = new StreamWriter(fileStream);
}
// Member Function which is used in the Delegate
public void Logger(string s)
{
streamWriter.WriteLine(s);
}
public void Close()
{
streamWriter.Close();
fileStream.Close();
}
}
// Main() is modified so that the delegate points to the Logger()
// function on the fl instance of a FileLogger. When this delegate
// is invoked from Process(), the member function is called and
// the string is logged to the appropriate file.
public class TestApplication
{
static void Main(string[] args)
{
FileLogger fl = new FileLogger("process.log");
MyClass myClass = new MyClass();
// Crate an instance of the delegate, pointing to the Logger()
// function on the fl instance of a FileLogger.
MyClass.LogHandler myLogger = new MyClass.LogHandler(fl.Logger);
myClass.Process(myLogger);
fl.Close();
}
}
}
The cool part here is that we didn't have to change the Process() function; the code to all the delegate is the same regardless of whether it refers to a static or member function.
Compile an test:
# csc SimpleDelegate3.cs
# SimpleDelegate3.exe
# cat process.log
Process() begin
Process() end
# SimpleDelegate3.exe
# cat process.log
Process() begin
Process() end
Delegates are ideally suited for use as events — notifications from one component to "listeners" about changes in that component.
An event is a message sent by an object to signal the occurrence of an action. The action could be caused by user interaction, such as a mouse click, or it could be triggered by some other program logic. The object that raises (triggers) the event is called the event sender. The object that captures the event and responds to it is called the event receiver.
In event communication, the event sender class does not know which object or method will receive (handle) the events it raises. What is needed is an intermediary (or pointer-like mechanism) between the source and the receiver. The .NET Framework defines a special type (Delegate) that provides the functionality of a function pointer.
A delegate is a class that can hold a reference to a method. Unlike other classes, a delegate class has a signature, and it can hold references only to methods that match its signature. A delegate is thus equivalent to a type-safe function pointer or a callback. While delegates have other uses, the discussion here focuses on the event handling functionality of delegates.
Delegates vs. Interfaces
Delegates and interfaces are similar in that they enable the separation of specification and implementation. Multiple independent authors can produce implementations that are compatible with an interface specification. Similarly, a delegate specifies the signature of a method, and authors can write methods that are compatible with the delegate specification. When should you use interfaces, and when should you use delegates ?
Delegates are useful when:
- A single method is being called.
- A class may want to have multiple implementations of the method specification.
- It is desirable to allow using a static method to implement the specification.
- An event-like design pattern is desired.
- The caller has no need to know or obtain the object that the method is defined on.
- The provider of the implementation wants to "hand out" the implementation of the specification to only a few select components.
- Easy composition is desired.
Interfaces are useful when:
- The specification defines a set of related methods that will be called.
- A class typically implements the specification only once.
- The caller of the interface wants to cast to or from the interface type to obtain other interfaces or classes.
The Delegate class is the base class for delegate types. However, only the system and compilers can derive explicitly from the Delegate class or from the MulticastDelegate class. It is also not permissible to derive a new type from a delegate type. The Delegate class is not considered a delegate type; it is a class used to derive delegate types.
Most languages implement a delegate keyword, and compilers for those languages are able to derive from the MulticastDelegate class; therefore, users should use the delegate keyword provided by the language.
The declaration of a delegate type establishes a contract that specifies the signature of one or more methods.
A delegate is an instance of a delegate type, and references one or more of the following:
- A target object that is not a null reference (Nothing in Visual Basic) and an instance method of the target object
- A static method
- A delegate can reference a method only if the signature of the method exactly matches the signature specified by the delegate type. When a delegate references an instance method, the delegate stores a reference to the method's entry point and a reference to an object, called the target, which is the class instance that the method is invoked on. The target of an instance method cannot be a null reference (Nothing). When a delegate references a static method, the delegate stores a reference to the method's entry point. The target of a static method is a null reference (Nothing).
The invocation list of a delegate is an ordered set of delegates in which each element of the list invokes exactly one of the methods invoked by the delegate. An invocation list can contain duplicate methods. During an invocation, a delegate invokes methods in the order in which they appear in the invocation list. A delegate attempts to invoke every method in its invocation list; duplicates are invoked once for each time they appear in the invocation list. Delegates are immutable; once created, the invocation list of a delegate does not change.
A delegate is either multicast (combinable) or singlecast (noncombinable). Multicast or combinable delegates invoke one or more methods, and can be used in combining operations. Singlecast or noncombinable delegates invoke exactly one method and cannot be used in combining operations. The invocation list of a singlecast delegate A contains a single element: a reference to A.
Combining operations, such as Combine and Remove, do not alter existing delegates. Instead, such an operation returns a new delegate that contains the results of the operation, an unchanged delegate, or a null reference (Nothing). A combining operation returns a null reference (Nothing) when the result of the operation is a delegate that does not reference at least one method. A combining operation returns an unchanged delegate when the requested operation has no effect.
If an invoked method throws an exception, the method stops executing, the exception is passed back to the caller of the delegate, and remaining methods in the invocation list are not invoked. Catching the exception in the caller does not alter this behavior. When the signature of the methods invoked by a delegate includes a return value, the delegate returns the return value of the last element in the invocation list. When the signature includes a parameter that is passed by reference, the final value of the parameter is the result of every method in the invocation list executing sequentially and updating the parameter's value.
Compilers provide two additional methods to the delegate: BeginInvoke and EndInvoke. For more information on these methods, see Asynchronous Programming Overview.
The closest equivalent of a delegate in C or C++ is a function pointer. However, a function pointer can only reference static functions, whereas a delegate can reference both static and instance methods. When the delegate references an instance method, the delegate stores not only a reference to the method's entry point, but also a reference to the class instance for which to invoke the method. Unlike function pointers, delegates are object-oriented and type-safe.
Why bother with the introduction of using delegates? The advantage of the publish / subscribe idiom is that any number of classes can be notified when an event is raised. The subscribing classes do not need to know how the Publisher works, and the Publisher does not need to know what they are going to do in response to the event.
The publisher and the subscribers are decoupled by the delegate. This is highly desirable as it makes for more flexible and robust code. The Publisher and the Subscriber classes spin indepentdently of one another, which makes for code that is easier to maintain.