Blogs
Understanding Delegates and Functions

Function and Delegates

Delegates… At first, they were confusing when I began my journey into C#. But then I realized how similar they are to TypeScript, especially as I had been using Express for some time. This is one of the reasons I love C# in general — it is very similar to TypeScript.

Additionally, I was also using Java as part of my tech stack, and I already had experience working with Functional Programming and its Streams API (Consumers, BiConsumers, Predicates, etc.). If you share a similar background, then this topic will be fairly smooth for you to learn.

What are delegates?

Delegates are just callbacks. C# includes built-in generic delegate types Func and Action, so that you don’t need to define custom delegates manually in most cases.

C#
1
public delegate TResult Func<in T, out TResult>(T arg);

Examples

You can assign any method to the Func delegate that takes two int parameters and returns an int value. Looks familiar to Typescript and Java right?

C#
1
class Program
2
{
3
static int Sum(int x, int y)
4
{
5
return x + y;
6
}
7
8
static void Main()
9
{
10
Func<int,int, int> add = Sum;
11
12
int result = add(10, 10);
13
14
Console.WriteLine(result);
15
}
16
}
Output
1
20

Let’s break this down to understand how is this possible?

Func with an Anonymous Method

A Func delegate type can include 0 to 16 input parameters of different types. However, it must include an out parameter for the result. For example, the following Func delegate doesn’t have any input parameter, and it includes only an out parameter.

C#
1
Func<int> getRandomNumber = delegate ()
2
{
3
Random rnd = new Random();
4
return rnd.Next(1, 100);
5
};

Func with Lambda Expression

1
Func<int> getRandomNumber = () => new Random().Next(1, 100);
2
3
//Or
4
5
Func<int, int, int> Sum = (x, y) => x + y;

Remeber:

  • Func is built-in delegate type.
  • Func delegate type must return a value.
  • Func delegate type can have zero to 16 input parameters.
  • Func delegate does not allow ref and out parameters.
  • Func delegate type can be used with an anonymous method or lambda expression.

Action Delegate

This type of delegate is a pre-defined generic delegate that points to a method that takes zero or more parameters and returns void

C#
1
public delegate void Print(int val);
2
3
static void ConsolePrint(int i)
4
{
5
Console.WriteLine(i);
6
}
7
8
static void Main()
9
{
10
Print prnt = ConsolePrint;
11
prnt(10);
12
}

You can use an Action delegate instead of defining the above Print delegate, for example:

C#
1
static void ConsolePrint(int i)
2
{
3
Console.WriteLine(i);
4
}
5
6
static void Main()
7
{
8
Action<int> printActionDel = ConsolePrint;
9
printActionDel(10);
10
}
Output
1
10

An Anonymous method can also be assigned to an Action delegate, for example:

C#
1
static void Main()
2
{
3
Action<int> printActionDel = delegate (int i)
4
{
5
Console.WriteLine(i);
6
};
7
8
printActionDel(10);
9
}

A Lambda expression also can be used with an Action delegate:

C#
1
static void Main()
2
{
3
4
Action<int> printActionDel = i => Console.WriteLine(i);
5
6
printActionDel(10);
7
}

Generic Delegate Types

Let’s spice this up. In this example, t will be called on each value. At runtime, t is Square(), hence, Transform(values, Square);

C#
1
public delegate T Transformer<T>(T arg);
2
3
// With this definition, we can write a generalized Transform utility method that works on any type:
4
public class Util
5
{
6
static void Main()
7
{
8
int[] values = { 1, 2, 3 };
9
Transform(values, Square); // Dynamically hook in Square
10
}
11
12
static int Square(int x) => x * x;
13
14
public static void Transform<T>(T[] values, Transformer<T> t)
15
{
16
for (int i = 0; i < values.Length; i++)
17
values[i] = t(values[i]);
18
}
19
}

More Examples

In this example, we can see how we can reuse functions to be called at a later time. This is similar to Currying.

