Generated Code¶
The EmailModule declaration generates effect methods and DI registration. No runtime code generation — the generated layer wraps the hand-written IEmailService interface.
What Gets Generated¶
Given this declaration:
[EffectsModule(typeof(IEmailService))]
[ServiceRegistration(nameof(AddDefaultEmail))]
public static partial class EmailModule
{
public static void AddDefaultEmail(this IServiceCollection services)
{
services.TryAddSingleton<IEmailService, InMemoryEmailService>();
}
}
The EffectsGenerator produces:
Effect Methods¶
// Generated: EmailModule.Email.SendAsync<RT>(message)
public static partial class EmailModule
{
public static partial class Email
{
public static Eff<RT, EmailResult> SendAsync<RT>(
EmailMessage message)
where RT : IHasEmailService =>
liftEff<RT, EmailResult>(async rt =>
await rt.EmailService.SendAsync(message));
public static Eff<RT, IReadOnlyList<EmailResult>> SendBatchAsync<RT>(
IReadOnlyList<EmailMessage> messages)
where RT : IHasEmailService =>
liftEff<RT, IReadOnlyList<EmailResult>>(async rt =>
await rt.EmailService.SendBatchAsync(messages));
}
}
Capability Interface¶
// Generated: capability for runtime constraint
public interface IHasEmailService
{
IEmailService EmailService { get; }
}
DI Registration¶
The [ServiceRegistration] attribute causes the runtime bootstrapper to call AddDefaultEmail() during Add{Runtime}(). Because it uses TryAddSingleton, production providers registered first (e.g., SES) take priority over InMemoryEmailService.
Using Without Effects¶
You can inject IEmailService directly without the effects layer:
public class OrderService(IEmailService emailService)
{
public async Task SendConfirmation(Order order)
{
await emailService.SendAsync(new EmailMessage
{
From = new EmailAddress("orders@example.com"),
To = [new EmailAddress(order.CustomerEmail)],
Subject = $"Order #{order.Id} Confirmed",
HtmlBody = RenderTemplate(order)
});
}
}
For functional effect composition, see Effects Composition.