Course contentsShow
← C#
Lesson 428 of 1,9084. Functional Programming with LINQFree lesson

Bridging Imperative and Declarative Code

Manage the interop between legacy APIs and modern functional LINQ pipelines without breaking contracts.

In this lesson, you will learn how to seamlessly integrate traditional, stateful imperative logic into your elegant, functional LINQ pipelines without violating the purity of your data streams.

The Conflict: State vs. Flow

We often work with legacy APIs—like old logging services, event emitters, or external state trackers—that require imperative, side-effect-heavy calls. If you try to force these into a standard LINQ projection (Select), you break the "functional" contract because Select expects a transformation, not a command.

The bridge between these worlds is the Do pattern (often implemented as a custom extension method) or using ToList() as a hard boundary.

Bridging with Side Effects

Imagine you have a stream of Order objects. You want to process them declaratively, but you are required to log each one to a legacy audit system that isn't built for streams.

If you attempt this inside a Select, you are technically performing a side effect, which can lead to unpredictable behavior if the query is deferred or executed multiple times. Instead, create a bridge:

public static IEnumerable<T> Peek<T>(this IEnumerable<T> source, Action<T> action) {
    foreach (var item in source) {
        action(item); // The imperative bridge
        yield return item; // Maintaining the declarative flow
    }
}

// Usage in your pipeline
var processedOrders = rawOrders
    .Where(o => o.IsValid)
    .Peek(o => legacyLogger.Write($"Processing: {o.Id}")) // Imperative bridge
    .Select(o => o.ApplyDiscount()); // Declarative transformation

Why This Matters

By isolating the imperative code within a "bridge" method like Peek (or simply using ToList() to force execution before an imperative foreach loop), you keep your primary business logic readable and declarative. You aren't "polluting" your pure transformations with state-dependent calls.

This approach preserves the integrity of your LINQ pipeline, ensuring that the declarative nature of your code remains predictable, while still satisfying the rigid requirements of older, stateful APIs.

Key Takeaway: Use controlled wrapper methods or explicit execution boundaries to encapsulate imperative side effects, keeping your primary LINQ queries focused on pure data transformation.