Codebelt
A magnifying glass revealing a small utility component.

Hidden Gems in Plain Sight

Cuemon.Kernel contains small helpers that are easy to overlook. A closer look at Patterns reveals reusable configuration delegates, explicit options translation, disposal during failed initialization, and exception filters with boundaries worth understanding.

LIBRARIES
  • .NET
  • Cuemon
  • Patterns
  • Configuration
  • IDisposable
  • Exception Handling
  • DevEx

Small APIs that deserve a closer look

Small Cuemon utility components revealed beneath a familiar .NET API surface.Small Cuemon utility components revealed beneath a familiar .NET API surface.

A library's most visible features tend to get the attention. The small methods underneath them are easier to miss, even when they solve problems that appear repeatedly in application code.

Cuemon has a good example in Patterns.cs. The class lives in the Cuemon namespace and ships in Cuemon.Kernel. Its surface includes configuration helpers, disposable initialization, and exception classification. None requires an application host to call it.

These helpers address familiar questions. How do you reuse a configuration delegate when another API expects a different options type? How do you turn an existing object back into an Action<TOptions>? Who disposes a resource if initialization fails before it reaches the caller?

The implementations are small enough to read. That is part of their value: you can see exactly which responsibility each helper takes on and which responsibilities remain yours.

Object construction with a configuration delegate

C# already provides constructors and object initializers. When the settings are known at the call site, ordinary construction is usually sufficient. A delegate becomes useful when another layer supplies the setup, or when the same setup needs to be passed between APIs.

Patterns.CreateInstance<T> creates a class through its public parameterless constructor and invokes an optional Action<T> on the result. It has no parameter-object requirement.

Patterns.Configure<TOptions> narrows that idea to classes implementing IParameterObject and adds two optional delegates. Its execution order is explicit:

new TOptions() -> initializer -> setup -> validator -> return

The following illustrative options class makes that sequence visible:

using System;
using Cuemon;
using Cuemon.Configuration;

public sealed class ExportOptions : IParameterObject
{
    public int BufferSize { get; set; } = 4096;
    public string FileName { get; set; } = "export.csv";
}

public static class ExportConfiguration
{
    public static ExportOptions Create(Action<ExportOptions> setup)
    {
        return Patterns.Configure(
            setup,
            initializer: options => options.BufferSize = 8192,
            validator: options =>
            {
                if (options.BufferSize <= 0)
                {
                    throw new ArgumentOutOfRangeException(
                        nameof(options.BufferSize),
                        "The export buffer size must be positive.");
                }
            });
    }
}

The initializer supplies a baseline before the caller's setup runs. The validator sees the resulting values and can reject them before the object is returned. Exceptions from those delegates propagate.

There is a distinction worth preserving here: Patterns.Configure invokes the delegates you provide. It does not automatically call post-configuration or validation interfaces on the object. The broader Cuemon lifecycle is covered in Options Pattern Beyond ASP.NET Core; this method is the smaller construction primitive.

A null setup is accepted and leaves the initializer or constructor values in place. If your API requires callers to provide setup, enforce that requirement at your own boundary.

Translate configuration without repeating the caller's setup

Two APIs can describe similar settings with different option types. A wrapper might expose ExportOptions, while an underlying writer expects its own configuration object.

Patterns.ConfigureExchange<TSource, TResult> accepts an Action<TSource> and returns an Action<TResult>. It constructs and configures the source when you create the exchange delegate. The returned delegate then applies the mapping to a target supplied by its caller.

Using the preceding ExportOptions, an explicit mapping can look like this:

public sealed class WriterOptions
{
    public int Capacity { get; set; }
    public string Destination { get; set; } = string.Empty;
}

public static class WriterConfiguration
{
    public static Action<WriterOptions> Translate(Action<ExportOptions> setup)
    {
        return Patterns.ConfigureExchange<ExportOptions, WriterOptions>(
            setup,
            (source, target) =>
            {
                target.Capacity = source.BufferSize;
                target.Destination = source.FileName;
            });
    }
}

The source must implement IParameterObject; the target only needs to be a class with a public parameterless constructor. That makes the helper usable at a boundary with a configuration type you do not own.

When no mapping delegate is supplied, Cuemon uses reflection to copy public read-write properties with matching names and exact property types. Unmatched properties are skipped. If there are no matches at all, applying the returned delegate throws InvalidOperationException.

That default is useful for deliberately aligned option types. An explicit mapping is easier to review when names differ, units need conversion, or the target contract has different semantics. One matching property is enough for the default mapper to proceed, so it does not prove that every required target setting was populated.

The setup also runs once when the exchange is created, rather than each time the target delegate is invoked. If setup reads changing state, choose the point at which you create the exchange accordingly. Validate source and target invariants explicitly when the integration requires them.

Turn an existing object back into setup

The reverse problem occurs when you already have configured options but the receiving API accepts an Action<TOptions>.

Patterns.ConfigureRevert<TOptions> returns a delegate that copies public read-write property values from the supplied object to a target of the same type. It rejects a null source object.

public static class ReusableExportConfiguration
{
    public static Action<ExportOptions> Create()
    {
        var configured = Patterns.Configure<ExportOptions>(options =>
        {
            options.BufferSize = 16384;
            options.FileName = "orders.csv";
        });

        return Patterns.ConfigureRevert(configured);
    }
}

The returned action can be passed to an API that accepts setup, or back into Patterns.Configure to construct another options object.

This is a property copy. Reference-valued properties continue to refer to the same objects; nested objects are not cloned. The delegate reads the original object's properties when invoked, so later changes to that original can affect later invocations. Treat the source as immutable after creating the delegate if repeatable values are required.

