February 26, 2026

Functional Programming & Monads in C#

🧠 Introduction

Functional programming (FP) is a paradigm focused on pure functions, immutability, and composable transformations. Modern C# supports many FP concepts, making it possible to write expressive, predictable, and safe code without leaving the .NET ecosystem. This guide walks through the core FP ideas, monads, immutability, partial functions, and practical C# examples — all in clean Markdown for your blog.

1. Core Concepts of Functional Programming

1.1 First-Class & Higher-Order Functions

In FP, functions are values. You can:

  • Functions are treated as values: you can pass them around, store them in variables, and return them from other functions.
  • Higher-order functions take other functions as parameters or return them.

Example:

Func<int, int, int> add = (x, y) => x + y;

// Higher-order function: takes a function as input
int ApplyOperation(int a, int b, Func<int, int, int> operation)
{
    return operation(a, b);
}

var result = ApplyOperation(5, 3, add); // result = 8

1.2 Immutability

  • FP emphasizes immutable data, once created, values don’t change.
  • In C#, you can use readonly fields, record types, or avoid mutating collections
  • Data does not change after creation. Instead, you create new values.

Example using records:

record Person(string Name, int Age);
...
var p1 = new Person("Isabel", 30);
// Instead of mutating, create a new instance
var p2 = p1 with { Age = 31 }; // p2 is a new object

1.3 Pure Functions

  • A pure function always returns the same output for the same input and has no side effects (like modifying global state or I/O).
  • This makes code predictable and testable.

Pure:

int Square(int x) => x * x; // Pure function

Not pure:

int counter = 0;
int Increment() => ++counter; // Not pure (depends on external state)

1.4 Function Composition

Combine small functions into larger ones.

Func<int, int> doubleIt = x => x * 2;
Func<int, int> squareIt = x => x * x;

// Compose manually
Func<int, int> doubleThenSquare = x => squareIt(doubleIt(x));

var result = doubleThenSquare(3); // (3*2)^2 = 36

1.5 Declarative Style (LINQ)

  • FP favors describing what to do rather than how to do it.
  • LINQ is a great example of declarative programming in C#.
var numbers = new[] { 1, 2, 3, 4, 5 };

// Declarative: filter and transform
var evensSquared = numbers
    .Where(n => n % 2 == 0)
    .Select(n => n * n);

foreach (var n in evensSquared)
    Console.WriteLine(n); // Output: 4, 16

1.6 Lazy Evaluation

  • FP often defers computation until needed.
  • In C#, IEnumerable with yield return or LINQ queries are (mostly) lazily evaluated.
IEnumerable<int> Squares()
{
    int i = 1;
    while (true)
        yield return i * i++;
}

🔑 Summary Functional programming in C# revolves around:

  • Treating functions as values
  • Using immutability
  • Writing pure functions
  • Composing small functions
  • Favoring declarative style (LINQ)
  • Leveraging lazy evaluation

2. Monads in C#

2.1 What Is a Monad?

A monad is a design pattern from functional programming that:

  • Wraps a value in a context (e.g., "maybe this value exists", "this value is asynchronous", "this value is a sequence").
  • Provides a way to chain operations on that value without breaking the context.
  • Ensures consistent handling of side effects (nulls, errors, async, logging, etc.). Think of it as a container + rules for chaining.

A monad must support:

  • Return/Unit → wrap a value (handle the cases where the function has no valid value, eg null pointer, ...)
  • Bind → (flatMap / SelectMany in LINQ) Chain operations (so it can be chained through functions) and everything works even if the value is null/none, etc. Make functions act like pure functions by handling the bad cases through the Monad.

2.2 Common Monads in C#

Monad Meaning
Task<T> Asynchronous computation
IEnumerable<T> Sequence computation
Nullable<T> Optional values
Result<T> Success/failure pipeline

2.3.1 Example: Option Monad

public class Option<T>
{
    private readonly T _value;
    public bool HasValue { get; }

    private Option() { HasValue = false; }
    private Option(T value) { _value = value; HasValue = true; }

    public static Option<T> Some(T value) => new(value);
    public static Option<T> None() => new();

    public Option<TResult> Bind<TResult>(Func<T, Option<TResult>> f)
        => HasValue ? f(_value) : Option<TResult>.None();

    public Option<TResult> Map<TResult>(Func<T, TResult> f)
        => HasValue ? Option<TResult>.Some(f(_value)) : Option<TResult>.None();

    // Extract the value with a fallback
    public T GetValueOrDefault(T fallback = default)
        => HasValue ? _value : fallback;
}

2.3.2 Using this Monad

var maybeNumber = Option<int>.Some(5);

var result = maybeNumber
    .Bind(x => Option<int>.Some(x * 2))
    .Bind(x => Option<int>.Some(x + 10));

