funk-neon-sign-brick-wall-background-87058206.jpg

Two of the three break-out talks on Tuesday were with Jeremy Clark (@jeremybytes) and since the two talks bridge each other nicely, I've decided to make this blog post a distillation of those two talks. 

The first talk was about Func and Action, which was something I was not quite as comfortable with, but the talk gave a few aha-moments, that made things fall into place. The second talk was about a concept that we use frequently as developers: lambdas. I had a fairly good grasp of how they work but put together with the knowledge from the first talk, I got a much deeper understanding of how to use lambdas.

Func and Action

Func and Action are both constructs that build on delegates. But instead of declaring the delegate and hooking it up manually, we can do it all with a Func/Action. They are pretty much the same thing, except that Func returns a value; an Action does not. To determine which input (and output for a Func), we can take a look at the signature of the delegate. 

Action<int, string> for example takes two arguments. The first is an int and the second is a string. 

Func<int, string> takes an int argument and returns a string. Func<int, int, string> takes two int arguments and returns a string. In short, the last type in the brackets is the return type and all the preceding types are the input parameters.

The cool thing about Func and Action is that you can have a method that takes a Func/Action and this way can defer the behavior until runtime. An example of this could be an overload of the ToString method in a class.

public class Person

{

    public string FirstName { get; set; }

    public string LastName { get; set; }

 

    public string ToString(Func<Person, string> formatter)

    {

        return formatter(this);

    }

}

To use this we can call the ToString method on a Person object with either a delegate with the Func<Person, string> signature or an anonymous lambda like this:

var person = new Person(){FirstName = "John", LastName = "Johnson" };

Console.WriteLine(person.ToString(p => $"{p.FirstName} {p.LastName}"));

This method of passing in some logic (or another type of dependency) is called "method dependency injection". It can be used in many different ways. Jeremy Clark has a good little example of this in his sample code 

In his example he has a GetFormatter() method, that will return the correct Func, based on which type of formatting has been chosen in the UI.

Lambdas

We use lambdas very often as C# programmers. The most common scenario is when we're working on IEnumerable<T>.  When you use LINQ you use lambdas! LINQ is essentially a set of extension methods on IEnumerable<T>, that provide a lot of functionality in a very succinct syntax. If we look a little closer on the signature of a LINQ method, it is a Func or an Action. Usually we use an anonymous delegate (Func or Action) when invoking the LINQ method, but it could just as well be a delegate defined somewhere else.

On a IEnumerable<Person> we can use the Where extension method to get a subset of the list, that matches a certain predicate.

IEnumerable<Person> persons = new List<Person>()

{

    new Person(){ FirstName = "John"},

    new Person(){ FirstName = "Foo"},

    new Person(){ FirstName = "Bar"}

};

var matches = persons.Where((Person p) => p.FirstName == "John");

 

This will return a new list of all the objects where the first name equals "John". In this example we're passing in an anonymous delegate and I've used a syntax that is a bit more explicit compared to what you usually see. I've added the type in front of the variable name 'p', to clearly indicate what the type is. It shows that the variable p is of type Person, so when we go to the right side of => we know that p is a Person object and that we can access the public interface (ie. properties, methods) of each of the Person objects in the list. 

 

However, we do not need to implicit declare what the type that we are working on is. The compiler can infer this automatically and this leads to the syntax that is the most normal:

var matches = persons.Where(p => p.FirstName == "John");

 

There are no rules about how the argument on the left of => should be named. But normally we use either 'x' or the first letter of the type that we are working on (here 'p'). Now, to know what type the expression to the right of => expects, we can look at the method signature of Where. In this case, the argument to where is Func<Person, bool>. As we just learned above this means that the argument is a Person object (this first type in the brackets) and that it is expecting the code to the right of => to evaluate to bool. In other words Where will return all the elements from the list, that satisfy the boolean expression on the right side of =>. In this case, it is all the Person objects where the first name property equals "John".

Another commonly used LINQ method is OrderBy (and OrderByDescending). If we take a look at the method signature of OrderBy, we can see that it is Func<Person, TKey>. This can be a little hard to understand since TKey is not a type per se, but instead signifies a function that the list should be ordered by. In most instances TKey is simply a type that implements the IComparable interface, so that two objects can be compared and placed correctly to each other in the lsit. All simple types  (ie. int, string) implement this out of the box and if you have your own complex types, it is fairly easy to implement the interface. So essentially what TKey means is that we have to tell the OrderBy method, which property it should order by.

var orderedList = persons.OrderBy(p => p.FirstName);

OrderBy returns the type IOrderedEnumerable<People> and not IEnumerable<People> as many think. This is to enable us to do a secondary sorting. IOrderedEnumerable has an extension method called ThenBy (and ThenByDescending) that allows us to sort first on one property (by using OrderBy) and after that by a second property. An example of this could be sorting by the last name first and after that by the first name.

var orderedList = persons.OrderBy(p => p.LastName).ThenBy(p => p.FirstName);

Summary

To summarize Func and Action are not as complex to understand, once you get the hang of it. As soon as you crack what a delegate is and how to read and understand the signature, it is actually quite easy to use. Check the types in the brackets, then you know what type the input arguments should have (and what type the return type is, if it is a Func).

 

A few last thing to note:

Func with no input arguments that return string looks like this :

Func<string>

() => ""

 

Func with more than one input argument looks like this:

Func<string, string, string>

(a, b) => $"{a} {b}"

 

Comments

Be the first to post a comment

Post a comment