Luna Tech

Tutorials For Dummies.

C# Delegates

2021-12-05


1. What are Delegates?

Delegate References

A delegate is a type safe function pointer, it holds a reference (pointer) to a function.

Delegate syntax looks similar to a method with a delegate keyword.

Example

We first declare a delegate, then we create a delegate instance, the parameter should be a method with the delegate signature.

Finally we invoke the delegate by passing the parameters defined in the delegate signature.

void Main()
{	
	HelloFuncDelegate del = new HelloFuncDelegate(Hello);
	del("Hello from Delegate");
}

public delegate void HelloFuncDelegate(string Message);

public static void Hello(string strMessage)
{
	Console.WriteLine(strMessage);
}

A delegate is similar to a class, you can create an instance of it, and when you do so, you pass in the function name as a parameter to the delegate constructor, and it is to this function the delegate will point to.

Type Safety

Noticing the delegate signature is the same as the Hello method signature, if we change one of the signatures, the delegate cannot be instantiated (compile error).

The signature of the delegate should match the signature of the function to which it points.


2. When to use Delegates?

You might be wondering, why don’t we just call that method? Why do we need a pointer?

Delegate is heavily used by framework developers to make reusable methods.

If you want to pass a method as a parameter to another method, you want to use delegate.

Example

You can decouple the logic from the employee class to the delegate method and you can easily replace that logic by changing the delegate method, thus achieve flexibility.

void Main()
{
	List<Employee> empList = new List<Employee>();
	empList.Add(new Employee(){ID = 1, Name = "A", Salary = 10000, Experience = 5});
	empList.Add(new Employee(){ID = 2, Name = "B", Salary = 20000, Experience = 3});
	
	Employee.PromoteWithoutDelegate(empList);
	
	IsPromotable isPromotable = new IsPromotable(Promote);
	Employee.PromoteWithDelegate(empList, isPromotable);
}

public delegate bool IsPromotable(Employee emp);

public static bool Promote(Employee emp)
{
	return emp.Experience >= 5;
}

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public int Salary { get; set; }
	public int Experience { get; set; }

	public static void PromoteWithoutDelegate(List<Employee> empList)
	{
		foreach (Employee emp in empList)
		{
			if (emp.Experience >= 5)
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}

	public static void PromoteWithDelegate(List<Employee> empList, IsPromotable promotable)
	{
		foreach (Employee emp in empList)
		{
			if (promotable(emp))
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}
}

3. Other Related Concepts

  1. Anonymous Methods (From C# 2)
  2. Lambda Expressions (From C# 3)
  3. Generic Func Delegate (0-16 inputs, 1 generic output)
  4. Generic Action Delegate (0-16 inputs, 0 output)
  5. Generic Predicate Delegate (1 input, 1 bool output)

These can be used to make your delegate code simpler. Generic Func, Action, Predicate are just pre-made delegates for us to use.

1. Anonymous Methods

Anonymous Methods Reference

A method without a name, they provide us a way of creating delegate instances without having to write a separate method.

Employee.PromoteWithDelegate(empList, delegate (Employee emp) {
  return emp.Experience >= 5;
});
delegate bool IsPromotable(Employee emp);
void Main()
{
	List<Employee> empList = new List<Employee>();
	empList.Add(new Employee(){ID = 1, Name = "A", Salary = 10000, Experience = 5});
	empList.Add(new Employee(){ID = 2, Name = "B", Salary = 20000, Experience = 3});
	
	Employee.PromoteWithoutDelegate(empList);

	Employee.PromoteWithDelegate(empList, delegate (Employee emp)
	{
		return emp.Experience >= 5;
	});
}

public delegate bool IsPromotable(Employee emp);

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public int Salary { get; set; }
	public int Experience { get; set; }

	public static void PromoteWithoutDelegate(List<Employee> empList)
	{
		foreach (Employee emp in empList)
		{
			if (emp.Experience >= 5)
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}

	public static void PromoteWithDelegate(List<Employee> empList, IsPromotable promotable)
	{
		foreach (Employee emp in empList)
		{
			if (promotable(emp))
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}
}

Anonymous functions are also useful to subscribe for an event handler, since delegate can be used for callback function, the anonymous functions are just a simpler way of writing the delegate callbacks, nothing fancy here.

2. Lambda Expressions

Lambda Expressions Reference

=> is called lambda operator, reads as GOES TO.

You can combine the delegate initialization and the delegate method together as a lambda expression. In this way, you just need to declare the delegate signature.

Employee.PromoteWithDelegate(empList, emp => emp.Experience >= 5);
delegate bool IsPromotable(Employee emp);
void Main()
{
	List<Employee> empList = new List<Employee>();
	empList.Add(new Employee(){ID = 1, Name = "A", Salary = 10000, Experience = 5});
	empList.Add(new Employee(){ID = 2, Name = "B", Salary = 20000, Experience = 3});
	
	Employee.PromoteWithoutDelegate(empList);

	Employee.PromoteWithDelegate(empList, emp => emp.Experience >= 5);
}

public delegate bool IsPromotable(Employee emp);

public class Employee
{
	public int ID { get; set; }
	public string Name { get; set; }
	public int Salary { get; set; }
	public int Experience { get; set; }

	public static void PromoteWithoutDelegate(List<Employee> empList)
	{
		foreach (Employee emp in empList)
		{
			if (emp.Experience >= 5)
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}

	public static void PromoteWithDelegate(List<Employee> empList, IsPromotable promotable)
	{
		foreach (Employee emp in empList)
		{
			if (promotable(emp))
			{
				Console.WriteLine(emp.Name + " promoted");
			}
		}
	}
}

The input type can be omitted because it is inferred, e.g., Employee emp => emp.Experience >= 5 can be shorten as emp => emp.Experience >= 5

Lambda expressions are also useful to create expression trees, e.g., LINQ.

lambda vs anonymous methods

Most of the cases, go for Lambda, unless you want to omit the parameters.

Anonymous methods allow you to omit parameters but lambda cannot.

3. Func<> - Generic Func Delegate

Func<T, TResult> is just a generic delegate, we can replace the generic types with a specific type.

With this built-in Generic Func, we don’t need to create our own delegate function.

Func<Employee, string> means the input parameter type is Employee and it returns a string.

Func<Employee, string> selector = emp => "Name = " + emp.Name;
IEnumerable<string> names = empList.Select(selector);

Rewrite in anonymous methods

IEnumerable<string> names = empList.Select(delegate(Employee emp){
  return "Name = " + emp.Name;
});

Rewrite in lambda expressions

IEnumerable<string> names = empList.Select(emp => "Name = " + emp.Name);

Func<> vs lambda expressions

They are two ways of writing the same logic, the lambda syntax is preferred.

Func<> with more than 1 input parameters

It can have 0 to 16 input params, must have 1 output parameter.

Func<int, int, string> means it accepts 2 int input and returns a string output.

4. Action<> - Generic Action Delegate

No output, only input.

5. Predicate<> - Generic Predicate Delegate

It represents a method that contains a set of criteria and checks whether the passed parameter meets those criteria or not.

A predicate delegate method must take one input parameter and it then returns a boolean value.

Example

In this example, the Find method accepts a Predicate, and the Predicate is a generic delegate, so we need to create a method FindEmployee with the signature of the Predicate delegate.

Then we create the empPredicate, pass in the delegate method we just created as an argument to the delegate constructor.

Finally, we use the predicate as argument in the Find method.

// step 2
Predicate<Employee> empPredicate = new Predicate<Employee>(FindEmployee);

// step 3
Employee emp = empList.Find(emp => empPredicate(emp));

// step 1
public static bool FindEmployee(Employee emp){
  return emp.ID == 1;
}

Rewrite in anonymous methods

Employee emp = empList.Find(delegate(Employee emp){
  return emp.ID == 1;
});

Rewrite in lambda expressions

Employee emp = empList.Find(emp => emp.ID == 1);

4. Delegate Pros and Cons

Advantages

Disadvantages