Skip to content

1.4 Delegate

1. Simple Delegates

A delegate in C# is a type that represents references to methods with a particular parameter list and return type. Delegates are used to pass methods as arguments to other methods. Here’s a simple example to illustrate how delegates work:

Example

using System;
namespace DelegateExample
{
// Step 1: Define a delegate
public delegate void SimpleDelegate(string message);
class Program
{
static void Main(string[] args)
{
// Step 2: Instantiate the delegate and assign a method to it
SimpleDelegate del = new SimpleDelegate(PrintMessage);
// Step 3: Call the delegate
del("Hello, this is a simple delegate example!");
// Alternatively, using method group conversion (no need to use 'new' keyword)
SimpleDelegate del2 = PrintMessage;
del2("This is another message using method group conversion.");
Console.ReadLine();
}
// A method that matches the delegate signature
public static void PrintMessage(string message)
{
Console.WriteLine(message);
}
}
}
  1. Delegate Definition (SimpleDelegate):

    • The SimpleDelegate is defined to match any method that takes a single string parameter and returns void.
  2. Delegate Instantiation (del):

    • A delegate instance del is created and is assigned the PrintMessage method. This method matches the signature of the delegate.
  3. Calling the Delegate (del("Hello, this is a simple delegate example!")):

    • The delegate is called like a regular method. When the delegate is invoked, it calls the method assigned to it, passing the argument "Hello, this is a simple delegate example!" to the PrintMessage method.
  4. Method Group Conversion:

    • Alternatively, you can assign a method to a delegate directly without using the new keyword, which is known as method group conversion.
Output

When you run this program, the output will be:

Hello, this is a simple delegate example!
This is another message using method group conversion.

2. Aysnc Delegate

The BeginInvoke and EndInvoke methods in .NET are used to asynchronously execute a delegate. Although the introduction of async and await in C# made these patterns less common, understanding them is useful for working with legacy code.

Here’s a simple example demonstrating how to use BeginInvoke and EndInvoke with a delegate:

Define a Delegate

First, define a delegate that represents a method signature.

using System;
namespace BeginInvokeExample
{
// Define a delegate with the desired signature
public delegate int AddDelegate(int a, int b);
class Program
{
static void Main(string[] args)
{
// Instantiate the delegate
AddDelegate addDelegate = new AddDelegate(Add);
// BeginInvoke starts the asynchronous call
IAsyncResult asyncResult = addDelegate.BeginInvoke(10, 20, AddCompleted, null);
// Do other work while the Add method runs asynchronously...
Console.WriteLine("Doing other work...");
// Optionally, wait for the asynchronous call to complete
int result = addDelegate.EndInvoke(asyncResult);
Console.WriteLine($"Result: {result}");
Console.ReadLine();
}
// The method that matches the delegate signature
static int Add(int a, int b)
{
// Simulate some work
System.Threading.Thread.Sleep(2000);
return a + b;
}
// The callback method that is called when the async operation completes
static void AddCompleted(IAsyncResult asyncResult)
{
Console.WriteLine("Addition operation completed.");
}
}
}
  1. Delegate Definition (AddDelegate): A delegate AddDelegate is defined to represent methods that take two integers and return an integer.

  2. Delegate Instantiation (addDelegate): An instance of the AddDelegate is created and assigned the Add method, which matches the delegate signature.

  3. Asynchronous Call (BeginInvoke):

    • The BeginInvoke method starts the asynchronous execution of the Add method. It takes the parameters required by the Add method, a callback method (AddCompleted), and an optional state object (passed as null here).
    • BeginInvoke immediately returns an IAsyncResult, allowing the main thread to continue executing other code while the Add method runs in the background.
  4. Callback (AddCompleted):

    • The AddCompleted method is a callback that runs when the asynchronous operation completes. Here, it simply prints a message indicating that the operation is done.
  5. EndInvoke:

    • The EndInvoke method is used to retrieve the result of the asynchronous operation. It blocks until the operation is complete if it hasn’t finished yet.
Output

When you run the program, the output will be:

Doing other work...
Addition operation completed.
Result: 30

3. AyncResult

The AsyncResult pattern was used in .NET for handling asynchronous operations before the introduction of async and await. It involves implementing the IAsyncResult interface and is often seen with the Begin/End method pair. Here’s a simple example that demonstrates how to use AsyncResult:

3.1 Example

Create a Simple Example Using the IAsyncResult Interface:

