Steffen_actors_parents_children_working_hard_camera_cartoon_sty_69608d69-e3b6-498f-834a-eeb9a2fddf72.png

One of the talks on day 2 of SDD 2023, was called "Let's stop programming like 2017". The talk was held by Richard Blewitt (@RichardBlewitt) and Andrew Clymer (@AndrewClymer) from https://www.rocksolidknowledge.com/. I've been to a few talks with them and they are an absolute blast. Always lots of good laughs - just the way I like it!

In the talk they went through some of the improvements that has been made over the last years in C#. I will not go through all of them, but one really stood out to me: Pattern matching

In the bustling world of code, finding specific properties can be as challenging as spotting Waldo in a crowded scene. But fear not! C# 11's property pattern matching saves the day! It's a stylish and powerful feature that lets you uncover the hidden treasures of your objects with ease. So get out your magnifying glass and get ready to unravel the secrets of this exciting feature, transforming the search for Waldo (or any property) into a stylish and enjoyable code adventure.

Property pattern matching

One of the types of pattern matching that we can do is called property pattern matching. Property pattern matching in C# 11 is a feature that allows you to match against specific properties within objects. It provides a concise and expressive syntax for performing pattern matching based on the values of object properties.

With property pattern matching, you can easily extract relevant information or perform specific actions based on the properties of an object. It simplifies complex conditional logic by enabling you to match against property values directly within patterns.

The syntax for property pattern matching involves specifying the property name and its expected value in a pattern. This feature is particularly useful when dealing with objects that have multiple properties and you need to selectively handle different combinations of property values. It enhances code readability and conciseness, making it easier to write and understand complex matching scenarios.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
public class Person
{
    public string Name { get; set; }
    public int Age { get; set; }
}

public string GetGreeting(Person person)
{
    return person switch
    {
        { Name: "Alice" } => "Hello, Alice!",
        { Name: "Bob", Age: 25 } => "Hey Bob, you're 25!",
        { Age: >= 18 } => "Welcome, adult!",
        _ => "Greetings!"
    };
}

In the example above, we have a `Person` class with properties `Name` and `Age`. The `GetGreeting` method takes a `Person` object and returns a greeting based on different matching patterns.

In the first pattern, `{ Name: "Alice" }`, we match against a `Person` object with the name "Alice". If the `Name` property matches "Alice", the corresponding greeting "Hello, Alice!" is returned.

In the second pattern, `{ Name: "Bob", Age: 25 }`, we match against a `Person` object with the name "Bob" and age 25. If both properties match the specified values, the greeting "Hey Bob, you're 25!" is returned.

The third pattern, `{ Age: >= 18 }`, matches any `Person` object where the `Age` property is greater than or equal to 18. If the age condition is met, the greeting "Welcome, adult!" is returned.

Finally, the underscore `_` acts as a catch-all pattern that matches any `Person` object not matched by the previous patterns. In this case, the generic greeting "Greetings!" is returned.

Property pattern matching allows for concise and expressive code by directly matching against object properties within patterns, making it easier to handle complex conditional logic and extract values from objects.

Type pattern matching

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
public string GetShapeInfo(object shape)
{
    return shape switch
    {
        Circle c => $"Circle with radius {c.Radius}",
        Rectangle r => $"Rectangle with width {r.Width} and height {r.Height}",
        _ => "Unknown shape"
    };
}

public class Circle
{
    public double Radius { get; set; }
}

public class Rectangle
{
    public double Width { get; set; }
    public double Height { get; set; }
}

In the example above, we have a GetShapeInfo method that takes an object representing a shape. We use type pattern matching within the switch statement to determine the type of the shape and provide specific information based on that type.

When the shape matches the Circle type, we can access its properties directly within the pattern and return a formatted string with the radius value.

Similarly, when the shape matches the Rectangle type, we can access its properties and return a formatted string with the width and height values.

The underscore _ acts as the default pattern and matches any object that doesn't match the previous patterns. In this case, it returns "Unknown shape".

Type pattern matching allows us to check and extract specific types from objects, making it easier to handle different cases and provide type-specific behavior or information.

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}"