Extension Methods and Fluent APIs

Extension methods must live in a Module and carry the <Extension> attribute. They're resolved statically, so instance methods win over extensions. They aren't available on late-bound Object variables.

Imports System.Runtime.CompilerServices

Public Module EnumerableExtensions
    <Extension>
    Public Function WhereNotNull(Of T As Class)(source As IEnumerable(Of T)) As IEnumerable(Of T)
        Return source.Where(Function(x) x IsNot Nothing)
    End Function

    <Extension>
    Public Iterator Function Batch(Of T)(source As IEnumerable(Of T), size As Integer) As IEnumerable(Of List(Of T))
        Dim bucket As New List(Of T)(size)
        For Each item In source
            bucket.Add(item)
            If bucket.Count = size Then
                Yield bucket
                bucket = New List(Of T)(size)
            End If
        Next
        If bucket.Count > 0 Then Yield bucket
    End Function

    <Extension>
    Public Sub ForEachItem(Of T)(source As IEnumerable(Of T), action As Action(Of T))
        For Each item In source : action(item) : Next
    End Sub
End Module

Public Module StringExtensions
    <Extension> Public Function OrDefault(s As String, fallback As String) As String
        Return If(String.IsNullOrWhiteSpace(s), fallback, s)
    End Function
End Module

' Fluent builder
Public Class EmailBuilder
    Private ReadOnly _msg As New EmailMessage()
    Public
…