C# has an object called a delegate which is similar to lambda functions and functional interfaces in Java. A delegate object has a single role - calling a method. This article explores delegates and compares them to similar language constructs in C# and Java.
A delegate type defines the return type and parameters required for a function to be one of its instances1. For example, the following
Combiner delegate must take in two integer arguments and return a single integer.
The following two functions are compatible with
Combiner delegate and these basic functions, I created a basic gambling simulator. The
Gamble method internally assigns the
Subtract functions to the
Combiner delegate type.
Delegates in C# and lambda functions in Java really shine when used with higher order functions. For example, the following code passes a
Transformer delegate as an argument to a
So far, nothing I've shown you about delegates differentiates them from Java lambda functions and functional interfaces. Both delegates and functional interfaces know how to call a function with a certain return value and parameter types. For example, the following Java code maps values in a list with a
Transformer functional interface just like the last example.
If you weren't already aware, functional interfaces in Java are just interfaces with a single method. Java functional interfaces expose what the C# delegate pattern truly is - an interface that knows how to call a method. Delegates are a shortened syntax for a single method interface. You can easily write an interface in C# to perform the same logic as a delegate2.
Well… for the most part. The similarities between delegates, functional interfaces, and interfaces breaks down in regards to multicasting.
Multicasting is one of the most interesting aspects of Delegates in C#. Multicasting is a form of function composition where the delegate invokes multiple functions. Think of multicasting as a way of combining delegate instances together. This pattern makes more sense with an example.
The following code defines a delegate type
TransformerRef and two functions named
These functions use
ref return types and arguments so that the integer value is modified in place (at a single location in memory) instead of creating a new integer. Both can be made instances of the
TransformerRef delegate type.
Now let's make a delegate instance with multicasting. The following delegate instance combines
The above code proved that the value 5 is first increment and then tripled.
Delegate instances with multicasting are easily modified after creation. Let's say I want the
transformer delegate instance to only triple the value. To do this, I simply remove the
Multicasting is a very powerful aspect of delegates in C#. It helps separate them from patterns found in comparable languages like Java.
Similar to how the Java standard library exposes multiple functional interfaces for use, C# provides delegate types in the
System namespace. These types can often be used instead of a custom type. I recreated the commonly used
Reduce functions for the
List type using the
System.Func delegate type. That code is available on GitHub.
One confusing thing about the C# language is the two arrow functions at a developers disposal. Without background knowledge, it's hard to decide which pattern to use in which context, and at worst it feels like feature bloat. Along with delegates, C# also offers lambda functions and local methods. It's important to understand the differences between these three options.
As we've discussed, a delegate knows how to call one or many method(s). It doesn't matter if this method is a standard function, lambda function, or local method. They all work with delegates!
A lambda function is a nameless function that uses the fat arrow syntax
args => result. It can take the place of a standard function in a delegate instance. One of the limitations of lambda functions is that they must be assigned to a delegate type3.
With C# 7.0 (released in 2017), local methods were added. Local methods are only visible in their lexical scope. Unlike lambda functions, local methods don't need to be assigned to a delegate type. This gives them greater flexibility. Otherwise, they use the same fat arrow syntax as lambda functions.
In many cases local methods are the easiest approach since you don't have to define a delegate type. However, in some cases delegate types are needed. For example, only lambda functions can be passed as arguments to other functions.
Delegates are a very interesting design pattern in C#. I think they're an elegant simplification for the single method interface pattern. I look forward to learning more about C# in the coming months. All the code from this article is available on GitHub.