Introduction
In this article I am going to describe some confusing terms including Func, Action, Predicate, lambda expression and delegate and their differences. Although the introduction of generics and lambda expressions added a bit to the complexity of these terms, they are all the same, Delegates.
This helps you to have a better understanding of these terms and could possibly solve complex interactions between objects and collections.
Background
Delegate has existed in the programming world since a long time ago, however throughout the time many forms of delegates have been added to .Net for the purpose of creating more ways to solve problems.
Let's start with a simple definition of delegates. A delegate is a reference to a piece of code that does something!, exactly like a method. It has arguments as input and results as output.
delegate(int x) {
return x == 0;
}
If we replace the delegate keyword with
public bool FunctionName
then it would become
public bool FunctionName(int x){
return x == 0;
}
so basically in one form it could be a function without a name and more technically an anonymous function. A delegate can also have a name like below and it refers to a method with the same signature.
public bool delegate Name(int x);
and then this could be associated with an event so that other objects can subscribe to it and be notified whenever the event is fired (which is out of scope for this article).
Now that we know what a delegate is, let's use it somewhere exciting such as LINQ and collections. Say we have a collection like this:
var collection = new List<int>();
collection.Add(1);
collection.Add(2);
collection.Add(3);
If we want to filter the elements of this collection, we normally write something like:
collection.Where(x => x == 2);
I will get back to lambda expressions and explain them as part of this article.
Delegate
Now what if I want to describe the condition inside parenthesis with a delegate. Remember a delegate is supposed to reference to a piece of code that does something so basically like a method. So why not comparing some elements and filter them for us, like the lambda expression that I talked about. So what if I write something like:
delegate(int item)
{
if(item == 2)
{
return true;
}
return false;
}
and subsequently:
collection.Where(delegate(int item)
{
if (item == 2)
{
return true;
}
return false;
});
How was that? So simply this was a delegate which was taking an integer as an input and was returning a boolean as output.
Func
Another form of a delegate is Func. Func could have any number and type of input arguments and could return any type of output, exactly like a delegate. The signature is something like:
Func<int, bool> condition = delegate(int item)
{
if(item == 2)
{
return true;
}
return false;
};
Because Func could return any type, we need to mention the return type in the signature (in this case the bool, which is literrally the last argument in the generic definition.
Func<int, bool> condition = deleg...
And I can write:
var result = collection.Where(condition);
Predicate
If a Func only returns a bool, that would have another name called a Predicate, just to confuse us a bit more. So a predicate is a delegate that always returns a boolean with any number and type of inputs. Therefore:
Predicate<int> predicate = delegate(int item)
{
if(item == 2)
{
return true;
}
return false;
};
However we need to convert this predicate to a Func if we want to use it like before:
Func<int, bool> condition = new Func<int, bool>(predicate);
collection.Where(condition);
But there are situations that we could use a predicate directly:
collection.Find(predicate);
Best Practice: Use Func, Lambda Expressions and Delegates instead of Predicates.
Action
Now what if my Func didn't return any value, what would be that called then? Yes! an Action. So basically an Action is a delegate that does something without any return value. Something like:
Action<int> action = delegate(int item)
{
item += 2;
}
Can I use this action with the previous example of filtering? Not really, because the argument to the Where method should return a boolean to evaluate whether that item should be included in the result or not, right? So Where can I use an Action?
The answer is whenever I wanted to do an operation without any return value. like:
collection.ForEach(action);
So this will run for every item in the collection and adds 2 to each item. However note that although this action is run for each item in the collection, in this case the change is not applied because we are modifying a collection using a LINQ for each loop which is not allowed. But it was a Reference type, properties of that type could be changed in a for each loop, like the samples below.
Lambda Expression
Now I'd like to define another synonym for a delegate, a Lambda Expression. A lambda expression can be in the form of a Func or an Action, and therefor inherently it's a delegate.
So the replacement for the Where argument could be:
Predicate<int> condition = (int x) => x == 2;
Func<int, bool> condition = (int x) => x == 2;
and for the ForEach method I can write:
Action<int> action = (int item) => item += 2;
And that's it. We are there, with all the confusion gone. I have written some samples below for each of these terms in a more complicated fashion. Hope it helps.
Samples
public class Customer
{
public string Name { get; set; }
public string Telephone { get; set; }
}
var customers = new List<Customers>();
customers.Add(new Customer() {
Name = "cust A",
Telephone = "123"
});
​customers.Add(new Customer() {
Name = "cust B",
Telephone = "456"
});
​customers.Add(new Customer() {
Name = "cust C",
Telephone = "789"
});
1. Delegate
customers.Where(delegate(Customer cust) {
return cust.Name == "cust B";
});
2. Func
Func<Customer, bool> filter = delegate(Customer cust){
return cust.Name == "cust B";
};
customers.Where(filter);
3. Predicate
Predicate<Customer> filter = delegate(Customer cust){
return cust.Name == "cust B";
};
customers.Find(filter);
4. Lambda
Func<Customer, bool> filter = (Customer cust) => cust.Name == "cust B";
customers.Where(filter);
5. Action
We can't use Actions here because it doesn't return any value let alone bool.
Now lets play with ForEach method, instead of filtering data:
1. Delegate
customers.ForEach(delegate(Customer cust){
cust.Name = "cust B name Modified !!!";
});
2. Func
We can't use Func here, as Func has to always return something.
3. Predicate
We can't use predicate here as well, as it always has to return boolean.
4. Lambda
customers.ForEach(cust => cust.Name = "cust B Modified !!!");
5. Action
Action<Customer> filter = (Customer cust) => cust.Name = "cust B Modified !!!";
customers.ForEach(filter);
*) Now a key question! why the following code snippet has a compilation error:
var simpleDelegate = delegate(int x) {
return x == 0;
};
The answer is, because as you saw, there are different structures (predicate, func, delegate) that could be associated to this delegate, and the compiler doesn't know which one to pick J.