- Clean Code in C#
- Jason Alls
- 961字
- 2025-04-04 12:56:15
Understanding functional programming
The only thing that sets functional programming aside from other methods of programming is that functions do not modify data or state. You will use functional programming in scenarios such as deep learning, machine learning, and artificial intelligence when it is necessary to perform different sets of operations on the same set of data.
The LINQ syntax within .NET Framework is an example of functional programming. So, if you are wondering what functional programming looks like, and if you have used LINQ before, then you have been subjected to functional programming and should know what it looks like.
Since functional programming is a deep subject and many books, courses, and videos exist on this topic, we will only touch on the topic briefly in this chapter by looking at pure functions and immutable data.
A pure function is restricted to only operating on the data that is passed into it. As a result, the method is predictable and avoids producing side effects. This benefits programmers because such methods are easier to reason about and test.
Once an immutable data object or data structure has been initialized, the contained data values will not be modified. Because the data is only set and not modified, you can easily reason about what the data is, how it is set, and what the outcome of any operation will be, given the inputs. Immutable data is also easier to test as you know what your inputs are and what outputs are expected. This makes writing test cases much easier as you don't have so many things to consider, such as object state. The benefit of immutable objects and structures is that they are thread-safe. Thread-safe objects and structures make for good data transfer objects (DTOs) that can be passed between threads.
But structs can still be mutable if they contain reference types. One way around this would be to make the reference type immutable. C# 7.2 added support for readonly struct and ImmutableStruct. So, even if our structures contain reference types, we can now use these new C# 7.2 constructs to make structures with reference types immutable.
Now, let's have a look at a pure function example. The only way to set the properties of an object is via the constructor at construction time. The class is a Player class whose only job is to hold the name of the player and their high score. A method is provided that updates the player's high score:
public class Player
{
public string PlayerName { get; }
public long HighScore { get; }
public Player(string playerName, long highScore)
{
PlayerName = playerName;
HighScore = highScore;
}
Public Player UpdateHighScore(long highScore)
{
return new Player(PlayerName, highScore);
}
}
Notice that the UpdateHighScore method does not update the HighScore property. Instead, it instantiates and returns a new Player class by passing in the PlayerName variable, which is already set in the class, and highScore, which is the method parameter. You have now seen a very simple example of how to program your software without changing its state.
Packt has some very good books and videos that specialize in teaching the top tiers of functional programming. You will find links to some Packt functional programming resources at the end of this chapter, in the Further reading section.
Before we move on, we will look at some LINQ examples since LINQ is an example of functional programming in C#. It will be good to have an example dataset. The following code builds a list of vendors and products. We'll start by writing the Product structure:
public struct Product
{
public string Vendor { get; }
public string ProductName { get; }
public Product(string vendor, string productName)
{
Vendor = vendor;
ProductName = productName;
}
}
Now that we have our struct, we will add some sample data inside the GetProducts() method:
public static List<Product> GetProducts()
{
return new List<Products>
{
new Product("Microsoft", "Microsoft Office"),
new Product("Oracle", "Oracle Database"),
new Product("IBM", "IBM DB2 Express"),
new Product("IBM", "IBM DB2 Express"),
new Product("Microsoft", "SQL Server 2017 Express"),
new Product("Microsoft", "Visual Studio 2019 Community Edition"),
new Product("Oracle", "Oracle JDeveloper"),
new Product("Microsoft", "Azure"),
new Product("Microsoft", "Azure"),
new Product("Microsoft", "Azure Stack"),
new Product("Google", "Google Cloud Platform"),
new Product("Amazon", "Amazon Web Services")
};
}
Finally, we can start to use LINQ on our list. In the preceding example, we will get a distinct list of products, ordered by the vendor's names, and print out the results:
class Program
{
static void Main(string[] args)
{
var vendors = (from p in GetProducts()
select p.Vendor)
.Distinct()
.OrderBy(x => x);
foreach(var vendor in vendors)
Console.WriteLine(vendor);
Console.ReadKey();
}
}
Here, we obtain a list of vendors by calling GetProducts() and selecting only the Vendor column. Then, we filter the list so that it only includes a vendor once by calling the Distinct() method. The list of vendors is then ordered alphabetically by calling OrderBy(x => x), where x is the vendor's name. Upon obtaining the ordered list of distinct vendors, we then loop through the list and print the vendor's name. Finally, we wait for the user to press any key to exit the program.
One of the benefits of functional programming is that your methods are much smaller than the methods in other types of programming. Next, we will take a look at why it is good to keep methods small, as well as the techniques we can use, including functional programming.