Skip to content

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

@joslat

Description

@joslat

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

Metadata

Metadata

Assignees

Labels

.NETworkflowsRelated to Workflows in agent-framework

Type

No type

Projects

Status

No status

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions