C# delegates, actions, and functions are a powerful tool for writing code that is both readable and maintainable. In this article, we’ll explore how these constructs work and how you can use them to write more efficient and robust code. ..
Delegates, Actions, and Funcs are all C# types focused around a similar concept: storing references in a variable to functions and methods, which can be invoked later on to execute the method it points to. We’ll discuss the differences between the different types, and how to use them.
What Are Delegates?
Delegates are essentially type-safe pointers to functions. The delegate is just the variable, which points towards a method and “delegates” execution to it. The method it points to can be named, like a class member method, or anonymous, like with arrow functions.
Delegates have two main parts: the type definition, and the implementation. The type definition ensures type safety, so if you want to only accept a method that takes a string and returns a bool, you’ll need to define it like a regular function, except with the delegate parameter and no actual code.
Then, you can write an implementation, or many implementations, which have the same return and input parameters, but actually have code in them.
Then, you can use the delegate definition like a type parameter, making a new variable that points to the real function. When you want to call the delegate, you can invoke it like a method, and it pass it the proper parameters. The .NET runtime will look at what method the delegate points to, and invoke that method.
It’s already pretty clear where this is quite useful—if you have multiple functions that all take the same input parameters but need to call different versions depending on, for example, an input enum, you could assign each enum value a delegate, and use that rather than duplicating the logic in a big switch statement.
Delegates don’t need to point towards named methods though, as they’re much more useful when pointing to anonymous functions. These are created with arrow function syntax, () => {}, which defines a function without a name, hence “anonymous.” You can use these just like regular delegates.
Actions and Funcs are simply delegates with specific arguments and return parameters. Actions can take any amount of arguments but return void. Funcs can take any amount of arguments, but always return a value, defined by the last type parameter.
Really, these are just premade delegate definitions with the generic type T. An Action without any types is literally just an empty delegate definition with a void return type.
You’ve probably used Funcs a lot without thinking about it. They’re the functions you pass to enumerable queries like .Where() to select elements. These kinds of Funcs in particular are referred to as Predicates, and a Predicate
You can actually strictly define a Func<int, bool>, assign it a function as a value, and pass it to .Where(), which looks strangely unsettling considering you usually do this inline without the type or variable definition, but it is entirely valid:
You can strictly define Predicate
Delegates have a few other interesting properties. Because they’re just variables, you can store them in Lists and Dictionaries like any other reference type. You could iterate over all delegates in the List, and invoke each one in turn.
You can also multicast delegates, which is a fancy term for saying you can add them together. For example, in the following example, a Greeting delegate is defined, which takes a name and greets the user. Two implementations are defined, Hello that says hello, and Goodbye that says goodbye.
Then, you can quite literally just add them together with the plus sign operator, though take note that this expects a delegate, and not a method group, so you need to wrap the function name in new DelegateDefinition() to have it work properly. This combined delegate executes both elements in the order they were added.
You can subtract delegates, which removes them from the list, though there aren’t multiplication operators for some reason, and division would not make any sense here.