Combine Overloads¶
Multi-arity Combine extensions that flatten nested tuples and auto-collect plural providers.
The Problem¶
Roslyn's built-in Combine only pairs two providers, producing nested tuples:
// Each .Combine() wraps in another layer
a.Combine(b) // (A, B)
a.Combine(b).Combine(c) // ((A, B), C)
a.Combine(b).Combine(c).Combine(d) // (((A, B), C), D)
// Destructuring gets painful fast
var (((webApp, dispatches), commands), queries) = tuple;
You also need manual .Collect() calls to convert IncrementalValuesProvider<T> (plural) to IncrementalValueProvider<ImmutableArray<T>> (singular) before combining.
The Solution¶
The CombineExtensions class provides overloads that accept multiple providers, call .Collect() internally, and return flat tuples:
using Deepstaging.Roslyn.Generators;
var combined = modules
.Combine(commandHandlers, queryHandlers)
.Select(static (tuple, _) =>
{
var (module, commands, queries) = tuple;
// commands: ImmutableArray<CommandHandlerGroupModel>
// queries: ImmutableArray<QueryHandlerGroupModel>
...
});
Available Overloads¶
All overloads accept IncrementalValuesProvider<T> (plural) parameters and collect them automatically.
On IncrementalValuesProvider<T> (plural source)¶
// 2 additional providers → flat 3-tuple
source.Combine(second, third)
// → IncrementalValuesProvider<(T1, ImmutableArray<T2>, ImmutableArray<T3>)>
// 3 additional providers → flat 4-tuple
source.Combine(second, third, fourth)
// → IncrementalValuesProvider<(T1, ImmutableArray<T2>, ImmutableArray<T3>, ImmutableArray<T4>)>
On IncrementalValueProvider<T> (singular source)¶
// 2 additional providers → flat 3-tuple
source.Combine(second, third)
// → IncrementalValueProvider<(T1, ImmutableArray<T2>, ImmutableArray<T3>)>
// 3 additional providers → flat 4-tuple
source.Combine(second, third, fourth)
// → IncrementalValueProvider<(T1, ImmutableArray<T2>, ImmutableArray<T3>, ImmutableArray<T4>)>
Full Example¶
[Generator]
public sealed class WebEndpointGenerator : IIncrementalGenerator
{
public void Initialize(IncrementalGeneratorInitializationContext context)
{
var webApps = context.ForAttribute<WebAppAttribute>()
.Map(static (ctx, _) => ctx.TargetSymbol.AsValidNamedType().QueryWebApp());
var dispatches = context.ForAttribute<DispatchModuleAttribute>()
.Map(static (ctx, _) => ctx.TargetSymbol.AsValidNamedType().QueryDispatchModule());
var commands = context.ForAttribute<CommandHandlerAttribute>()
.Map(static (ctx, _) => ctx.TargetSymbol.AsValidNamedType().QueryCommandHandlerGroup());
var queries = context.ForAttribute<QueryHandlerAttribute>()
.Map(static (ctx, _) => ctx.TargetSymbol.AsValidNamedType().QueryQueryHandlerGroup());
var combined = webApps
.Combine(dispatches, commands, queries)
.Select(static (tuple, _) =>
{
var (webApp, dispatches, commands, queries) = tuple;
if (dispatches.Length == 0) return null;
var dispatch = dispatches[0] with
{
CommandHandlers = commands,
QueryHandlers = queries
};
return webApp.WithRoutes(dispatch);
})
.Where(static model => model is not null)
.Select(static (model, _) => model!);
context.RegisterSourceOutput(combined, static (ctx, model) =>
{
EndpointWriter.WriteEndpoints(model)
.AddSourceTo(ctx, HintName.From(model.Namespace, $"{model.TypeName}.WebApp"));
});
}
}