Skip to content

Functional Programming in C#

Published: at 03:13 PM

What is Functional Programming?

Definition:

Functional programming (FP) is a programming paradigm that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

At its core, functional programming is a way of solving problems by thinking in terms of functions. In C#, functional programming concepts are implemented through features like LINQ and lambda expressions.

Other Programming Paradigms

Note: In functional programming, “mathematical functions” refer to the mathematical definition of a function, where the output depends solely on the input without side effects.

Functional Programming in C#

“OO makes code understandable by encapsulating moving parts. FP makes code understandable by minimizing moving parts.”
— Michael Feathers

Functional programming emphasizes computing results rather than performing actions. Here are three central themes of FP:

1. Tame Side Effects

A side effect in programming is anything that alters the state of the system when a function is invoked. While necessary in some cases, side effects complicate code, making it harder to understand, test, and maintain.

Why are side effects problematic? When a function has side effects, you must consider not only its main result but also how it impacts the rest of the system. This complicates testing, as side effects imply dependencies on other parts of the system.

Functional Purity Functional purity refers to the extent to which a language restricts side effects. Purely functional languages like Haskell disallow side effects, except in controlled situations. These languages also enforce referential transparency, meaning you can replace a function with its result without affecting the program’s behavior.

Pure functions are more predictable because they always behave the same way, regardless of external state. Since pure functions don’t alter the system’s state, they are well-suited for parallel or asynchronous execution.

C#, being an impure language, does not prevent functions from changing system state. However, you can achieve functional purity in C# by enforcing immutability in your types, although this is not the default behavior.

2. Expression-Based Programming

In functional programming, everything produces a result, which is different from statement-based languages like C# where many constructs do not yield direct results but are executed for their side effects.

Statements vs. Expressions

Key Differences:

Example: Composability

Statement-Based:

string evenOrOdd;

if (value % 2 == 0)
{
    evenOrOdd = "even";
}
else
{
    evenOrOdd = "odd";
}

var msg = $"{value} is {evenOrOdd}";

Expression-Based:

var msg = $"{value} is {(value % 2 == 0 ? "even" : "odd")}";

Notice how the expression-based version is shorter, eliminates the need for an unnecessary variable, and directly produces the desired result.

3. Treat Functions as Data

Arguably the most important theme in functional programming is the ability to treat functions as first-class citizens, just like any other data type. This is crucial because it enables the use of higher-order functions—functions that accept other functions as arguments or return functions as results.

In C#, functions are treated as first-class citizens through delegation and lambda expressions, making higher-order functions possible. This capability allows for more abstract thinking and composition of functionality, much like what is achieved through inheritance in OOP.

LINQ: Functional Programming in C#

Over the years, C# has incorporated several functional programming features, with LINQ (Language Integrated Query) being one of the most prominent. LINQ is built on a combination of:

Example: Filtering and Sorting

Imperative Approach:

var x = 0;

while (x < arry.Count)
{
    if (arry[x] < 0)
    {
        arry.RemoveAt(x);
    }
    else
    {
        ++x;
    }
}

arry.Sort();

This code is imperative and has several drawbacks, such as mutable state and potential side effects, making it less suitable for concurrent execution.

Expression-Based (LINQ) Approach:

arry
    .Where(x => x > 0)
    .OrderBy(x => x);

This LINQ-based code is more concise, easier to understand, and avoids side effects. It abstracts away much of the plumbing code, allowing you to focus on solving the problem. Additionally, it doesn’t mutate the original list, making it safer for use in multi-threaded environments.

Conclusion

Functional programming offers a powerful alternative to traditional imperative and object-oriented paradigms, especially when working with C#. By taming side effects, using expressions over statements, and treating functions as data, you can write more predictable, testable, and maintainable code. LINQ is a perfect example of how C# has embraced functional principles, making it easier to write clean, efficient, and parallel-friendly code.