Skip to content

Generic Result Binding: Remove unnecessary type parameter from ModuleResult<T> #1876

@thomhurst

Description

@thomhurst

Problem

ModuleResult has a generic type parameter that doesn't add value in most scenarios:

  • Type Parameter Rarely Used: Most modules don't use the result type
  • Verbose Declarations: Module<string> vs Module<void> creates noise
  • Unnecessary Complexity: Generic binding adds complexity without much benefit
  • Poor Ergonomics: Developers always specify T, often void or a rarely-used type
  • Composition Issues: Generic binding makes composition patterns harder

Current Design

// Every module must specify result type, even if unused
public class BuildModule : Module<string> // Returns build log string
{
    protected override async Task<string> ExecuteAsync(
        IModuleContext context,
        CancellationToken cancellationToken)
    {
        return "Build successful";
    }
}

public class CleanModule : Module<void> // Returns nothing
{
    protected override async Task<void> ExecuteAsync(
        IModuleContext context,
        CancellationToken cancellationToken)
    {
        // void is awkward here
    }
}

Proposed Solution

Remove generic result binding, provide access to results through context:

// Simpler base class, no type parameter
public class BuildModule : Module
{
    // Results stored in context for modules that need them
    protected override async Task ExecuteAsync(
        IPipelineContext context,
        CancellationToken cancellationToken)
    {
        var result = "Build successful";
        context.SetModuleResult(result); // Store if needed
    }
}

// Or provide explicit output mechanism
public class ReportModule : Module
{
    public string? GeneratedReport { get; private set; }
    
    protected override async Task ExecuteAsync(...)
    {
        GeneratedReport = "Report content";
    }
}

// Accessing results
var report = context.GetModuleResult<string>(typeof(BuildModule));
// Or directly
var buildModule = await host.GetModuleAsync<BuildModule>();
var result = buildModule.GeneratedReport;

Benefits

  • Simpler API (no generic type parameter)
  • Cleaner module declarations
  • Easier for modules without return values
  • Composition becomes simpler
  • Focus on side effects (more common pattern)
  • Less cognitive load

Trade-offs

  • Less type safety for results (type safety moved to context)
  • More explicit result access

Priority: LOW - Simplification with some trade-offs

Metadata

Metadata

Assignees

No one assigned

    Labels

    .NETPull requests that update .net codeenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions