Skip to content

.NET: Excessive boilerplate for executors compared to Python - poor discoverability of simpler patterns #1505

Description

Summary

Creating executors in .NET requires significantly more boilerplate code compared to the Python SDK, creating a poor developer experience and high barrier to entry. While simpler patterns exist (e.g., .AsExecutor() for functions), they are poorly documented and not shown in official samples.
Python SDK (3 lines):

@executor(id="reverse_text_executor")
async def reverse_text(text: str, ctx: WorkflowContext[Never, str]) -> None:
    result = text[::-1]
    await ctx.yield_output(result)

.NET SDK - Official Pattern (10+ lines):

internal sealed class ReverseExecutor : ReflectingExecutor<ReverseExecutor>, IMessageHandler<string, string>
{
    public ReverseExecutor() : base("ReverseExecutor") { }

    public ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
    {
        var result = string.Concat(message.Reverse());
        return ValueTask.FromResult(result);
    }
}

.NET SDK - Undocumented Simpler Pattern (3 lines):

Func<string, IWorkflowContext, CancellationToken, ValueTask<string>> reverseFunc = 
    async (text, ctx, ct) => string.Concat(text.Reverse());
var executor = reverseFunc.AsExecutor("ReverseExecutor");

Problem: The simpler pattern exists but is hidden - all documentation and samples use the verbose class-based approach.


Current Issues

  1. Excessive Boilerplate Required
    Class-based pattern requires:
    • Creating a class (sealed for performance)
    • Inheriting from ReflectingExecutor with recursive generic
    • Implementing IMessageHandler<TIn, TOut> interface
    • Writing a constructor that calls base with ID
    • Implementing HandleAsync with all parameters
    • Understanding ValueTask, CancellationToken, etc.
    For a simple function, this is 10x more code than Python

  2. Poor Discoverability of Alternatives
    The .AsExecutor() extension method exists but:
    • ❌ Not mentioned in official documentation
    • ❌ Not shown in any samples
    • ❌ Not discoverable through IntelliSense on functions
    • ❌ No examples in getting started guide
    Result: Developers assume the verbose pattern is the only way.

  3. Documentation Mismatch
    Python tutorial (https://learn.microsoft.com/en-us/agent-framework/tutorials/workflows/simple-sequential-workflow?pivots=programming-language-python):
    • Shows clean, simple executor decorator
    • 3 lines of code
    • Focus on logic, not framework ceremony

.NET tutorial (https://learn.microsoft.com/en-us/agent-framework/tutorials/workflows/simple-sequential-workflow?pivots=programming-language-csharp):
• Shows verbose class-based pattern
• 10+ lines of boilerplate
• Framework ceremony obscures business logic

Creates perception that C# SDK is harder to use

  1. High Barrier to Entry
    New developers face:
    • Must learn ReflectingExecutor, IMessageHandler, ValueTask
    • Must understand generic type parameters
    • Must know C# async patterns
    • Must create multiple files/classes for simple logic

Python developers switching to C# face significant friction
I myself I am tempted to switch to Python, basically...


Proposed Solutions

Solution 1: Promote Function-Based Pattern in Documentation
Update documentation and samples to show simpler pattern first:

// ✅ Simple function-based executor (recommended for simple logic)
var reverseExecutor = ((string text, IWorkflowContext ctx, CancellationToken ct) 
    => ValueTask.FromResult(string.Concat(text.Reverse())))
    .AsExecutor("ReverseExecutor");

// Class-based executor (use for complex logic with state)
internal sealed class ComplexExecutor : ReflectingExecutor<ComplexExecutor>, IMessageHandler<string, string>
{
    // Use when you need: state, dependency injection, complex logic
}

Changes needed:
• Update tutorial to show function-based pattern first
• Add comparison section: "Simple vs Complex Executors"
• Update all samples to use function-based for simple cases
• Add XML docs to .AsExecutor() extension method
• Create "Best Practices" guide


Solution 2: Decorator based by using Source Generators - very similar to Python conceptually

[Executor("reverse_text_executor")]
public static string ReverseText(string text)
{
    return string.Concat(text.Reverse());
}

// Source generator creates boilerplate class automatically

Pros: Best of both worlds - simple syntax, full power
Cons: Requires source generator infrastructure, C# 9+

Impact

• Priority: High (affects all developers, especially new users)
• Severity: Major developer experience issue
• Audience:
• New developers learning the framework
• Python developers migrating to C#
• Developers building simple workflows
• Workaround Available: Yes (.AsExecutor() exists but undocumented)
• User Experience: Poor - excessive ceremony discourages adoption
• Perception: Makes C# SDK appear more complex than it needs to be

Activity

  1. added
    .NETUsage: [Issues, PRs], Target: .Net
    pythonUsage: [Issues, PRs], Target: Python
    triageUsage: [Issues], Target: All issues that still need to be triaged
    on Oct 15, 2025
  2. changed the title [-].NET: Excessive boilerplate for executors compared to Python - poor discoverability of simpler patterns[/-] [+]Python: .NET: Excessive boilerplate for executors compared to Python - poor discoverability of simpler patterns[/+] on Oct 15, 2025
  3. moonbox3 commented on Oct 16, 2025

    @moonbox3
    Contributor
  4. changed the title [-]Python: .NET: Excessive boilerplate for executors compared to Python - poor discoverability of simpler patterns[/-] [+].NET: Excessive boilerplate for executors compared to Python - poor discoverability of simpler patterns[/+] on Oct 16, 2025
  5. added
    workflowsUsage: [Issues, PRs], Target: Workflows
    and removed
    pythonUsage: [Issues, PRs], Target: Python
    on Oct 16, 2025
  6. Ryan-Palmer commented on Oct 19, 2025

    @Ryan-Palmer

    As an F# dev I'm very pleased to see there is a functional API for workflows instead of the class based approach. I agree that it is extremely verbose, almost to a parody.

  7. lokitoth commented on Oct 21, 2025

    @lokitoth
    Contributor

    The description here is a little inaccurate, though the samples/docs do not have a solid coverage and need expansion:

    1. You do not need to use the ReflectingExecutor pattern if you only want to handle a single message type: The simpler Executor<TInput> or Executor<TInput, TOutput> are available that prefer to implement a class, but also the .AsExecutor() mechanism to wrap a pure function in an executor.

    2. You can choose to have synchronous as well as asynchronous handlers, as well as those taking CancellationToken and not if you use the ConfigureRoutes low-level API for mapping the message handling. ReflectingExecutor and others simplify this away from you to some extent.

  8. lokitoth commented on Jan 26, 2026

    @lokitoth
    Contributor

    Further improvements here with #3131

    There is not currently a design for simplifying the function executor, because unlike in Python, .NET Source Generators cannot replace a syntactic element with another one, so we would either need a convention or have the user declare the type themselves or as a parameter to the argument (which feels clunky).

    I will spend some more time thinking about a good API here, though any further improvements for Function-as-Executor, specifically, may not land for v1.0.

  9. markwallace-microsoft commented on Mar 20, 2026

    @markwallace-microsoft
    Contributor

    Jose Luis Latorre Millas (@joslat), friendly reminder — this issue is waiting on your response. Please share any updates when you get a chance. (This is an automated message.)

  10. added
    questionUsage: [Issues, PRs], Target: all issues or PRs where additional context is needed from the author
    and removed on Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

.NETUsage: [Issues, PRs], Target: .NetquestionUsage: [Issues, PRs], Target: all issues or PRs where additional context is needed from the authorworkflowsUsage: [Issues, PRs], Target: Workflows

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions