Functional Programming in C#: From A to Z
Functional Programming (FP) is a paradigm that treats computation as the evaluation of mathematical functions and avoids changing state and mutable data. Despite C# being a multi-paradigm language with strong support for Object-Oriented Programming (OOP), its modern versions (starting from C# 3.0 and onwards) actively integrate elements of the functional style. This allows you to write more concise, predictable, and easily testable code.
In this article, we will journey from the basics to advanced functional programming techniques in C# within the .NET context. You will learn how to use lambda expressions, LINQ, immutable data types, and patterns to build robust applications.
1. Fundamentals: Lambda Expressions and Delegates
At the heart of the functional approach in C# are lambda expressions — anonymous functions that can be used to create delegates or expression trees. They allow you to pass behavior as data.
Consider a classic example of sorting a list. Instead of creating a separate comparison method, we use a lambda:
using System;using System.Collections.Generic;
public class LambdaExample{ public static void Main() { var numbers = new List<int> { 5, 3, 8, 1, 9 }; // Sorting in descending order using a lambda expression numbers.Sort((x, y) => y.CompareTo(x)); Console.WriteLine(string.Join(", ", numbers)); // Output: 9, 8, 5, 3, 1 }}Key concepts at play here:
- Delegates
Func<>andAction<>:Func<T, TResult>returns a value,Action<T>does not. Lambdas implicitly convert to these types. - Closures: A lambda can capture variables from the outer scope, making it a powerful tool, but requiring caution due to potential side effects.
2. LINQ: A Declarative Style for Data Manipulation
Language Integrated Query (LINQ) is arguably the most prominent example of FP application in C#. It allows you to write declarative queries over collections, databases, and XML using method chaining.
Instead of imperative foreach loops, we use functional operations:
using System;using System.Linq;using System.Collections.Generic;
public class LinqExample{ public static void Main() { var people = new List<Person> { new Person { Name = "Alice", Age = 30 }, new Person { Name = "Bob", Age = 17 }, new Person { Name = "Charlie", Age = 25 } };
// Functional query: filtering, projection, sorting var result = people .Where(p => p.Age >= 18) // Filtering (predicate) .OrderBy(p => p.Name) // Sorting .Select(p => new { p.Name, IsAdult = true }) // Projection (anonymous type) .ToList();
foreach (var item in result) { Console.WriteLine($"{item.Name} - Adult: {item.IsAdult}"); } // Output: Alice - Adult: True, Charlie - Adult: True }
public class Person { public string Name { get; set; } public int Age { get; set; } }}Key LINQ operators:
Where— filtering (analogous tofilter).Select— transformation (analogous tomap).Aggregate— folding (analogous toreduce).Any,All— condition checking.
It is important to remember about deferred