Patterns.ConfigureRevertExchange<TSource, TResult> combines reversion with exchange when the existing object and the receiving API use different types. It carries the same mapping boundaries as ConfigureExchange.

These methods are useful where object-based and delegate-based APIs meet. They can remove repeated assignment code without forcing either API to change its public shape.

Dispose a resource when initialization cannot finish

Resource ownership is a different kind of small problem with larger consequences.

A using declaration disposes an acquired resource when its scope ends. A factory returning a disposable object has another obligation: release the object if work fails before ownership reaches the caller. Microsoft's CA2000 guidance describes this construction and ownership-transfer problem.

Patterns.SafeInvoke<TResult> packages the sequence into two delegates: one creates the resource, and one prepares it and returns the result.

using System.IO;
using System.Text;
using Cuemon;

public static class ExportBuffer
{
    public static MemoryStream Create(string header)
    {
        return Patterns.SafeInvoke(
            () => new MemoryStream(),
            stream =>
            {
                byte[] bytes = Encoding.UTF8.GetBytes(header);
                stream.Write(bytes, 0, bytes.Length);
                stream.Position = 0;
                return stream;
            });
    }
}

If preparation throws after the initializer returns, the helper disposes the initialized stream in finally. With no catcher supplied, it rethrows the exception. On success, the stream stays open and the caller becomes responsible for disposal.

Return the initialized resource itself, as above, unless you have explicitly designed the ownership relationship of the returned object. The implementation releases its cleanup reference after the tester returns successfully; it does not dispose the original merely because the tester returned a different object or null. Resources allocated inside an initializer that throws before returning also remain the initializer's responsibility.

The optional catcher changes the failure contract. If it handles the exception and returns normally, SafeInvoke returns null. Its internal catch is broad and does not apply the fatal-exception filter used by TryInvoke. Leave the catcher omitted when the failure must propagate. This API is synchronous and constrained to IDisposable; the separate Cuemon.Threading.AsyncPatterns class contains the task-based variants.

Exception classification is useful, but policy still belongs to the caller

Patterns.IsFatalException recognizes a specific set of exception types: OutOfMemoryException, StackOverflowException, SEHException, AccessViolationException, ThreadAbortException, ThreadInterruptedException, and the obsolete ExecutionEngineException.

Patterns.IsRecoverableException is its logical inverse. The names describe Cuemon's classification; they do not establish that an application can successfully recover from every exception outside the fatal list. The implementation examines the supplied exception directly rather than recursively inspecting inner exceptions.

That distinction matters when considering the nearby TryInvoke and InvokeOrDefault methods.

TryInvoke(Action) returns false when the action throws an exception outside the fatal set. The generic overload also writes default(TResult) to its out parameter. InvokeOrDefault builds on that behavior and returns the supplied fallback, or the type's default value, after failure. A null delegate also produces the failure result because argument validation occurs inside the try block.

These are explicit exception-suppression APIs. They can be appropriate when failure is an accepted part of the operation's contract and the caller only needs success or failure. They discard the exception details, however, and a fallback result can be indistinguishable from a legitimate result with the same value.

For expected parsing failures, prefer a dedicated API such as int.TryParse. Microsoft's Try-Parse guidance concerns a defined failure condition and avoiding exceptions for that condition. Wrapping a throwing parser in Patterns.TryInvoke still incurs the thrown exception and catches a broader range of failures.

For required configuration, persistence, or external dependencies, preserve actionable failure information. A helper that turns an exception into false or a default value cannot make that decision for the application.

Start with the boundary you actually need

The useful discovery in Patterns.cs is how little code some recurring boundaries require.

Use CreateInstance when a caller supplies object setup. Use ConfigureExchange or ConfigureRevert when configuration needs to cross between types or representations. Consider SafeInvoke when a factory must prepare a disposable resource before returning ownership. Read the exception helpers as policy tools whose consequences must be intentional.

You can explore these APIs through the Cuemon.Kernel package page and the Patterns API reference. Patterns.Use also exposes a singleton receiver for extension methods targeting Patterns; the static methods shown here are called directly.

Before adding another helper to an application's utility folder, inspect the small APIs in the libraries it already uses. Sometimes the missing piece has been public for years.

Timeline

The history below follows the stable releases that established the current Patterns surface. Source work predates the 6.0.0 release entry; that entry is a release milestone rather than a claim about the first implementation date.

  1. v6.0.0 2021-04-18

    Patterns became a documented utility surface in Cuemon.Core.

    The release changelog lists the class. During development of that release, SafeInvoke and SafeInvokeAsync moved from Disposable into Patterns, grouping the initialization helpers with the other small pattern utilities.

  2. v7.0.0 2022-11-09

    Construction and configuration exchange gained clearer contracts.

    The release added CreateInstance and ConfigureRevertExchange. Configuration and exchange methods gained the IParameterObject constraint, separating option objects from general object construction.

  3. v9.0.0 2024-11-13

    Task-based initialization moved to Cuemon.Threading.

    SafeInvokeAsync moved from Patterns to Cuemon.Threading.AsyncPatterns, establishing the separate location for asynchronous helpers.

  4. v10.3.0 2026-02-19

    Exception classification became part of the public API.

    IsFatalException and IsRecoverableException were added, and TryInvoke began filtering exceptions through that classification.

  5. v10.5.0 2026-03-13

    The foundational helpers moved into Cuemon.Kernel.

    The new kernel assembly took over Patterns alongside configuration, validation, decorator, and disposable infrastructure. The class retained its Cuemon namespace; the package boundary changed.

  6. v10.7.1 2026-09-10 (current)

    The current stable surface remains in Cuemon.Kernel.

    This patch release updated test infrastructure and dependencies. The synchronous configuration, exception, and ownership helpers discussed here remain in Cuemon.Kernel/Patterns.cs.

Sources