Chapter 3. Functional Coding in C# 7 and Beyond

I’m not sure when exactly the decision was made to make C# a hybrid object-oriented/functional language. The first foundation work was laid in C# 3. That was when features like lambda expressions and anonymous types were introduced, which later went on to form parts of LINQ in .NET 3.5.

After that, though, there wasn’t much new in terms of functional features for quite some time. In fact, it wasn’t really until the release of C# 7 in 2017 that FP seemed to become relevant again to the C# team. From C# 7 onward, every version of C# has contained something new and exciting to do more functional-style coding, a trend that doesn’t currently show any signs of stopping!

Chapter 2 introduced functional features that could be implemented in just about any C# codebase likely to still be in use out in the wild. In this chapter, we’re going to throw away that assumption and look at all the features you can use if your codebase is allowed to use any of the latest features—or at least those released since C# 7.

Tuples

Tuples were introduced in C# 7. NuGet packages do exist to allow some of the older versions of C# to use them too. They’re basically a way to throw together a quick-and-dirty collection of properties, without having to create and maintain a class.

If we have a few properties we want to hold onto for a minute in one place and then dispose of immediately, tuples are great for that.

If we have multiple objects we want to pass between Select() statements, or multiple items we want to pass in or out of one, then we can use a tuple.

This is an example of the sort of thing we might consider using tuples for:

var filmIds = new[]
{
    4665,
    6718,
    7101
};

// Turns each int element of the filmIds array
// into a tuple containing the film and cast list
// as separate properties

var filmsWithCast = filmIds.Select(x => (
    film: GetFilm(x),
    castList: GetCastList(x)
));


// 'x' here is a tuple, and it's now being converted to a string

var renderedFilmDetails = filmsWithCast.Select(x =>
    "Title: " + x.film.Title +
    "Director: " + x.film.Director +
    "Cast: " + string.Join(", ", x.castList));

In this example, we use a tuple to pair up data from two lookup functions for each given film ID, meaning we can run a subsequent Select() to simplify the pair of objects into a single return value.

Pattern Matching

Switch statements have been around for longer than just about any developers still working today. They have their uses, but they’re quite limited in what can be done with them. FP has taken that concept and moved it up a few levels. That’s what pattern matching is.

C# 7 started to introduce this feature to the C# language. Later versions have subsequently enhanced pattern matching multiple times, and yet more features will likely be added in the future.

Pattern matching is an amazing way to save yourself an awful lot of work. To illustrate what I mean, I’ll show you a bit of procedural code and then how pattern matching is implemented in a few versions of C#.

Procedural Bank Accounts

Let’s imagine one of the classic object-oriented worked examples: bank accounts. We’re going to create a set of bank account types, each with different rules for calculating the amount of interest. These aren’t really based on real banking; they’re straight out of my imagination.

These are our rules:

  • A standard bank account calculates interest by multiplying the balance by the interest rate for the account.

  • A premium bank account with a balance of $10,000 or less is a standard bank account.

  • A premium bank account with a balance over $10,000 applies an interest rate augmented by a bonus additional rate.

  • A millionaire’s bank account contains so much money it’s larger than the largest value a decimal can hold. (It’s a really, really big number—around 8 × 1028—so they must be very wealthy indeed. Do you think they’d be willing to lend me a little, if I were to ask? I could use a new pair of shoes.) An overflow balance property is used to add in all the millionaire’s money that is over the max decimal value and that can’t be stored in the standard balance property as for those of us unlucky enough not to be multibillionaires. The millionaire’s interest is calculated based on both balances.

  • A Monopoly player’s bank account gets an extra $200 for passing Go. I’m not implementing the “Go Directly to Jail” logic, as there are only so many hours in a day.

These are our classes:

public class StandardBankAccount
{
    public decimal Balance { get; set; }
    public decimal InterestRate { get; set; }
}

public class PremiumBankAccount : StandardBankAccount
{
    public decimal BonusInterestRate { get; set; }
}

public class MillionairesBankAccount : StandardBankAccount
{
    public decimal OverflowBalance { get; set; }
}

public class MonopolyPlayersBankAccount : StandardBankAccount
{
    public decimal PassingGoBonus { get; set; }
}

The procedural approach to implementing the CalculateInterest() feature for bank accounts—or, as I think of it, the longhand approach—could look like this:

public decimal CalculateNewBalance(StandardBankAccount sba)
{
    // If real type of object is PremiumBankAccount
    if (sba.GetType() == typeof(PremiumBankAccount))
    {
        // cast to correct type so we can access the Bonus interest
        var pba = (PremiumBankAccount)sba;
        if (pba.Balance > 10000)
        {
            return pba.Balance * (pba.InterestRate + pba.BonusInterestRate);
        }
    }

    // if real type of object is a Millionaire's bank account
    if(sba.GetType() == typeof(MillionairesBankAccount))
    {
        // cast to the correct type so we can get access to the overflow
        var mba = (MillionairesBankAccount)sba;
        return (mba.Balance * mba.InterestRate) +
            (mba.OverflowBalance * mba.InterestRate)
    }

    // if real type of object is a Monopoly Player's bank account
    if(sba.GetType() == typeof(MonopolyPlayersBankAccount))
    {
        // cast to the correct type so we can get access to the bonus
        var mba = (MonopolyPlayersBankAccount)sba;
        return (mba.Balance * mba.InterestRate) +
            mba.PassingGoBonus
    }

    // no special rules apply
    return sba.Balance * sba.InterestRate;
}

As is typical with procedural code, this code isn’t concise, and understanding its intent might take a little bit of reading. It’s also wide open to abuse if many more new rules are added after the system goes into production.

The object-oriented approach would be to use either an interface or polymorphism—i.e., create an abstract base class with a virtual method for the CalculateNew​Ba⁠lance() function. The issue is that the logic is now split over many places, rather than being contained in a single, easy-to-read function. In the sections that follow, I’ll show how each subsequent version of C# handled this problem.

Pattern Matching in C# 7

C# 7 gave us two ways of solving this problem. The first was the new is operator—a much more convenient way of checking types than had previously been available. An is operator can also be used to automatically cast the source variable to the correct type.

Your updated source would look something like this:

public decimal CalculateNewBalance(StandardBankAccount sba)
{
    // If real type of object is PremiumBankAccount
    if (sba is PremiumBankAccount pba)
    {
        if (pba.Balance > 10000)
        {
            return pba.Balance * (pba.InterestRate + pba.BonusInterestRate);
        }
    }

    // if real type of object is a Millionaire's bank account
    if(sba is MillionairesBankAccount mba)
    {
        return (mba.Balance * mba.InterestRate) +
            (mba.OverflowBalance * mba.InterestRate);
    }

    // if real type of object is a Monopoly Player's bank account
    if(sba is MonopolyPlayersBankAccount mba)
    {
        return (mba.Balance * mba.InterestRate) +
            mba.PassingGoBonus;
    }
    // no special rules apply
    return sba.Balance * sba.InterestRate;
}

Note that in this code sample, with the is operator, we can also automatically wrap the source variable into a new local variable of the correct type. This isn’t bad: it’s a little more elegant, and we’ve saved ourselves a few redundant lines. But we could do better, and that’s where another feature of C# 7, type switching, comes in:

public decimal CalculateNewBalance(StandardBankAccount sba)
{
    switch (sba)
    {
        case PremiumBankAccount pba when pba.Balance > 10000:
            return pba.Balance * (pba.InterestRate + pba.BonusInterestRate);
        case MillionairesBankAccount mba:
            return (mba.Balance * mba.InterestRate) +
                   (mba.OverflowBalance & mba.InterestRate);
        case MonopolyPlayersBankAccount mba:
            return (mba.Balance * mba.InterestRate) + PassingGoBonus;
        default:
            return sba.Balance * sba.InterestRate;
    }
}

Pretty cool, right? Pattern matching seems to be one of the most developed features of C# in recent years. As I’m about to show, every major version of C# since has continued to add to it.

Pattern Matching in C# 8

Things moved up a notch in C# 8, which uses pretty much the same concept but with a new, updated matching syntax that more closely matches JavaScript Object Notation (JSON) or a C# object initializer expression. Any number of clauses to properties or subproperties of the object under examination can be put inside the curly braces, and the default case is now represented by the _ discard character:

public decimal CalculateNewBalance(StandardBankAccount sba) =>
    sba switch
    {
        PremiumBankAccount { Balance: > 10000 } pba => pba.Balance *
            (pba.InterestRate + pba.BonusInterestRate),
        MillionairesBankAccount mba => (mba.Balance * mba.InterestRate) +
            (mba.OverflowBalance & mba.InterestRate);
        MonopolyPlayersBankAccount mba =>
            (mba.Balance * mba.InterestRate) + PassingGoBonus;
        _ => sba.Balance * sba.InterestRate
    };
}

Also, switch can now also be an expression, which you can use as the body of a small, single-purpose function with surprisingly rich functionality. This means it can also be stored in a Func delegate for potential passing around as a higher-order function.

The next example uses an old childhood game: Scissors, Paper, Stone (known in the United States as Rock, Paper, Scissors and in Japan as Janken). I’ve created a Func delegate for this with the following rules:

  • Both players choosing the same thing is a draw.

  • Scissors beats Paper.

  • Paper beats Stone.

  • Stone beats Scissors.

This function is specifically determining the result from my perspective against my imaginary adversary, so me choosing Scissors and beating my opponent’s choice of Paper would be considered a win, because I won, even if my opponent would consider it a loss:

public enum SPS
{
    Scissor,
    Paper,
    Stone
}

public enum GameResult
{
    Win,
    Lose,
    Draw
}

var calculateMatchResult = (SPS myMove, SPS theirMove) =>
    (myMove, theirMove) switch
    {
        _ when myMove == theirMove => GameResult.Draw,
        ( SPS.Scissor, SPS.Paper) => GameResult.Win,
        ( SPS.Paper, SPS.Stone ) => GameResult.Win,
        (SPS.Stone, SPS.Scissor) => GameResult.Win,
        _ => GameResult.Lose
    };

Having stored the logic that determines the winner of a given game in a Func<SPS,SPS> typed variable, I can pass it around to wherever needs it.

This can be as a parameter to a function, so that the functionality can be injected at runtime:

public string formatGames(
    IEnumerable<(SPS,SPS)> game,
    Func<SPS,SPS,Result) calc) =>

string.Join("
",
    game.Select((x, i) => "Game " + i + ": " +
        calc(x.Item1,x.Item2).ToString());

If I wanted to test the logic of this function without putting the actual logic into it, I could easily inject my own Func from a test method instead, so I wouldn’t have to care what the real logic is—that can be tested in a dedicated test elsewhere. This approach is another small way to make the structure even more useful.

Pattern Matching in C# 9

Nothing major was added in C# 9 but a couple of nice little features. The and and not keywords from is expressions now work inside the curly braces of one of the patterns in the list, and it’s no longer necessary to have a local variable for a cast type if its properties aren’t needed.

Although not groundbreaking, this does continue to reduce the amount of necessary boilerplate code and gives us an extra few pieces of more expressive syntax.

I’ve added a few more rules into the next example using these features. Now we have two categories of PremiumBankAccount with different levels of special interest rates1 and another type for a closed account, which shouldn’t generate any interest:

public decimal CalculateNewBalance(StandardBankAccount sba) =>
    sba switch
    {
        PremiumBankAccount { Balance: > 10000 and <= 20000 } pba => pba.Balance *
            (pba.InterestRate + pba.BonusInterestRate),
        PremiumBankAccount { Balance: > 20000 } pba => pba.Balance *
            (pba.InterestRate + pba.BonusInterestRate * 1.25M),
        MillionairesBankAccount mba => (mba.Balance * mba.InterestRate) +
            (mba.OverflowBalance + mba.InterestRate),
        MonopolyPlayersBankAccount {CurrSquare: not "InJail" } mba =>
            (mba.Balance * mba.InterestRate) + mba.PassingGoBonus;
        ClosedBankAccount => 0,
        _ => sba.Balance * sba.InterestRate
    };
}

Not bad, is it?

Pattern Matching in C# 10

Like C# 9, C# 10 added another nice time-and-boilerplate-saving feature. Here’s the simple syntax for comparing the properties of subobjects belonging to the type being examined:

