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.
1public 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?
1class Program2{3static int Sum(int x, int y)4{5return x + y;6}78static void Main()9{10Func<int,int, int> add = Sum;1112int result = add(10, 10);1314Console.WriteLine(result);15}16}
120
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.
1Func<int> getRandomNumber = delegate ()2{3Random rnd = new Random();4return rnd.Next(1, 100);5};
Func with Lambda Expression
1Func<int> getRandomNumber = () => new Random().Next(1, 100);23//Or45Func<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
andout
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
1public delegate void Print(int val);23static void ConsolePrint(int i)4{5Console.WriteLine(i);6}78static void Main()9{10Print prnt = ConsolePrint;11prnt(10);12}
You can use an Action
delegate instead of defining the above Print
delegate, for example:
1static void ConsolePrint(int i)2{3Console.WriteLine(i);4}56static void Main()7{8Action<int> printActionDel = ConsolePrint;9printActionDel(10);10}
110
An Anonymous method can also be assigned to an Action delegate, for example:
1static void Main()2{3Action<int> printActionDel = delegate (int i)4{5Console.WriteLine(i);6};78printActionDel(10);9}
A Lambda expression also can be used with an Action
delegate:
1static void Main()2{34Action<int> printActionDel = i => Console.WriteLine(i);56printActionDel(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);
1public delegate T Transformer<T>(T arg);23// With this definition, we can write a generalized Transform utility method that works on any type:4public class Util5{6static void Main()7{8int[] values = { 1, 2, 3 };9Transform(values, Square); // Dynamically hook in Square10}1112static int Square(int x) => x * x;1314public static void Transform<T>(T[] values, Transformer<T> t)15{16for (int i = 0; i < values.Length; i++)17values[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.
1class DelegateWithReturnType2{3public delegate int Multiply(int x, int y);45private class MathClass6{7public static int Add(int x, int y)8{9Console.WriteLine("Addition Method");10return x + y;11}1213// ...14}1516static void MockMain()17{18Multiply ed = MathClass.Add; // can be called later...19int ans = ed(1, 1);20Console.WriteLine(ans); // 221}22}
Now, let’s consider the following types of delegate
functions:
1GetNewList applyReset = (currentList)2=> ResetList(currentList, ref isReset);34Action<List<string>> applyResetTwo = currentList =>5{6ResetList(currentList, ref isReset);7};
Notice that applyReset
is accomplished by this delegate
:
1public 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 takingsortedList
and you’re somehow going to get a new list”. That “somehow” is the callback you define.
1public class DelExample2{3public delegate List<string> GetNewList(IReadOnlyList<string> sortedList);45private static bool isReset = false;67public static List<string> ResetList(IReadOnlyList<string> listToReset, ref bool isReset)8{9isReset = true;10listToReset = new List<string>();11return listToReset.ToList();12}1314public static List<string> WorkloadMethod(GetNewList getNewList)15{16List<string> list = ["a", "b"];17Console.WriteLine($"Current list:");18list.ForEach(Console.Write);1920// Invoke the callback here21list = getNewList(list);2223Console.Write($"\nNew list:");24list.ForEach(Console.Write);25return list;26}2728public static void CheckIsReset()29{30Console.WriteLine(isReset);31}3233static void Main()34{35CheckIsReset();3637// Define the callback here38var newList = WorkloadMethod((list => ResetList(list, ref isReset)));3940CheckIsReset();41}4243}
1False2Current list:3ab45New list:6True7
Now, let’s consider another example where we mix both delegate
and Func
:
1class PassingAnonymousCallbacks2{3public delegate void PrintSomething(int text);45private static readonly decimal x = 1;6private static readonly decimal y = 1;78private static decimal CallbackFunc(PrintSomething print, Func<decimal, decimal, decimal> expression)9{10// inboke print() callback11print(1);1213// inboke expression() callback14decimal total = expression(x, y);1516return total;17}1819static void Main()20{21// Pass callbacks here22decimal result = PassingAnonymousCallbacks.CallbackFunc(PrintNumber, Compute);23Console.WriteLine(result);24}2526// Your `callbacks` are defined below27private static void PrintNumber(int num)28=> Console.WriteLine($"Number: {num}");2930private 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:
1public static TResult NormalMethodFunc<T1, T2, TResult>(T1 arg1, T2 arg2, DelegateFuncTwo<T1, DelegateFuncTwo<T2, TResult>> func)2{3return func(arg1)(arg2);4}
Next, understand these and try calling all of these delegates.
1// Delegates allow you to define custom method signatures, including methods with return types2// and parameters. You can define delegates with any method signature, providing flexibility3// 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.5public delegate TResult DelegateFunc<out TResult>();6public delegate TResult DelegateFuncTwo<in T, out TResult>(T arg);7public delegate TResult DelegateFuncThree<in T1, in T2, out TResult>(T1 arg1, T2 arg2);8public delegate bool DelegateFuncNormal(int arg);9public delegate StatusResult DelegateFunc<in T1, out StatusResult>();101112public Func<int> func = () => { return 0; };13public Func<Employee> funcEmployee = () => default(Employee);14public Func<int, int> funcTwo = (arg) => { return 0; };15public Func<int, int, double> funcThree = (arg1, arg2) => { return 0.0; };1617// Similarly without `in/out`18public delegate TResult DelegateFuncGeneric<TResult>();19public delegate int DelegateFuncGenericTwo<T>(T arg); // if you change int to T you're forced to return that generic type!20public delegate double DelegateFuncGenericThree<TResult>(double arg1, TResult arg2);21public delegate TValue DelegateFuncGenericFour<TKey, TValue>(TKey arg1, TValue arg2);
In this example, we see how we can again use both delegate
or Func
:
1public delegate T Transformer<T>(T arg);2public static void Transform<T>(T[] values, Transformer<T> t)3{4for (int i = 0; i < values.Length; i++)5{6values[i] = t(values[i]);7}8}910public static void TransformWithFunc<T>(T[] values, Func<T, T> transformer)11{12for (int i = 0; i < values.Length; i++)13{14values[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.
1class MoreFuncsAndDelegates2{3// Could use delegate and set it as arrow function (js style)4public delegate string StrDelegate(string s);56private StrDelegate s = (string s) => // func not used7{8return s + "s";9};101112private static string strMethod(string s) => s + "s";1314static void MockMain()15{1617// anonymous function18var s = (string s) =>19{20return s + "s";21};2223// same but more explicit of return type anonymous function24var sTwo = string (string s) =>25{26return s + "s";27};2829var newStr = s("t");30Console.WriteLine(newStr); // ts3132// Could call the anonymous func33var newStrTwo = s("g");34Console.WriteLine(newStrTwo); // gs3536// Could assign the func37StrDelegate strDelegateCall = strMethod;38var newStThree = strDelegateCall("j");39Console.WriteLine(newStThree); // js40}41}
Anotehr example:
1class FuncAndActionDelegates2{3public delegate TResult DelegateFuncTwo<in T, out TResult>(T arg);45private static DelegateFuncTwo<string, int> StringLength = (string input) =>6{7return input.Length;8};910public static TResult ProcessWithDelegate<T, TResult>(Action<T> logProcess, DelegateFuncTwo<T, TResult> func, T input)11{12logProcess(input);1314return func(input);15}1617public static void LogProcess<T>(T input)18=> Console.WriteLine("logging some stuff... {0}", input);1920static void Main()21{22int length = ProcessWithDelegate(LogProcess, StringLength, "Hello!");logging some stuff... Hello! 62324Console.WriteLine(length);25}26}
I hope this helps!
x, gio