Introduction

In our previous post, we explored the strengths and weaknesses of F# in the .NET 9 ecosystem and compared its capabilities with those of C#. Now, let’s go a step further and see the differences in practice.

This post showcases side-by-side code comparisons for common backend tasks in F# and C#, helping you understand when each language shines and how they differ in syntax, expressiveness, and readability.


📌 Scenario 1: Modeling a Domain with Discriminated Unions / Enums and Classes

✅ F# – Expressive and Exhaustive

type PaymentMethod =
    | Cash
    | CreditCard of string
    | PayPal of string

let describePayment method =
    match method with
    | Cash -> "Paid in cash"
    | CreditCard number -> $"Paid by credit card ending in {number.Substring(number.Length - 4)}"
    | PayPal email -> $"Paid via PayPal ({email})"

✅ C# – More Verbose, Uses Inheritance or Enums

public abstract class PaymentMethod {}

public class Cash : PaymentMethod {}
public class CreditCard : PaymentMethod 
{
    public string Number { get; }
    public CreditCard(string number) => Number = number;
}
public class PayPal : PaymentMethod 
{
    public string Email { get; }
    public PayPal(string email) => Email = email;
}

public string DescribePayment(PaymentMethod method)
{
    return method switch
    {
        Cash => "Paid in cash",
        CreditCard cc => $"Paid by credit card ending in {cc.Number[^4..]}",
        PayPal pp => $"Paid via PayPal ({pp.Email})",
        _ => throw new ArgumentOutOfRangeException()
    };
}

🔍 Takeaway: F# uses built-in discriminated unions and pattern matching, making this logic more concise and safer (exhaustiveness is checked at compile time).


📌 Scenario 2: Working with Lists

✅ F# – Pipelines and Functions

let isEven x = x % 2 = 0
let square x = x * x

let result = [1..10] |> List.filter isEven |> List.map square

✅ C# – LINQ

var result = Enumerable.Range(1, 10)
    .Where(x => x % 2 == 0)
    .Select(x => x * x)
    .ToList();

🔍 Takeaway: Both languages are concise here. F# pipelines read naturally for functional programmers. C# LINQ is elegant but slightly more verbose and chained.


📌 Scenario 3: Handling Optional Values

✅ F# – Option Type

let divide a b =
    if b = 0 then None
    else Some(a / b)

let result = 
    match divide 10 2 with
    | Some v -> $"Result: {v}"
    | None -> "Cannot divide by zero"

✅ C# – Nullable and Optional (via pattern or Optional)

public double? Divide(double a, double b) => b == 0 ? null : a / b;

var result = Divide(10, 2) is double value
    ? $"Result: {value}"
    : "Cannot divide by zero";

🔍 Takeaway: F# has native Option types; C# supports nullables and can mimic Option-like behavior with third-party libraries or pattern matching.


📌 Scenario 4: Immutable Records

✅ F# – Lightweight by Default

type User = { Name: string; Age: int }

let user1 = { Name = "Anna"; Age = 30 }
let user2 = { user1 with Age = 31 } // copy and update

✅ C# – record Type (since C# 9)

public record User(string Name, int Age);

var user1 = new User("Anna", 30);
var user2 = user1 with { Age = 31 };

🔍 Takeaway: F# has supported this since the beginning. C# caught up with record types and now offers a very similar experience.


📌 Scenario 5: Async Workflows

✅ F# – Async Computation Expressions

let fetchData url =
    async {
        use client = new HttpClient()
        let! result = client.GetStringAsync(url) |> Async.AwaitTask
        return result.Length
    }

✅ C# – async / await

public async Task<int> FetchData(string url)
{
    using var client = new HttpClient();
    var result = await client.GetStringAsync(url);
    return result.Length;
}

🔍 Takeaway: Both handle async elegantly, but F# allows compositional async blocks (via computation expressions), which can feel more natural for pipelines and chaining.


🧠 Final Thoughts: Syntax or Semantics?

F# and C# in .NET 9 are both extremely powerful languages. What sets them apart isn’t the underlying platform (they both target the same CLR) but the developer ergonomics and programming philosophy:

TraitF#C#
ConcisenessHighMedium
Learning CurveSteep for OO devsLower for most .NET developers
ImmutabilityDefaultOpt-in
Pattern MatchingBuilt-in with exhaustivenessImproving but less robust
Functional CompositionFirst-class citizenSupported, but less idiomatic
Tooling SupportModerateExcellent
Community & EcosystemSmall but passionateHuge and active

✅ Summary

This side-by-side comparison makes one thing clear: both C# and F# are capable of solving the same problems, but with different approaches.

  • Choose F# if you value clarity through conciseness, immutability by default, and functional domain modeling.
  • Choose C# if you want mainstream ecosystem support, top-tier tooling, and a flexible language that balances OO and functional constructs.

If you’re interested in combining F# for core logic and C# for infrastructure or UI layers, that hybrid approach is perfectly viable in .NET 9—and it may offer the best of both worlds.


🔁 F# vs. C# – Side-by-Side Code Comparison in .NET 9

Johannes Rest


.NET Architekt und Entwickler


Beitragsnavigation


Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert