Functional VB.NET You Are Already Using

Part 1 of a three-part series on functional programming in VB.NET. What’s already in the box.
Version v1792077374
Updated

Functional VB.NET You Are Already Using

Part 1 of a three-part series on functional programming in VB.NET. These notes cover the patterns built into the language. Part 2 covers monads and bind chains. Part 3 covers domain modeling with records and type classes.

last updated: 2026-07-20 · part 1 of 3


Why I started writing this down

I have written VB.NET for years and only recently realized I had been writing functional code the whole time. LINQ, extension methods, lambdas, Iterator/Yield, generic constraints: all functional, all already in the box, all in use before I had ever heard the term “monad.”

These notes document the patterns I actually use day to day. The goal is to record what works, not to sell the language. If it helps someone else who got stuck in a VB.NET codebase the way I did, even better.


LINQ is functional programming

The .NET team did not talk about it this way, but LINQ (Language Integrated Query) is a functional programming library bolted onto C# and VB.NET. Every chain I write, every Where, Select, Aggregate, GroupBy, is a composition of pure functions over a sequence.

The pattern, in case it has been a while:

Dim activeOrders = orders _
    .Where(Function(o) o.Status = OrderStatus.Active) _
    .Select(Function(o) New With {
        .Id = o.Id,
        .Total = o.LineItems.Sum(Function(li) li.Price * li.Quantity)
    }) _
    .OrderBy(Function(x) x.Total) _
    .ToList()

That is a functional pipeline. Where filters. Select projects. Aggregate folds. GroupBy partitions. They chain like bash pipes. They do not mutate the input. They return new collections.

The trick is realizing you can write LINQ over anything that implements IEnumerable(Of T). Not just collections. Database query results. CSV rows parsed line by line. Web API responses. Generated sequences from Iterator functions. Anything.

When I catch myself writing nested For loops to compute things, the first question is: could this be a Where/Select/Aggregate chain? Almost always yes, and the LINQ version is shorter, faster to write, and easier to test.

Query syntax vs method syntax — pick one

VB.NET gives you two ways to write the same LINQ:

' Method syntax (chain of extension methods)
Dim result = items _
    .Where(Function(x) x.IsActive) _
    .Select(Function(x) x.Name) _
    .ToList()

' Query syntax (SQL-like comprehension)
Dim result = (From x In items
              Where x.IsActive
              Select x.Name).ToList()

Both compile to the same thing. The convention I default to:

If I mix the two in the same query, the VB.NET compiler gets confused and I waste twenty minutes figuring out why. Pick one per query and stay consistent.


Extension methods: turn anything into a pipeline

This pattern shows up everywhere:

Dim s$ = foo().Replace("foo", "bar").Capitalize()

That Capitalize() is not a built-in method on String. It is an extension method someone wrote. In VB.NET, you declare one like this:

<Extension>
Module StringExtensions
    <Extension>
    Public Function Capitalize(s As String) As String
        If String.IsNullOrEmpty(s) Then Return s
        Return Char.ToUpper(s(0)) & s.Substring(1)
    End Function
End Module

The <Extension> attribute on the module is optional but conventional. The attribute on the function is mandatory. The first parameter is the receiver, which is what s.Capitalize() passes in.

The pipeline pattern

Once a few extension methods exist, they start composing:

Dim cleanName = rawName _
    .Trim() _
    .Capitalize() _
    .Replace("  ", " ")

This reads like a recipe. Each step does one thing. The data flows left to right. To add a step, add a line. To remove a step, delete a line.

The discipline: each extension method should do one transformation and return a new value. Do not write extensions that mutate state. Do not write extensions that have side effects unless they are named like they do (LogTo, SendTo, WriteTo).

When extension methods earn their place

Not every method should be an extension. The filter I apply:

Good candidate Bad candidate
Operations you do repeatedly on a type One-off logic specific to your app
Transformations that read naturally chained Operations that need lots of parameters
Methods that fit the type’s conceptual API Methods that are really just helpers
Composable pieces (output is the same type) Methods that take input you do not have access to

The smell test: would myString.MyExtension() read like a method someone would have written on String itself? If yes, it is a good extension. If I am fighting the type system, it is not.


Iterator functions: lazy sequences for free

Most VB.NET developers learn about For Each and never think about how sequences get built. The Iterator keyword lets you write a function that produces a sequence lazily, one element at a time, on demand.

Public Iterator Function EvenNumbersUpTo(n As Integer) As IEnumerable(Of Integer)
    For i = 2 To n Step 2
        Yield i
    Next
End Function

' Usage
Dim evens = EvenNumbersUpTo(10)  ' [2, 4, 6, 8, 10]

The body looks like a normal For loop. The Yield statement produces the next element. The function returns when control falls off the end (or hits Return).

Why this matters:

  1. Lazy evaluation. The sequence is not built until something consumes it. If you only .Take(3), the rest never gets computed.

  2. Readable data sources. If you need to generate a test fixture, parse a custom format, or walk a tree, Iterator is usually cleaner than building a List(Of T) and returning it.

  3. LINQ interop. The result is an IEnumerable(Of T), so it chains with everything LINQ has:

Dim firstFiveEvensSquared = EvenNumbersUpTo(100) _
    .Select(Function(x) x * x) _
    .Take(5) _
    .ToArray()
' [4, 16, 36, 64, 100]

Real example: walking a tree

Public Iterator Function WalkTree(node As TreeNode) As IEnumerable(Of TreeNode)
    Yield node
    For Each child In node.Children
        For Each descendant In WalkTree(child)
            Yield descendant
        Next
    Next
End Function

That recursive walk would be ten lines of imperative code with manual stack management. Here it is six lines and obviously correct.


Lambdas, Func(Of), and method references

VB.NET has had lambdas since 2010. The syntax is verbose by modern standards but it works:

' Lambda assigned to a variable
Dim isPositive = Function(x As Integer) x > 0

' Lambda passed directly to LINQ
Dim positives = numbers.Where(Function(x) x > 0).ToList()

' Lambda stored as a field
Public ReadOnly Add As Func(Of Integer, Integer, Integer) =
    Function(x%, y%) x + y

The % suffix on parameters is a type character shorthand, Integer instead of As Integer. Common in VB.NET, especially in tight code.

Method references: cleaner than lambdas

If you already have a function that does what you want, pass the method reference directly instead of wrapping it in a lambda:

' Lambda wrapping an existing function
Dim upper = words.Select(Function(w) w.ToUpper()).ToList()

' Method reference — same thing, no wrapping
Dim upper = words.Select(AddressOf String.ToUpper).ToList()

AddressOf String.ToUpper is the same as Function(w) w.ToUpper() but the compiler does the wrapping for you. The code is shorter, the intent is clearer.

When to use which

Pattern Use when
Lambda inline You need a one-off transformation
Method reference A function already exists that does the work
Stored Func(Of) field The function is part of your domain (e.g., a validator, a calculator)

Generic constraints as type classes — the underused gem

This is the trick I reach for most when I need to add behavior to existing types without modifying them. Almost nobody talks about it in VB.NET, and it is the single most useful pattern for domain modeling in the language.

The pattern

Interface IExpirable
    ReadOnly Property ExpiresAt As Date
End Interface

Public Function IsExpired(Of T As IExpirable)(item As T) As Boolean
    Return item.ExpiresAt < Date.Now
End Function

' Works on any type that implements IExpirable
Dim cert = GetCertificate()
If IsExpired(cert) Then ...

The Of T As IExpirable constraint says “this generic only accepts types that implement IExpirable.” Inside the function, the compiler knows item.ExpiresAt exists, so it is type-safe.

This is essentially a type class from Haskell. The constraint defines a contract. The generic function works on anything that satisfies it.

Duck typing with Func(Of)

The constraint approach requires the type to implement your interface. Sometimes you do not control the type. It might be from a third-party library, or it might be a primitive like Date. In that case, use Func(Of):

<Extension>
Public Function IsExpired(Of T)(x As T, getExpiry As Func(Of T, Date)) As Boolean
    Return getExpiry(x) < Date.Now
End Function

' Usage
Dim orderDate As Date = ...
Dim isOld = orderDate.IsExpired(Function(d) d.AddYears(2))  ' "expires" 2 years after order

Dim cert = GetCertificate()
Dim isCertOld = cert.IsExpired(Function(c) c.ExpiresAt)

The caller provides the function that extracts the relevant field. The IsExpired extension does not care about the source type. It works on anything.

Why this matters

Most VB.NET codebases model domain concepts with inheritance:

Animal
├── Dog
├── Cat
└── Fish

That is rigid. If you want to add a “swimmable” predicate, you either edit the base class (breaks open-closed) or add an interface (everywhere). The Of T As IExpirable pattern lets you add behavior to existing types without modifying them:

' Add "expirable" behavior to ANY type that has a Date field
' without modifying the type itself

It is the open-closed principle, expressed in VB.NET’s type system instead of through inheritance hierarchies.

A real example

Interface IHasUpdatedAt
    ReadOnly Property UpdatedAt As Date
End Interface

Public Function WasUpdatedSince(Of T As IHasUpdatedAt)(item As T, since As Date) As Boolean
    Return item.UpdatedAt >= since
End Function

' Both Order and Customer implement IHasUpdatedAt
Dim recentOrders = allOrders.Where(Function(o) WasUpdatedSince(o, lastWeek)).ToList()
Dim recentCustomers = allCustomers.Where(Function(c) WasUpdatedSince(c, lastWeek)).ToList()

One generic function, used on two completely unrelated types. No base class needed. No interface-inheritance hierarchy to maintain.


Anonymous variables in With blocks

This one is smaller but I use it constantly. Inside a With block, you can omit the variable name and access properties directly:

With order.Customer.Address
    city = .City
    state = .State
    zip = .ZipCode
End With

The leading . is an anonymous variable. It refers to the With target but does not give it a name.

When you combine it with If for null-safety, you get something close to a null-safe navigation operator:

Dim cityName = order?.Customer?.Address?.City
' Equivalent to:
If order IsNot Nothing AndAlso
   order.Customer IsNot Nothing AndAlso
   order.Customer.Address IsNot Nothing Then
    cityName = order.Customer.Address.City
End If

The ?. operator is C# syntax. VB.NET does not have it directly. The closest equivalent is either the chained AndAlso (verbose) or a With block wrapped in If:

Dim cityName As String = Nothing
With order
    If .Customer IsNot Nothing Then
        With .Customer.Address
            If .City IsNot Nothing Then cityName = .City
        End With
    End If
End With

Not as clean as C#’s ?., but it works. For deeper null safety, you would want a Maybe(Of T) type. That is covered in Part 2.


A small example: putting it together

Here is a real function that uses several of these patterns:

<Extension>
Public Function FindOverdueInvoices(Of T As IHasDueDate)(
    invoices As IEnumerable(Of T),
    asOf As Date
) As IEnumerable(Of T)
    Return invoices _
        .Where(Function(i) i.DueDate < asOf) _
        .Where(Function(i) Not i.IsPaid) _
        .OrderBy(Function(i) i.DueDate)
End Function

' Usage
Dim overdue = allInvoices _
    .FindOverdueInvoices(asOf:=Date.Now) _
    .Select(Function(i) i.CustomerName) _
    .ToList()

That is a 10-line function that replaces what would be 30 lines of imperative filtering, with the type system guaranteeing you can only call it on things that have a due date.


Reference: the patterns in the box

The functional VB.NET already available, with what I use each for:

Pattern What it does When to use
LINQ Pipeline-style data transformation Whenever I have a collection
Extension methods Add methods to existing types Repeated transformations on a type
Iterator + Yield Lazy sequences Generating sequences without building lists
Lambdas + AddressOf Functions as values Passing behavior to LINQ or storing it
Generic constraints Type classes for domain modeling Operations across unrelated types
Anonymous With vars Cleaner nested access Deep property chains

I have been writing VB.NET for years and use at least three of these daily. The goal is to use them deliberately, to see a function and know which pattern fits before I start writing.


Next

Part 2 — Bind Chains in VB.NET. When LINQ is not enough. Maybe(Of T), Result(Of T, F), discriminated unions via OneOf, railroad programming for error handling. See vbnet-monads-bind-chains.md.

Part 3 — Domain Modeling in VB.NET. Records, type classes, discriminated unions. A worked example pulling it all together. See vbnet-domain-modeling-records.md.

Reply to this! (Via Email)

Home • Blog • 爪闩ㄒ尺工乂 • ©Copyright 2026 •