// result = Some(20)

This avoids null checks everywhere — the monad handles the "no value" case.

2.4.1 Task Monad (async) Example

Task in C# is essentially a monad:

  • Task.FromResult(value) → wraps a value.
  • await / ContinueWith → bind operations.
async Task<int> DoubleAsync(int x) => x * 2;

var result = await Task.FromResult(5)
    .ContinueWith(t => DoubleAsync(t.Result))
    .Unwrap();

Here Task ensures async chaining without manually handling threads.

2.5.1 LINQ Query Syntax (Enumerable Monad)

LINQ’s SelectMany is the bind operation for sequences.

var numbers = new[] { 1, 2, 3 };
var doubled = from n in numbers
              from m in new[] { n * 2 }
              select m;

// doubled = {2, 4, 6}

LINQ query comprehension is syntactic sugar for monadic chaining.

⚡ Summary In C#:

  • Option (or Nullable) → Maybe Monad
  • Task → Async Monad
  • IEnumerable → Sequence Monad
  • LINQ query syntax → Monadic chaining (SelectMany) 👉 The purpose: monads let you build pipelines of computation while hiding the messy details of context management (nulls, async, errors, etc.).

🧩 Scenario Imagine a WPF app where the ViewModel fetches a user profile. Sometimes the profile data might be missing (e.g., network error, no record). Instead of sprinkling if (profile != null) everywhere, we’ll use an Option Monad.

  1. Define a Simple Option<T> Monad. See the class above

3. Monads in MVVM (Practical Example)

public class ProfileViewModel : ObservableObject
{
    private Option<UserProfile> _profile = Option<UserProfile>.None();

    public string DisplayName =>
        _profile.Map(p => $"Welcome, {p.Name}!").GetValueOrDefault("Guest");

    public async Task LoadAsync()
    {
        var fetched = await FetchProfileAsync();
        _profile = fetched is null
            ? Option<UserProfile>.None()
            : Option<UserProfile>.Some(fetched);

        OnPropertyChanged(nameof(DisplayName));
    }
}

4. Partial Functions

4.1 What Is a Partial Function?

A function that is not defined for all inputs. Example:

int Divide(int x, int y) => x / y; // undefined when y ** 0

4.2 Partial Application (Different Concept)

Fixing some arguments of a function.

Func<int, int, int> add = (x, y) => x + y;
Func<int, int> addFive = y => add(5, y);

4.3 Lifting a Partial Function into a Monad

Option<int> SafeDivide(int x, int y)
{
    if (y == 0) return Option<int>.None();
    return Option<int>.Some(x / y);
}

6. Immutability in Functional Programming

6.1 Why It Matters

Immutability provides:

  • predictability
  • pure functions
  • thread safety
  • referential transparency
  • easier debugging
  • safe composition
  • undo/redo and time-travel debugging

6.2 Example: Immutable State

record AppState(int Count);

var state1 = new AppState(0);
var state2 = state1 with { Count = state1.Count + 1 };

7. Using Monads for Unit-Safe Values

7.1 Example: Length Monad

public class Length
{
    private readonly double _meters;

    private Length(double meters) => _meters = meters;

    public static Length FromMeters(double m) => new(m);
    public static Length FromKilometers(double km) => new(km * 1000);

    public Length Map(Func<double, double> f) => new(f(_meters));
    public Length Bind(Func<double, Length> f) => f(_meters);

    public double ToMeters() => _meters;
}

8. Practice Quiz (Optional)

1. What is a pure function?

Answer: A function with no side effects that always returns the same output for the same input.

2. Which LINQ method corresponds to monadic bind?

Answer: Select Many

3. What is the purpose of a monad?

Answer: To wrap values in context and allow safe, composable operations.

4. Which C# type is an example of a monad?

Answer: Task

5. How does Option help in MVVM?

Answer: Eliminates null checks by encapsulating optional values.

9. Summary

Functional programming in C# gives you:

  • safer code
  • predictable behavior
  • composable pipelines
  • easier testing
  • fewer bugs

Monads, immutability, and pure functions work together to create a clean, expressive, and maintainable architecture.

Copied and edited from a Copilot chat

February 17, 2026

From ".slnx" => ".sln" file

To create a new ".slnx" solution file from a ".sln" file. Open a command window (cmd.exe). Within that window change to the folder containing your "{YourSolutionName}.sln" file. Type in the following line:

"dotnet sln {YourSolutionName}.sln migrate"
This generates a new "{YourSolutionName}.slnx" file. The original "{YourSolutionName}.sln" file remains untouched.

February 6, 2026

C# Dot Net SDK Version Macros

It is useful to be able to include or exclude code based on preprocessor macros. We can do this for the different versions of .NET