C#
1
class DelegateWithReturnType
2
{
3
public delegate int Multiply(int x, int y);
4
5
private class MathClass
6
{
7
public static int Add(int x, int y)
8
{
9
Console.WriteLine("Addition Method");
10
return x + y;
11
}
12
13
// ...
14
}
15
16
static void MockMain()
17
{
18
Multiply ed = MathClass.Add; // can be called later...
19
int ans = ed(1, 1);
20
Console.WriteLine(ans); // 2
21
}
22
}

Now, let’s consider the following types of delegate functions:

1
GetNewList applyReset = (currentList)
2
=> ResetList(currentList, ref isReset);
3
4
Action<List<string>> applyResetTwo = currentList =>
5
{
6
ResetList(currentList, ref isReset);
7
};

Notice that applyReset is accomplished by this delegate:

1
public delegate List<string> GetNewList(IReadOnlyList<string> sortedList);

We will now use these in this example:

  • Notice how we can pass a callback to WorkloadMethod(). Familiar isn’t it?
  • Think about the delegate as: “You’re taking sortedList and you’re somehow going to get a new list”. That “somehow” is the callback you define.
C#
1
public class DelExample
2
{
3
public delegate List<string> GetNewList(IReadOnlyList<string> sortedList);
4
5
private static bool isReset = false;
6
7
public static List<string> ResetList(IReadOnlyList<string> listToReset, ref bool isReset)
8
{
9
isReset = true;
10
listToReset = new List<string>();
11
return listToReset.ToList();
12
}
13
14
public static List<string> WorkloadMethod(GetNewList getNewList)
15
{
16
List<string> list = ["a", "b"];
17
Console.WriteLine($"Current list:");
18
list.ForEach(Console.Write);
19
20
// Invoke the callback here
21
list = getNewList(list);
22
23
Console.Write($"\nNew list:");
24
list.ForEach(Console.Write);
25
return list;
26
}
27
28
public static void CheckIsReset()
29
{
30
Console.WriteLine(isReset);
31
}
32
33
static void Main()
34
{
35
CheckIsReset();
36
37
// Define the callback here
38
var newList = WorkloadMethod((list => ResetList(list, ref isReset)));
39
40
CheckIsReset();
41
}
42
43
}
Output
1
False
2
Current list:
3
ab
4
5
New list:
6
True
7

Now, let’s consider another example where we mix both delegate and Func:

C#
1
class PassingAnonymousCallbacks
2
{
3
public delegate void PrintSomething(int text);
4
5
private static readonly decimal x = 1;
6
private static readonly decimal y = 1;
7
8
private static decimal CallbackFunc(PrintSomething print, Func<decimal, decimal, decimal> expression)
9
{
10
// inboke print() callback
11
print(1);
12
13
// inboke expression() callback
14
decimal total = expression(x, y);
15
16
return total;
17
}
18
19
static void Main()
20
{
21
// Pass callbacks here
22
decimal result = PassingAnonymousCallbacks.CallbackFunc(PrintNumber, Compute);
23
Console.WriteLine(result);
24
}
25
26
// Your `callbacks` are defined below
27
private static void PrintNumber(int num)
28
=> Console.WriteLine($"Number: {num}");
29
30
private static decimal Compute(decimal x, decimal y)
31
=> x + y;
32
}

Now, you’ve learned the fundamentals on how to use these type of functions.

Advanced Delegates

There are more complex defined delegates, to kind of wrap your understanding behind it - I think if you understand these then it will be easy to understand most delegates out there:

But, first understand this function signature before jumping ahead:

C#
1
public static TResult NormalMethodFunc<T1, T2, TResult>(T1 arg1, T2 arg2, DelegateFuncTwo<T1, DelegateFuncTwo<T2, TResult>> func)
2
{
3
return func(arg1)(arg2);
4
}

Next, understand these and try calling all of these delegates.