using System;
using System.Threading;
namespace AsyncResultExample
{
// A simple class to hold the state information.
public class MyAsyncResult : IAsyncResult
{
private readonly ManualResetEvent _waitHandle;
private readonly object _state;
public MyAsyncResult(object state)
{
_state = state;
_waitHandle = new ManualResetEvent(false);
}
// Properties required by IAsyncResult
public object AsyncState => _state;
public WaitHandle AsyncWaitHandle => _waitHandle;
public bool CompletedSynchronously { get; private set; }
public bool IsCompleted { get; private set; }
// Method to signal that the operation is complete
public void Complete(bool completedSynchronously)
{
CompletedSynchronously = completedSynchronously;
IsCompleted = true;
_waitHandle.Set();
}
}
public class MyAsyncClass
{
// Begin method that starts the asynchronous operation
public IAsyncResult BeginOperation(string data, AsyncCallback callback, object state)
{
var asyncResult = new MyAsyncResult(state);
// Simulate an asynchronous operation using a thread
ThreadPool.QueueUserWorkItem(_ =>
{
// Simulate work
Thread.Sleep(2000);
// Operation is complete
asyncResult.Complete(false);
// Invoke the callback
callback?.Invoke(asyncResult);
});
return asyncResult;
}
// End method that completes the asynchronous operation
public string EndOperation(IAsyncResult result)
{
var myResult = (MyAsyncResult)result;
// Wait for the operation to complete if it hasn't already
myResult.AsyncWaitHandle.WaitOne();
return $"Operation completed with state: {myResult.AsyncState}";
}
}
class Program
{
static void Main()
{
var myAsyncClass = new MyAsyncClass();
// Start the asynchronous operation
var result = myAsyncClass.BeginOperation("Hello, AsyncResult!", MyCallback, "User State");
// Do other work while the async operation is running...
// Wait for the operation to complete
string operationResult = myAsyncClass.EndOperation(result);
Console.WriteLine(operationResult);
Console.ReadLine();
}
// The callback method
static void MyCallback(IAsyncResult result)
{
Console.WriteLine("Callback invoked.");
}
}
}
  • MyAsyncResult: This class implements the IAsyncResult interface and holds the state information. It includes the AsyncState, AsyncWaitHandle, CompletedSynchronously, and IsCompleted properties, which are required by IAsyncResult.

  • BeginOperation: This method starts the asynchronous operation. It creates a MyAsyncResult instance and simulates an operation using ThreadPool.QueueUserWorkItem.

  • EndOperation: This method is called to retrieve the result of the asynchronous operation. It ensures that the operation is complete before returning the result.

  • Callback Method: The callback method (MyCallback) is invoked once the asynchronous operation is complete. It receives the IAsyncResult instance as a parameter.

Output

When you run the program, it will:

  1. Start the asynchronous operation.
  2. Invoke the callback method when the operation is complete.
  3. Retrieve and print the result using the EndOperation method.

In the example above where asyncResult.Complete(false); was used, this line of code serves to mark the asynchronous operation as complete and to indicate whether the operation was completed synchronously or asynchronously.

3.1.1 Explanation of Complete(false)

The Complete(bool completedSynchronously) method in the MyAsyncResult class is designed to:

  1. Mark the Operation as Complete: By setting IsCompleted to true and signaling the ManualResetEvent (using _waitHandle.Set()), this method informs any waiting threads or callback methods that the asynchronous operation has finished.

  2. Indicate Whether the Operation Was Completed Synchronously: The completedSynchronously parameter is a bool that specifies whether the operation was completed synchronously (true) or asynchronously (false).

    • If completedSynchronously is true, it means that the operation was completed on the same thread that initiated the operation, without any delay.
    • If completedSynchronously is false, it means that the operation was completed asynchronously, possibly on a different thread.

3.1.2 Why Complete(false)?

In the provided example:

  • The asynchronous operation is simulated using ThreadPool.QueueUserWorkItem, which runs the operation on a different thread.
  • Therefore, the operation is completed asynchronously, so completedSynchronously is set to false in asyncResult.Complete(false);.

Key Points

  • Synchronous vs. Asynchronous Completion: The distinction between synchronous and asynchronous completion is important for consumers of the IAsyncResult interface. Some consumers might handle synchronous completions differently, such as skipping the need to call EndInvoke immediately because the result is already available.

  • Callback Handling: When Complete(false) is called, it ensures that the callback provided in the BeginOperation method is invoked after the asynchronous operation is complete, allowing the main thread or other threads to handle the result as needed.