C# – Delegates

Delegates

  • An object that knows how to call a method.
  • A delegate type defines the kind of method that delegate instances can call (return types and parameter types).
  • Assigning a method to a delegate variable creates a delegate instance which can be invoked same way as method.

A delegate instance acts as a delegate for the caller, the caller invokes the delegate and then the delegate calls the target method.

// A delegate type declaration is like an abstract method declaration, prefixed with the delegate keyword:
delegate int Transformer (int x);

static void Main()
{
	// To create a delegate instance, assign a method to a delegate variable:	
	Transformer t = Square;          // Create delegate instance
	int result = t(3);               // Invoke delegate
        //alternative longhand invoke 
        //int result = t.Invoke (3); 
	Console.WriteLine (result);      // 9
}

static int Square (int x) => x * x;

Writing Plug-in Methods with Delegates

// A delegate variable is assigned a method dynamically. This is useful for writing plug-in methods:

delegate int Transformer (int x);

class Util
{
  public static void Transform (int[] values, Transformer t)
  {
	for (int i = 0; i < values.Length; i++)
	  values[i] = t (values[i]);
  }
}

static void Main()
{
	int[] values = { 1, 2, 3 };
	Util.Transform (values, Square);      // Hook in the Square method
	values.Dump();
	
	values = new int[] { 1, 2, 3 };
	Util.Transform (values, Cube);        // Hook in the Cube method
	values.Dump();
}

static int Square (int x) => x * x;
static int Cube (int x)   => x * x * x;

Multicast Delegates

  • A delegate instance can reference a list of target methods with + or += operators.
  • Delegates gets invoked in the order they are added.
  • It can be removed using the – or -= operators.
  • It is okay to add null value to delegate variable.
  • Delegates are immutable.
  • If the delegate has a nonvoid return type, the method is still invoked but the return values are discarded.
delegate void SomeDelegate();

static void Main()
{
	SomeDelegate d = SomeMethod1;
	d += SomeMethod2;

	d();	
	" -- SomeMethod1 and SomeMethod2 both fired\r\n".Dump();
	
	d -= SomeMethod1;
	d();
	" -- Only SomeMethod2 fired".Dump();
}

static void SomeMethod1 () { "SomeMethod1".Dump(); }
static void SomeMethod2 () { "SomeMethod2".Dump(); }

Multicast delegate example

If a method takes long time to execute, the method could regularly report progress to its caller by invoking a delegate.

public delegate void ProgressReporter (int percentComplete);

public class Util
{
	public static void HardWork (ProgressReporter p)
	{
		for (int i = 0; i < 10; i++)
		{
			p (i * 10);                           // Invoke delegate
			System.Threading.Thread.Sleep (100);  // Simulate hard work
		}
	}
}

static void Main()
{
	ProgressReporter p = WriteProgressToConsole;
	p += WriteProgressToFile;
	Util.HardWork (p);
}

static void WriteProgressToConsole (int percentComplete)
{
	Console.WriteLine (percentComplete);
}

static void WriteProgressToFile (int percentComplete)
{
	System.IO.File.WriteAllText ("progress.txt", percentComplete.ToString());
}

Instance Versus Static Method Targets

When a delegate object is assigned to an instance method, the delegate object must maintain a reference not only to the method, but also to the instance to which the method belongs.

public delegate void ProgressReporter (int percentComplete);

static void Main()
{
	X x = new X();
	ProgressReporter p = x.InstanceProgress;
	p(99);                                 // 99
	Console.WriteLine (p.Target == x);     // True
	Console.WriteLine (p.Method);          // Void InstanceProgress(Int32)
}

class X
{
	public void InstanceProgress (int percentComplete) => Console.WriteLine (percentComplete);
}

Generic Delegate Types

// A delegate type may contain generic type parameters:
public delegate T Transformer (T arg);

// With this definition, we can write a generalized Transform utility method that works on any type:
public class Util
{
	public static void Transform (T[] values, Transformer t)
	{
		for (int i = 0; i < values.Length; i++)
			values[i] = t (values[i]);
	}
}

static void Main()
{
	int[] values = { 1, 2, 3 };
	Util.Transform (values, Square);      // Dynamically hook in Square
	values.Dump();
}

static int Square (int x) => x * x;

The Func and Action Delegates

With the Func and Action family of delegates in the System namespace, you can avoid the need for creating the most custom delegate types.

public class Util
{
	// Define this to accept Func instead of a custom delegate:
	public static void Transform (T[] values, Func transformer)
	{
		for (int i = 0; i < values.Length; i++)
			values[i] = transformer (values[i]);
	}
}

static void Main()
{
	int[] values = { 1, 2, 3 };
	Util.Transform (values, Square);      // Dynamically hook in Square
	values.Dump();	
}

static int Square (int x) => x * x;

Delegates Versus Interfaces

Interface can be used instead of delegates but there is some limitations.

public interface ITransformer
{
	int Transform (int x);
}

public class Util
{
	public static void TransformAll (int[] values, ITransformer t)
	{
		for (int i = 0; i < values.Length; i++)
			values[i] = t.Transform (values[i]);
	}
}

class Squarer : ITransformer
{
	public int Transform (int x) => x * x;
}

public static void Main()
{
	int[] values = { 1, 2, 3 };
	Util.TransformAll (values, new Squarer());
	values.Dump();
}

The interface defines only a single method. Multicast capability is needed and the subscriber needs to implement the interface multiple times.

// With interfaces, we’re forced into writing a separate type per transform
// since Test can only implement ITransformer once:

public interface ITransformer
{
	int Transform (int x);
}

public class Util
{
	public static void TransformAll (int[] values, ITransformer t)
	{
		for (int i = 0; i < values.Length; i++)
			values[i] = t.Transform (values[i]);
	}
}

class Squarer : ITransformer
{
	public int Transform (int x) => x * x;
}

class Cuber : ITransformer
{
	public int Transform (int x) => x * x * x;
}

static void Main()
{
	int[] values = { 1, 2, 3 };
	Util.TransformAll (values, new Cuber());
	values.Dump();
}

Delegate Compatibility

Type compatibility

// Delegate types are all incompatible with each other, even if their signatures are the same:

delegate void D1();
delegate void D2();

static void Main()
{
	D1 d1 = Method1;
	D2 d2 = d1;            // Compile-time error
}

static void Method1() { }
delegate void D1();
delegate void D2();

static void Main()
{
	D1 d1 = Method1;
	D2 d2 = new D2 (d1);	// Legal
}

static void Method1() { }
// Delegate instances are considered equal if they have the same method targets:

delegate void D();

static void Main()
{
	D d1 = Method1;
	D d2 = Method1;
	Console.WriteLine (d1 == d2);         // True
}

static void Method1() { }

Parameter compatibility

// A delegate can have more specific parameter types than its method target. This is called contravariance:

delegate void StringAction (string s);

static void Main()
{
	StringAction sa = new StringAction (ActOnObject);
	sa ("hello");
}

static void ActOnObject (object o) => Console.WriteLine (o);   // hello

Return type compatibility

// A delegate can have more specific parameter types than its method target. This is called contravariance:

delegate object ObjectRetriever();

static void Main()
{
	ObjectRetriever o = new ObjectRetriever (RetriveString);
	object result = o();
	Console.WriteLine (result);      // hello
}

static string RetriveString() => "hello";

Generic delegate type parameter variance

For generic delegate type, it's good practice to:

  • Mark a type parameter used only on the return value as covariant (out).
  • Mark any type parameters used only on parameters as contravariant (in).
/* From C# 4.0, type parameters on generic delegates can be marked as covariant (out) or contravariant (in).

For instance, the System.Func delegate in the Framework is defined as follows:

	public delegate TResult Func();

This makes the following legal:  */

Func<string> x = () => "Hello, world";
Func<object> y = x;

/* The System.Action delegate is defined as follows:

	void Action<in T> (T arg);

This makes the following legal:  */

Action<object> x2 = o => Console.WriteLine (o);
Action<string> y2 = x2;