1. Use "#if" with framework version symbols to include specific code for specific .Net versions .NET SDK defines symbols like "Net5_0", "Net6_0", "Net7_0", etc. For .NET 10, the symbol will be "Net10_0". This works automatically if you're targeting .NET 10 via the SDK-style project and using the element <TargetFramework> like: "<TargetFramework>net10.0<TargetFramework>". Here is an example of this:

#if NET10_0
    // This code will be included when targeting .NET 10
    Console.WriteLine("Running on a framework other than .NET 10");
#endif
🧠 These symbols can be combined for example:

#if NET6_0 || NET7_0
    Console.WriteLine("Running on .NET 6 or 7");
#elif NET10_0
    Console.WriteLine("Running on .NET 10");
#endif
2. Invert the macro condition to exclude code using the ! sign For example, you can use "#if !NET10_0" to exclude code when the target is not .NET 10. Here is an example of this:

#if !NET10_0
    // This code will be excluded when targeting .NET 10
    Console.WriteLine("Running on a framework other than .NET 10");
#endif
3. You can also use a "_OR_GREATER" suffix, for example:

#if NET5_0_OR_GREATER
    // This code will be included when targeting .NET 5 or greater
#endif
There are also platform-specific preprocessor symbols for conditional compilation based on the operating system. These are especially useful when writing cross-platform code in .NET Core or .NET 5+. 🧭 Built-in platform symbols Here are the most common ones:
OS Target
WINDOWS Compiling for Windows
LINUX Compiling for Linux
OSX Compiling for Mac OS
ANDROID Compiling for Android
IOS Compiling for iOS
MACCATALYST Compiling for Mac Catalyst
FREEBSD Compiling for FreeBSD
BROWSER Compiling for WebAssembly (Blazor)
These are automatically defined by the SDK when targeting specific platforms.
✅ Example usage

#if WINDOWS
    Console.WriteLine("Running on Windows");
#elif LINUX
    Console.WriteLine("Running on Linux");
#elif OSX
    Console.WriteLine("Running on macOS");
#else
    Console.WriteLine("Unknown platform");
#endif

🔍 Notes

  • These symbols are only defined when the runtime identifier (RID) or target platform is specified appropriately in your ".csproj " file.
  • If you're multi-targeting or using runtime checks, you might prefer OperatingSystem.IsWindows() or similar APIs from System.Runtime.InteropServices

Note that this is heavily edited output from CoPilot.

January 30, 2026

Using System.IO.Abstractions for File System Abstraction in C#

System.IO.Abstractions is a library that helps abstract file system operations in C#. It is particularly useful when writing unit tests because it allows you to mock file system interactions. Here's how you can integrate it into your project for both regular file operations and testing scenarios.

1. Adding System.IO.Abstractions to Your Project

First, you'll need to add the System.IO.Abstractions NuGet package to your project. In your .csproj file, include the following line:

      <PackageReference Include="System.IO.Abstractions" Version="21.0.29" /> <!-- See https://github.com/TestableIO/System.IO.Abstractions -->

    

More details are here: https://github.com/TestableIO/System.IO.Abstractions

2. Registering the IFileSystem Service for Dependency Injection

Next, inject IFileSystem into your application's services. This allows you to easily work with the file system and mock it for testing. In your Startup.cs (or Program.cs), register the FileSystem implementation as a transient service:

      // Register IFileSystem to allow injection
services.AddTransient<IFileSystem, FileSystem>();

    

This ensures that the IFileSystem interface will be resolved to the concrete FileSystem class when injected.

3. Injecting and Using IFileSystem in Your Class

In your class, inject IFileSystem via the constructor. This allows you to interact with the file system in a testable way.

      public class SomeService
{
    private readonly IFileSystem _fileSystem;

    // Constructor injection of IFileSystem
    public SomeService(IFileSystem fileSystem)
    {
        _fileSystem = fileSystem;
    }

    // Example method using IFileSystem to check if a file exists
    public void DoSomething(string fileName)
    {
        bool exists = _fileSystem.File.Exists(fileName);
        if (exists)
        {
            var content = _fileSystem.File.ReadLines(fileName);
            // Process the content
			...
        }
    }
}

    

In this example, DoSomething checks if a file exists using the abstracted IFileSystem and reads the file's contents if it does.

4. Unit Testing with System.IO.Abstractions.TestingHelpers

For unit testing, you'll want to mock the file system to avoid interacting with the actual file system. You can do this by adding System.IO.Abstractions.TestingHelpers to your project:

      <PackageReference Include="System.IO.Abstractions.TestingHelpers" Version="21.0.29" />

    

Now, you can use the MockFileSystem class to simulate a file system in your tests.

4.1. Mocking Files and Directories

Here's an extension method for setting up files (empty) and directories in your IFileSystem (mock or otherwise):

      public static IFileSystem AddFilesAndDirectories(
    this IFileSystem fileSystem, 
    IEnumerable<string> fileSystemEntries)
{
    if (fileSystem == null)
        throw new ArgumentNullException(nameof(fileSystem));

    if (fileSystemEntries == null)
        throw new ArgumentNullException(nameof(fileSystemEntries));

    foreach (var entry in fileSystemEntries.Select(x => x.Trim()))
    {
        // Check if the entry is a directory or file by checking the last character
        // if it is a directory separator assume it is a directory
        if (entry[^1] == Path.DirectorySeparatorChar || entry[^1] == Path.AltDirectorySeparatorChar)
        {
            fileSystem.AddDirectory(entry); // Add directory if the string ends with a directory separator
        }
        else // otherwise assume it is a file
        {
            fileSystem.AddFile(entry, "x"); // Add file with some content. File parent directories will also be created.
        }
    }

    return fileSystem;
}

    

This helper method adds directories and files to the MockFileSystem. It differentiates between files and directories based on the path format (whether it ends with a directory separator).

4.2. Example Unit Test

Here’s a simple unit test demonstrating how to use the MockFileSystem:

      using System.IO.Abstractions.TestingHelpers;
using Xunit;

public class SomeServiceTests
{
    [Fact]
    public void TestFileOperations()
    {
        // Setup mock file system
        var mockFileSystem = new MockFileSystem();

        var someService = new SomeService(fileSystem);
        ...
    }
}

    

You can use these methods to interact with files and directories in an abstracted way, making your code more testable and platform-independent.

6. Conclusion

System.IO.Abstractions is a library for abstracting file system operations, which simplifies unit testing and makes your code more modular and easier to maintain.

By using IFileSystem for dependency injection and MockFileSystem for unit tests, you can easily mock file system interactions without relying on the actual file system.By following the steps outlined above, you'll be able to set up file system abstractions in your project and write unit tests that are isolated from the underlying file system, improving the testability and reliability of your application.

April 16, 2025

Stream Extension Class

This extension class is useful when you want to create a stream from some bytes of directly from a string

public static class StreamExtender
{
    /// <summary>
    /// Create a stream from an array of bytes
    /// </summary>
    /// <param name="streamBytes">Bytes source foe the stream</param>
    /// <returns></returns>
    public static MemoryStream CreateStreamFromBytes(this byte[] streamBytes)
    {
        var stream = new MemoryStream();
        stream.Write(streamBytes, 0, streamBytes.Length);
        stream.Seek(0, 0);
        return stream;
    }

    /// <summary>
    /// Create a stream from a string
    /// </summary>
    /// <remarks>Useful for unit testing file storage</remarks>
    /// <param name="contents">The contents to be placed in the stream</param>
    /// <returns>A memory stream with the string as contents</returns>
    public static MemoryStream CreateStreamFromString(this string contents)
    {
        byte[] encodedStreamBytes = Encoding.UTF8.GetBytes(contents);
        var stream CreateStreamFromBytes(encodedStreamBytes);
        return stream;
    }
}

December 12, 2024

Using Patterns in if and switch statements

Using Patterns in if and switch statements See this link: https://www.thomasclaudiushuber.com/2021/02/18/c-9-0-improved-pattern-matching/ Replace the old inefficient
ChangedEvent lce = ev as ChangedEvent
if (lce != null)
{
  DoSomethingWith(lce);
}
to this much shorter and more readable:
if (ev is ChangedEvent lce)
{
  DoSomethingWith(lce);
}
And this has been refined further with much more readable conditional clauses using patterns
var developer = new Developer { YearOfBirth = 1983 };
if (developer is { YearOfBirth: >= 1980 and <= 1989 and not 1984 })
{
  // The dev is born in the eighties, but not in 1984
}
And when checking for is or is not null use this pattern
if (developer is not null)
{
  ...
}
We can also use these patterns in switch statements TODO add an example Now we have "and" and "&&" plus "or" and "||": The "and" pattern combinator is used to combine patterns. The conditional "and" operator of "&&" is a boolean operator and it is used to combine bool values in your C# code. Similar statements can be made for the pattern "or" and "not" clauses

August 22, 2024

Immediate Window in Visual Studio

Sometimes I find I want to dump an object that I see in debugging for further analysis, testing or just to get a feel for the shape of some data. To do this I stop at a location in the debugger where the data is visible grab a copy of the required variable and then switch to the VS Immediate Window.
In Immediate window I can dump a variables contents to a json file by typing in the following C#:

File.WriteAllText(@"c:\Somewhere\delme.json", Newtonsoft.Json.JsonConvert.SerializeObject(myobject, Formatting.Indented));