C#
1
// Delegates allow you to define custom method signatures, including methods with return types
2
// and parameters. You can define delegates with any method signature, providing flexibility
3
// in scenarios where you need to define custom delegates.
4
// You may change the arg type. If you change the out type of return type they both must match.
5
public delegate TResult DelegateFunc<out TResult>();
6
public delegate TResult DelegateFuncTwo<in T, out TResult>(T arg);
7
public delegate TResult DelegateFuncThree<in T1, in T2, out TResult>(T1 arg1, T2 arg2);
8
public delegate bool DelegateFuncNormal(int arg);
9
public delegate StatusResult DelegateFunc<in T1, out StatusResult>();
10
11
12
public Func<int> func = () => { return 0; };
13
public Func<Employee> funcEmployee = () => default(Employee);
14
public Func<int, int> funcTwo = (arg) => { return 0; };
15
public Func<int, int, double> funcThree = (arg1, arg2) => { return 0.0; };
16
17
// Similarly without `in/out`
18
public delegate TResult DelegateFuncGeneric<TResult>();
19
public delegate int DelegateFuncGenericTwo<T>(T arg); // if you change int to T you're forced to return that generic type!
20
public delegate double DelegateFuncGenericThree<TResult>(double arg1, TResult arg2);
21
public delegate TValue DelegateFuncGenericFour<TKey, TValue>(TKey arg1, TValue arg2);

In this example, we see how we can again use both delegate or Func:

C#
1
public delegate T Transformer<T>(T arg);
2
public static void Transform<T>(T[] values, Transformer<T> t)
3
{
4
for (int i = 0; i < values.Length; i++)
5
{
6
values[i] = t(values[i]);
7
}
8
}
9
10
public static void TransformWithFunc<T>(T[] values, Func<T, T> transformer)
11
{
12
for (int i = 0; i < values.Length; i++)
13
{
14
values[i] = transformer(values[i]);
15
}
16
}

In this example, you can see simillar delegate signatures are defined. .NET has made it very easy to combine syntax from other languages which is why I enjoy using this.

C#
1
class MoreFuncsAndDelegates
2
{
3
// Could use delegate and set it as arrow function (js style)
4
public delegate string StrDelegate(string s);
5
6
private StrDelegate s = (string s) => // func not used
7
{
8
return s + "s";
9
};
10
11
12
private static string strMethod(string s) => s + "s";
13
14
static void MockMain()
15
{
16
17
// anonymous function
18
var s = (string s) =>
19
{
20
return s + "s";
21
};
22
23
// same but more explicit of return type anonymous function
24
var sTwo = string (string s) =>
25
{
26
return s + "s";
27
};
28
29
var newStr = s("t");
30
Console.WriteLine(newStr); // ts
31
32
// Could call the anonymous func
33
var newStrTwo = s("g");
34
Console.WriteLine(newStrTwo); // gs
35
36
// Could assign the func
37
StrDelegate strDelegateCall = strMethod;
38
var newStThree = strDelegateCall("j");
39
Console.WriteLine(newStThree); // js
40
}
41
}

Anotehr example:

C#
1
class FuncAndActionDelegates
2
{
3
public delegate TResult DelegateFuncTwo<in T, out TResult>(T arg);
4
5
private static DelegateFuncTwo<string, int> StringLength = (string input) =>
6
{
7
return input.Length;
8
};
9
10
public static TResult ProcessWithDelegate<T, TResult>(Action<T> logProcess, DelegateFuncTwo<T, TResult> func, T input)
11
{
12
logProcess(input);
13
14
return func(input);
15
}
16
17
public static void LogProcess<T>(T input)
18
=> Console.WriteLine("logging some stuff... {0}", input);
19
20
static void Main()
21
{
22
int length = ProcessWithDelegate(LogProcess, StringLength, "Hello!");
logging some stuff... Hello! 6
23
24
Console.WriteLine(length);
25
}
26
}

I hope this helps!

x, gio