From £99/Month

Understanding Delegates in C#. A Simple Explanation

C# | Programming
Last Updated: October 06, 2019
Asad Butt
Asad Butt

Delegates. Many of us if not all, struggle through them. Topics covered: syntax, Action, Func, Multicast, Anonymous methods, lambda expressions.

img

Table Of Contents



If you are a beginner, Delegates might haunt you.

If you are junior to the mid-level developer, I can understand your frustration. We all go through this. Believe me, you are not alone. (hugs..)

Even if you are at a senior level, I can sense an awkwardness, while you try explaining delegates …

To understand a delegate, we have to first focus a bit onto the basic data types in C# which are separated into value types and reference types. The shortest and neat explanation regarding delegates I ever got was on StackOverflow. Something like:

Hey, I just met you, this is crazy…. here’s my number (Delegate), if around(event), call me maybe ( Callback)

Back to the desk.

The C# type system is classified into reference and value types.

1. Value Types

int, char, byte, float, double, bool enum, DateTime and structs, including user-defined structs. Value types are variables that store the value in them, rather than a reference (information about the location where the value is held) to the storage location. For a complete list of c# value types, check Value types table (C# Reference)

2. Reference Types

Class, Interface, String, Dynamic and delegates. Unlike value types, a reference type doesn’t hold a value itself but holds the information about the location where the variable is stored in the memory.

Delegates

A delegate is a type that represents/holds the reference to a method, provided:

  • the parameter list is similar
  • the return type is similar
  • method is accessible

Three important points to understand about delegates

Delegate is a Type… And it is a Reference Type… And it points to a method. I know it seems very confusing because the typical syntax to declare a Type like a struct or class is very much different than declaring a delegate.

As a delegate is referencing a method, we don’t see any behavioral information like it’s own methods or fields or properties. It does, however, has crucial information about what kind of method, it is happy to point to. Which means, it can point to any method, as long as the values, a method accepts and returns, match the delegate signature.

class Program
{
    static void Main()
    {
        // You can also pass in any Function() that matches the delegate signature
        MyMainMethod(5, 3, AddTwoNumbers);
        MyMainMethod(5, 3, SubtractTwoNumbers);
        MyMainMethod(5, 3, MultiplyTwoNumbers);
        MyMainMethod(5, 3, DivideTwoNumber);
    }

    private static void MyMainMethod(double number1, double number2, Func<double, double, double> funcToCall)
    {
        Console.WriteLine("Startinging computation");
        var totalTime = 0.0;
        var watch = new Stopwatch();
        watch.Start();

        // This is where delegate rock!! Think a need 
        // to call duplicate code with thousands of lines
        var result = funcToCall(number1, number2);

        watch.Stop();
        totalTime = watch.ElapsedMilliseconds;
        Console.WriteLine($"It took {totalTime}, milliseconds to compute result: {result}.");            
    }

    static double AddTwoNumbers(double a, double b) { return a + b; }
    static double SubtractTwoNumbers(double a, double b) { return a - b; }
    static double MultiplyTwoNumbers(double a, double b) { return a * b; }
    static double DivideTwoNumber(double a, double b) { return a / b; }
}

Here is another small program that takes an int[] array of numbers, shortlists and returns the desired collection as an IEnumerable

namespace CSharpFundamentals

{ delegate bool CheckNumberDelegate(int n);

class Program
{
    static bool LessThanFive(int n) { return n < 5; }
    static bool LessThanTen(int n) { return n < 10; }
    static bool GreaterThanFive(int n) { return n > 5; }
    static bool GreaterThanTen(int n) { return n > 10; }

    public static IEnumerable<int> FindNumbers(int[] numbers, CheckNumberDelegate checkNumbersDelegate)
    {
        foreach (int number in numbers)
            if (checkNumbersDelegate(number))
                yield return number;
    }
    static void Main()
    {

        int[] numbers = new[] { 2, 7, 3, 9, 17, 5, 7, 1, 8, 13, 15, 34, 23 };
        IEnumerable<int> result;
        result = FindNumbers(numbers, LessThanFive);
        result = FindNumbers(numbers, LessThanTen);
        result = FindNumbers(numbers, GreaterThanFive);
        result = FindNumbers(numbers, GreaterThanTen);
    }
}

}

Delegates basic syntax

There are three steps to using delegates. These include

A declaration:

public delegate void PrintDelegate(string text);

An instantiation:

PrintDelegate hello = new PrintDelegate(IPrintHello);
PrintDelegate howdy = new PrintDelegate(IPrintHowdy);

An invocation:

 Class MainClass
 {
  static void Main()
  {
    hello();
    // or 
    howdy.Invoke();
  }
 }

But why do we need delegates?

Most people do learn how to implement delegates but scratch their heads for a long while, to learn the purpose/advantages of delegates over calling methods directly.

The practicality of delegates isn’t that clear. When we can call the methods directly, and most of the times we do call them directly? Why delegates?

Why shouldn't we call methods directly or in a combination with control flow (if/else, switch), why get into this delegate hell anyway? The point is, if we are able to pass functions to other functions and generalize code, It can tremendously help eliminate duplicate or nearly-duplicate code, differing only partially in logic.

Why not call methods directly?

It is hard to understand delegates unless we understand the advantages. I’ll try explaining, using two examples.

The first program is a very basic type of calculator. It performs four basic arithmetic function. It takes two value from the user, requests the type of calculation required and prints result. Now a calculator can perform uncountable functions, you name it, on any two variables. We might have a lot of duplicate code, if we implement a separate method for all function, along with all necessary checks on a number, pre-calculation. Never mind the control-flow, using if/else or switch statement for a program performing a large number of calculations.