public decimal CalculateNewBalance(StandardBankAccount sba) =>
    sba switch
    {
        PremiumBankAccount { Balance: > 10000 and <= 20000 } pba =>
            pba.Balance * (pba.InterestRate + pba.BonusInterestRate),
        MillionairesBankAccount mba =>
            (mba.Balance * mba.InterestRate) +
                (mba.OverflowBalance + mba.InterestRate),
        MonopolyPlayersBankAccount {CurrSquare: not "InJail" } mba =>
            (mba.Balance * mba.InterestRate) + PassingGoBonus,
        MonopolyPlayersBankAccount {Player.FirstName: "Simon" } mba =>
            (mba.Balance * mba.InterestRate) + (mba.PassingGoBonus / 2),
        ClosedBankAccount => 0,
        _ => sba.Balance * sba.InterestRate
    };

In this slightly silly example, it’s now possible to exclude all Simons from earning so much money in Monopoly when passing Go. Poor, old me.

I’d suggest taking another moment at this point to examine the function in the preceding example. Think just how much code would have to be written if it weren’t done as a pattern-matching expression! As it is, the function technically comprises just a single line of code. One…​really long…​line of code, with a whole ton of newlines to make it readable. Still, the point stands.

Pattern Matching in C# 11

C# 11 contains a new pattern-matching feature that probably has a somewhat limited scope of usage but will be devastatingly useful when something fits into that scope. The .NET team has added the ability to match based on the contents of an enumerable and even to deconstruct its elements into separate variables.

Let’s imagine we are creating a simple text-based adventure game. These were a big thing when I was young—adventure games you played by typing in commands. Imagine something like Monkey Island, but with no graphics, just text. You had to use your imagination a lot more back then.

The first task is to take the input from the user and decide what they’re trying to do. In English, commands just about universally have their relevant verbs as the first word of the sentence: GO WEST, KILL THE GOBLIN, EAT THE SUSPICIOUS-LOOKING MUSHROOM. The relevant verbs here are GO, KILL, and EAT, respectively.

Here’s how we’d use C# 11 pattern matching:

var verb = input.Split(" ") switch
{
    ["GO", "TO",.. var rest] => this.actions.GoTo(rest),
    ["GO", .. var rest] => this.actions.GoTo(rest),
    ["EAT", .. var rest] => this.actions.Eat(rest),
    ["KILL", .. var rest] => this.actions.Kill(rest)
};

The two dots (..) in this switch expression mean, “I don’t care what else is in the array; ignore it.” We put a variable after the two dots to contain everything else in the array besides those bits that’re specifically matched for.

In this example, if we were to enter the text GO WEST, the GoTo() action would be called with a single-element array ["WEST"] as a parameter, because GO is part of the match.

Here’s another neat way of using this C# 11 feature. Imagine we’re processing people’s names into data structures and we want three of them to be FirstName, LastName, and an array, MiddleNames (I have only one middle name, but plenty of folks have many):

public class Person
{
    public string FirstName { get; set; }
    public IEnumerable<string> MiddleNames { get; set; }
    public string LastName { get; set; }
}

// The real name of Doctor Who actor Sylvester McCoy
var input = "Percy James Patrick Kent-Smith".Split(" ");

var sylv = new Person
{
    FirstName = input.First(),
    MiddleNames = input is [_, .. var mns, _] ? mns : Enumerable.Empty<string>(),
    LastName = input.Last()
};

In this example, the Person class is instantiated with the following:

FirstName = "Percy",
LastName = "Kent-Smith",
MiddleNames = [ "James", "Patrick" ]

I’m not sure I’ll find many uses for this, but it’ll probably get me excited when I do. It’s a powerful feature.

Read-Only Structs

I’m not going to discuss structs a great deal here, as other excellent books talk about the features of C# in far more detail.2 What’s great about structs from a C# perspective is that they’re passed between functions by value, not reference—i.e., a copy is passed in, leaving the original untouched. The old OOP technique of passing an object into a function for it to be modified there, away from the function that instantiated it, is anathema to a functional programmer. We instantiate an object based on a class and never change it again.

Structs have been around for an awfully long time, and although they’re passed by value, they can still have their properties modified, so they aren’t immutable as such. At least until C# 7.2.

Now, it’s possible to add a readonly modifier to a struct definition, which will enforce all properties of the struct as read-only at design time. Any attempt to add a setter to a property will result in a compiler error.

Since all properties are enforced as read-only, in C# 7.2 itself, all properties need to be included in the constructor to be set. The code would look like this:

public readonly struct Movie
{
    public string Title { get; private set; };
    public string Directory { get; private set; };
    public IEnumerable<string> Cast { get; private set; };


    public Movie(string title, string directory, IEnumerable<string> cast)
    {
        this.Title = title;
        this.Directory = directory;
        this.Cast = cast;
    }
}

var bladeRunner = new Movie(
        "Blade Runner",
        "Ridley Scott",
        new []
        {
            "Harrison Ford",
            "Sean Young"
        }
);

This is still a little clunky, forcing us to update the constructor with every property as they’re added to the struct, but it’s still better than nothing.

It’s also worth discussing this case, where I’ve added in a List to the struct:

public readonly struct Movie
{
    public readonly string Title;
    public readonly string Directory;
    public readonly IList<string> Cast;


    public Movie(string title, string directory, IList<string> cast)
    {
        this.Title = title;
        this.Directory = directory;
        this.Cast = cast;
    }
}

var bladeRunner = new Movie(
        "Blade Runner",
        "Ridley Scott",
        new []
        {
            "Harrison Ford",
            "Sean Young"
        }
);

bladeRunner.Cast.Add(("Edward James Olmos"));

This will compile, and the application will run, but an error will be thrown when the Add function is called. It’s nice that the read-only nature of the struct is being enforced, but I’m not a fan of having to worry about another potential unhandled exception.

Still, it’s a good thing that the developer can now add the readonly modifier to clarify intent. This modifier will prevent any easily avoidable mutability from being added to the struct—even if it does mean that there has to be another layer of error handling.

Init-Only Setters

C# 9 introduced a new kind of auto-property type. We already had Get and Set, but now there’s also Init.

If you have a class property with Get and Set attached to it, the property can be retrieved or replaced at any time. If instead it has Get and Init, the property can have its value set when the object it’s part of is instantiated, but can’t then be changed again.

Therefore, our read-only structs (and, indeed all our classes too) can now have a slightly nicer syntax to be instantiated and then exist in a read-only state:

public readonly struct Movie
{
    public string Title { get; init; }
    public string Director { get; init;  }
    public IEnumerable<string> Cast { get; init; }
}

var bladeRunner = new Movie
   {
       Title = "Blade Runner",
       Director = "Ridley Scott",
       Cast = new []
       {
           "Harrison Ford",
           "Sean Young"
       }
   };

This means we don’t have to maintain a convoluted constructor (i.e., one with a parameter for every single property—and there could be dozens of them), along with the properties themselves, which has removed a potential source of annoying boilerplate code. We still have the issue with exceptions being thrown when attempting to modify lists and subobjects, though.

Record Types

In C# 9, one of my favorite features since pattern matching was added: record types. If you haven’t had a chance to play with these yet, do yourself a favor and do so as soon as possible. Record types are fantastic.

On the face of it, they look about the same as a struct. In C# 9, a record type is based on a class, and as such is passed around by reference.

As of C# 10 and onward, that’s no longer the case, and records are treated more like structs, meaning they can be passed by value. Unlike a struct, however, there is no readonly modifier, so immutability has to be enforced by the developer. This is an updated version of the Blade Runner code:

public record Movie
{
    public string Title { get; init; }
    public string Director { get; init;  }
    public IEnumerable<string> Cast { get; init; }
}

var bladeRunner = new Movie
   {
       Title = "Blade Runner",
       Director = "Ridley Scott",
       Cast = new []
       {
           "Harrison Ford",
           "Sean Young"
       }
   };

The code doesn’t look all that different, does it? Records come into their own, though, when we want to create a modified version. Let’s imagine for a moment that in our C# 10 application, we want to create a new movie record for the director’s cut of Blade Runner.3

This version is exactly the same for our purposes, except that it has a different title. To save defining data, we’ll literally copy over data from the original record but with one modification. With a read-only struct, we’d have to do something like this:

public readonly struct Movie
{
    public string Title { get; init; }
    public string Director { get; init;  }
    public IEnumerable<string> Cast { get; init; }
}

var bladeRunner = new Movie
    {
        Title = "Blade Runner",
        Director = "Ridley Scott",
        Cast = new []
        {
            "Harrison Ford",
            "Sean Young"
        }
    };

var bladeRunnerDirectors = new Movie
{
    Title = $"{bladeRunner.Title} - The Director's Cut",
    Director = bladeRunner.Director,
    Cast = bladeRunner.Cast
};

The code is following the functional paradigm, and it’s not too bad, but it contains another heap of boilerplate we have to include in our applications if we want to enforce immutability.

This becomes important if we have something like a state object that needs to be updated regularly following interactions with the user, or external dependencies of some sort. That’s a lot of copying of properties we’d have to do using the read-only struct approach.

Record types give us an absolutely amazing new keyword: with. This is a quick, convenient way of creating a replica of an existing record but with a modification. The updated version of the director’s cut code with record types looks like this:

 public record Movie
 {
     public string Title { get; init; }
     public string Director { get; init;  }
     public IEnumerable<string> Cast { get; init; }
 }

var bladeRunner = new Movie
    {
        Title = "Blade Runner",
        Director = "Ridley Scott",
        Cast = new []
        {
            "Harrison Ford",
            "Sean Young"
        }
    };

var bladeRunnerDirectors = bladeRunner with
{
    Title = $"{bladeRunner.Title} - The Director's Cut"
};

Isn’t that cool? The sheer amount of boilerplate we can save with record types is staggering.

I recently wrote a text adventure game in functional C#. I made a central GameState record type, containing all the progress the player has made so far. I used a massive pattern-matching statement to work out what the player was doing this turn, and a simple with statement to update state by returning a modified duplicate record. It’s an elegant way to code state machines and clarifies intent massively by cutting away so much of the uninteresting boilerplate.

One more neat feature of records is that we can even define them simply in a single line like this:

public record Movie(string Title, string Director, IEnumerable<string> Cast);

Creating instances of Movie using this style of definition can’t be done with curly braces; it has to be done with a function:

var bladeRunner = new Movie(
"Blade Runner",
"Ridley Scott",
new[]
{
    "Harrison Ford",
    "Sean Young"
});

Note that all properties have to be supplied and in order, unless we use constructor tags like this:

var bladeRunner = new Movie(
    Cast: new[]
     {
        "Harrison Ford",
        "Sean Young"
    },
    Director: "Ridley Scott",
    Title: "Blade Runner");

We still have to provide all the properties, but we can put them in any order we like (for all the good that does…​).

Which syntax you use is a matter of preference. In most circumstances, they’re equivalent.

Nullable Reference Types

Despite what it sounds like, a nullable reference type isn’t actually a new type, as with record types. This is effectively a compiler option, which was introduced in C# 8. This option is set in the CSPROJ file, as in this extract:

 <PropertyGroup>
   <TargetFramework>net6.0</TargetFramework>
   <Nullable>enable</Nullable>
   <IsPackable>false</IsPackable>
 </PropertyGroup>

If you prefer using a UI, the option can also be set in the Build section of the project’s properties.

Strictly speaking, activating the null reference types feature doesn’t change the behavior of the code generated by the compiler, but does add an extra set of warnings to the IDE and the compiler to help avoid a situation where null might end up assigned. Figure 3-1 shows a warning added to our Movie record type, indicating that it’s possible for properties to end up null.

Another warning occurs if we try to set the title of the Blade Runner director’s cut to null, as shown in Figure 3-2.

Warnings for nullable properties on a record
Figure 3-1. Warning for nullable properties on a record
Warning for setting a property to null
Figure 3-2. Warning for setting a property to null

Do bear in mind that these are only compiler warnings. The code will still execute without any errors at all. It’s just guiding us to writing code that’s less likely to contain null reference exceptions, which can only be a good thing.

Avoiding the use of a null value is generally a good practice, whether you’re using FP or not. A null value is the so-called “billion-dollar mistake.” Invented by British computer scientist Tony Hoare in the mid ’60s, null has been one of the leading causes of bugs in production ever since: an object being passed into something that turns out unexpectedly to be null. This gives rise to a null-reference exception, and you don’t need to have been in this business long before you encounter your first one of those!

Having null as a value adds unneeded complexity to your codebase and introduces another source of potential errors. This is why it’s worth paying attention to the compiler warning and keeping null out of your codebase wherever possible.

If there’s a perfectly good reason for setting a value to be null, we can do so by adding ? characters to properties, like this:

public record Movie
{
    public string? Title { get; init; }
    public string? Director { get; init;  }
    public IEnumerable<string>? Cast { get; init; }
}

I’d consider deliberately adding a nullable property to my codebase only if a third-party library requires it. Even then, I wouldn’t allow the nullable to be persisted through the rest of my code. I’d probably tuck it away somewhere that the code that parses the external data can see it, and then convert it into a safer, more controlled structure for passing to other areas of the system.

The Future

As of the time of writing, C# 11 is out and well established as part of .NET 7. The full spec for C# 12 is out, and for the first time in many years, it doesn’t seem to contain anything especially functional—even if it does contain many generally neat features!

C# 12 Specifications

You can find a description of all the new features of C# 12 on the Microsoft website.

Primary constructors are now available to all classes, and not just record types. That’s nice, and a way to reduce code noise, but not specifically functional.

Default values can also now be included on lambda expressions. That does make it slightly easier to write composable functions in places, but once again, not specifically functional.

Although it’s a little disappointing to have to skip a year of having new functional features added to C#, there is still really quite a lot available already that we can play with for now. Especially as Microsoft have said that there are some very exciting things coming on the horizon…​

Discriminated Unions

I’m not sure that it’s by any means certain that we’ll get discriminated unions in C#, but it is a feature that’s being actively considered and worked on within Microsoft.

C# 12 Specifications

There’s a discussion available on YouTube where you can see Microsoft C# team members discussing this very idea: “Languages and Runtime Community Standup: Considering Discriminated Unions”.

I’m not going to discuss discriminated unions in too much detail here, when you can turn to Chapter 6 for full details of what they are and how we can use them now in an awful lot more detail.

I’m also aware of at least two attempts to implement this concept that are available currently in NuGet:

In brief, they’re a way of having a type that might actually be one of several types. Discriminated unions are available natively in F#, but C# doesn’t have them to date, and it’s not necessarily a given that we’ll ever get them.

As of the time of writing, they’re under active consideration for C# 13, and discussions are happening over on GitHub, and proposals exist as well. For now, however, we’ll just have to keep watching the skies!

Active Patterns

Active patterns is an F# feature I can see being added to C# sooner or later. It’s an enhancement to pattern matching that allows functions to be executed in the lefthand “pattern” side of the expression. This is an F# example:

let (|IsDateTime|_|) (input:string) =
    let success, value = DateTime.TryParse input
    if success then Some value else None

let tryParseDateTime input =
    match input with
    | IsDateTime dt -> Some dt
    | _ -> None

As you can see in this example, F# developers are able to provide their own custom functions for the lefthand “pattern” side of the expression. IsDateTime is the custom function here, defined on the first line. It takes a string and returns a value if the parse worked, and what is effectively a null result if it doesn’t.

The pattern-match expression tryParseDateTime() uses IsDateTime as the pattern. If a value is returned from IsDateTime, that case on the pattern-match expression is selected and the resulting DateTime is returned.

Don’t worry too much about the intricacies of F# syntax; I’m not expecting you to learn about that here. Other books cover F#, and you could probably do worse than one or more of these resources:

Whether either of these F# features becomes available in a later version of C# remains to be seen, but C# and F# share a common language runtime, so it’s not beyond imagining that they might be ported over.

Summary

In this chapter, we looked at all the features of C# that have been released since FP began to be integrated in C# 3 and 4. We looked at what they are, how they can be used, and why they’re worth considering.

Broadly, these FP features fall into two categories:

Pattern matching

Implemented in C# as an advanced form of switch statement that allows for incredibly powerful, code-saving logic to be written briefly and simply. We saw how every version of C# has contributed more pattern-matching features to the developer.

Immutability

The ability to prevent variables from being altered once instantiated. It’s highly unlikely that true immutability will ever be made available in C# for reasons of backward compatibility, but new features are being added to C#, such as read-only structs and record types that make it easier for a developer to pretend that immutability exists without having to add a lot of tedious boilerplate code to the application.

The next chapter takes things a step further and demonstrates novel ways to use some existing features of C#, for new additions to your FP tool belt.

1 Which, frankly, no bank would ever offer.

2 For a start, you could check out C# 10 in a Nutshell by Joseph Albahari (O’Reilly).

3 Vastly superior to the theatrical cut, in my opinion.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.191.105.15