class Program
{

static void Main() { // You can also pass in any Function() that matches the delegate signature MyMainMethod(5, 3, AddTwoNumbers); MyMainMethod(5, 3, SubtractTwoNumbers); MyMainMethod(5, 3, MultiplyTwoNumbers); MyMainMethod(5, 3, DivideTwoNumber); }

private static void MyMainMethod(double number1, double number2, Func<double, double, double> funcToCall) { Console.WriteLine("Startinging computation"); var totalTime = 0.0; var watch = new Stopwatch(); watch.Start();

// This is where delegate rock!! Think a need 
// to call duplicate code with thousands of lines
var result = funcToCall(number1, number2);

watch.Stop();
totalTime = watch.ElapsedMilliseconds;
Console.WriteLine($"It took {totalTime}, milliseconds to compute result: {result}.");            

}

static double AddTwoNumbers(double a, double b) { return a + b; } static double SubtractTwoNumbers(double a, double b) { return a - b; } static double MultiplyTwoNumbers(double a, double b) { return a * b; } static double DivideTwoNumber(double a, double b) { return a / b; } }


Here is another small program that takes an int[] array of numbers, shortlists and returns the desired collection as an

Embedded content: https://gist.github.com/asdal/f1b74b4dd0dbff05d1ddea655fff7541.js

Delegate | Method And Target

A delegate keeps track of two things. The Method, it is pointing to The Instance which it is invoked upon We can use the available properties to get this information, anytime.

Delegate | Multi Cast / Chaining Multiple delegate objects can be combined into a single instance, and it's the combination of delegates under that instance that becomes a Multicast delegate. A multicast delegate, when invoked, iterates through its list of delegates and invokes them in order. Some Important points to remember. Multicast Delegate won’t return a value for individual delegates in the list, apart from the last delegate in the invocation list. When delegates are combined, a complete list of methods is called. First in First Out(FIFO) order applies. Use ‘+’ or ‘+=’ Operator to add the methods. Use ‘–’ or ‘-=’ to remove methods.

public delegate void IPrintDateTime();

class MultiCastDelegate
{
public void TestMultiasting()
{
IPrintDateTime printDateTime, UtcTimeDelegate, DateTimeNowDelegate;

UtcTimeDelegate = UtcTime;
DateTimeNowDelegate = DateTimeNow;
printDateTime = UtcTimeDelegate + DateTimeNowDelegate;
printDateTime();
}
public void UtcTime() { Console.WriteLine(DateTime.UtcNow); }

public void DateTimeNow() { Console.WriteLine(DateTime.Now); }
}

Delegate | Action

The Action Delegate has the void return type. Important points about Action: It must not return any value. (0-16) parameters allowed. Can be combined with anonymous methods or lambda expressions.

Action<string, int> sampleyAction = (x, y) => Console.WriteLine(x, y);

//Execute Method    
 sampleyAction("Please print: ", 10);

Delegate | Func

The Func Delegate is similar to the Action Delegate, the difference being that Func must return a value and will always require at least one argument. The last argument specified in the list of parameters dictates the return type of the delegate.

class Program
{
public static Func<Object, bool> delegateFunc { get; set; }

static void Main()
{
// check if type is a class
delegateFunc = IsAClass;
Console.WriteLine(delegateFunc(new List<int>()));

// check if type is a delegate
delegateFunc = IsADelegate;
Console.WriteLine(delegateFunc(delegateFunc));
}

static bool IsAClass(Object obj)
{
  return obj.GetType().IsClass;
}
static bool IsADelegate(Object obj)
{
return obj is Delegate;
}
}

Delegate | Anonymous Method

When a delegate is followed by a standard method declaration, the compiler generates an anonymous method. The return type is deduced by the compiler itself. An anonymous method does not belong to a class. It is a code block representing a behavior.


// Method beviour
{ return a - b; };

// Method's parameters list and beaviour
SomeMethod(int a, int b){ return a - b; };

// Method's parameters list, beaviour, 
// followed by delegate keyword
SomeMethod(int a, int b){ return a - b; };

Here is a final anonymous method:

Func<int, int, int> sub = delegate (int a, int b) { return a - b; };
Console.WriteLine(sub(10, 4));  

Delegate | Lambda Expressions

Beginning with C# 3, lambda expressions provide a more concise and expressive way to create an anonymous function.

// Same example as above using Lambda expression

Func<int, int, int> sub = (a, b) => a - b;
var result = sub(10, 5);
Console.WriteLine(result);  // output: 8

Func<int, int, int> sum = (a, b) => a + b;
result = sum(10, 20);
Console.WriteLine(result);  // output: 8

Action<int, int> printSum = (a, b) => Console.WriteLine(a + b);

Final words

Think about delegates as what they can do for you in terms of solving a simple problem, say a calculator app. Your App has to take two number and perform the desired calculation. You have more than 100 functions.

Would you prefer, writing 100+ if/else statements for control-flow? Or use a dictionary or some other collection to store the delegates and call the appropriate method. This was just one of the thousands of ways, we can use delegates.

Delegates provide the developer, a lot more power and flexibility. It also helps them write and maintain and safely extend the existing functionality of classes and methods, without compromising quality.

Originally Published on Medium: C# | Delegates simplified


About the Author:

Asad Likes anything creative, but mainly, developing web-applications, optmizing websites for search engine algorithms and writing about all stuff creative.

He could be reached at LinkedIn or Twitter. He is also an active contributor at stackovrflow.com

Asad Butt

Published Under: C#



Disclaimer: “Whilst we have made every effort to ensure that the information we have provided is accurate, it is not and advice. We cannot accept any responsibility or liability for any errors or omissions. Visit third party sites at your own risk. This article does not constitute legal advice.