Feature: Factory Pattern Source Generator
A source generator that produces boilerplate free, GoF consistent factory implementations without requiring PatternKit at runtime. This generator lives in PatternKit.Generators and allows developers to implement Factory Method and simple Abstract Factory patterns using attributes. The generator emits idiomatic C# that is easy to maintain and entirely self contained.
Two factory models are supported:
- Keyed Factory Methods
- Creator Based Type Factories (GoF Factory Method)
Both models support synchronous and asynchronous variants, with ValueTask favored when generating async code.
Goals
- Eliminate factory boilerplate.
- Encourage GoF aligned design without enforcing external frameworks.
- Require no runtime PatternKit dependencies.
- Generate readable code that is a drop in replacement for handwritten factories.
- Provide async variations, preferring ValueTask where applicable.
Factory Pattern Coverage
1. Keyed Factory Method Generation
A keyed factory maps:
Keyed factories are appropriate when selecting behavior, not concrete types. All methods participating in a keyed factory must share the same signature and return type.
Supported behavior:
Generated methods include:
Create(...)
TryCreate(...)
CreateAsync(...) returning ValueTask<TResult>
TryCreateAsync(...) returning ValueTask<(bool Success, TResult Result)>
2. Creator Based Type Factory Generation
This model mirrors the GoF Factory Method: a base product type and one or more concrete implementations. The generator produces a factory (Creator) capable of constructing the appropriate instance based on a key.
Supported behavior:
- Interfaces or abstract classes as product bases.
- Concrete types annotated with unique keys.
- Async creation if any product exposes async creation methods.
The generated factory uses ValueTask for async methods whenever possible.
Generated members include:
Create(key)
TryCreate(key, out TProduct)
- Optional enum based creation
CreateAsync(key) returning ValueTask<TProduct>
TryCreateAsync(key) returning ValueTask<(bool Success, TProduct Result)>
Async generation rules:
- If any concrete product has a static async factory method returning
Task<T> or ValueTask<T>, the generator uses ValueTask in the generated signatures.
- If all products are synchronous, async variants are omitted unless explicitly enabled later.
Source Generator API
All attributes reside in PatternKit.Generators.Factories.
A. Keyed Factory Attributes
KeyedFactoryAttribute
[AttributeUsage(AttributeTargets.Class)]
public sealed class KeyedFactoryAttribute : Attribute
{
public Type KeyType { get; }
public string CreateMethodName { get; set; } = "Create";
public bool CaseInsensitiveStrings { get; set; } = true;
public KeyedFactoryAttribute(Type keyType)
{
KeyType = keyType;
}
}
FactoryCaseAttribute
[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)]
public sealed class FactoryCaseAttribute : Attribute
{
public object Key { get; }
public FactoryCaseAttribute(object key)
{
Key = key;
}
}
FactoryDefaultAttribute
[AttributeUsage(AttributeTargets.Method)]
public sealed class FactoryDefaultAttribute : Attribute { }
B. Creator Factory Attributes
CreatorFactoryAttribute
[AttributeUsage(AttributeTargets.Interface | AttributeTargets.Class)]
public sealed class CreatorFactoryAttribute : Attribute
{
public Type KeyType { get; }
public string FactoryTypeName { get; set; }
public bool GenerateTryCreate { get; set; } = true;
public bool GenerateEnumKeys { get; set; } = false;
public CreatorFactoryAttribute(Type keyType)
{
KeyType = keyType;
}
}
CreatorKeyAttribute
[AttributeUsage(AttributeTargets.Class)]
public sealed class CreatorKeyAttribute : Attribute
{
public object Key { get; }
public CreatorKeyAttribute(object key)
{
Key = key;
}
}
Generator Behavior
Keyed Factory Example
Input:
[KeyedFactory(typeof(string))]
public static partial class MimeFactory
{
[FactoryCase("json")]
public static string Json() => "application/json";
[FactoryDefault]
public static string Default() => "application/octet-stream";
}
Generated synchronous output:
public static partial class MimeFactory
{
public static string Create(string key)
{
if (key is null) throw new ArgumentNullException(nameof(key));
if (string.Equals(key, "json", StringComparison.OrdinalIgnoreCase))
return Json();
return Default();
}
public static bool TryCreate(string key, out string value)
{
if (key is null)
{
value = default!;
return false;
}
if (string.Equals(key, "json", StringComparison.OrdinalIgnoreCase))
{
value = Json();
return true;
}
value = Default();
return false;
}
}
If any mapping method is async, the generator emits:
public static ValueTask<string> CreateAsync(string key);
public static ValueTask<(bool Success, string Result)> TryCreateAsync(string key);
ValueTask is used unless Task based signatures cannot be rewritten.
Creator Factory Example
Input:
[CreatorFactory(typeof(string), FactoryTypeName = "NotificationFactory")]
public interface INotification {}
[CreatorKey("email")]
public class EmailNotification : INotification {}
[CreatorKey("sms")]
public class SmsNotification : INotification {}
Generated output:
public sealed partial class NotificationFactory
{
public INotification Create(string key)
{
return key switch
{
"email" => new EmailNotification(),
"sms" => new SmsNotification(),
_ => throw new ArgumentOutOfRangeException(nameof(key))
};
}
public bool TryCreate(string key, out INotification result)
{
switch (key)
{
case "email":
result = new EmailNotification();
return true;
case "sms":
result = new SmsNotification();
return true;
default:
result = default!;
return false;
}
}
}
Async variants:
public ValueTask<INotification> CreateAsync(string key);
public ValueTask<(bool Success, INotification Result)> TryCreateAsync(string key);
Async result types are ValueTask unless underlying implementations force the use of Task.
Diagnostics
Keyed Factories
- KF001: Type with KeyedFactoryAttribute must be a static partial class.
- KF002: All FactoryCase and FactoryDefault methods must share the same signature.
- KF003: Duplicate keys detected.
- KF004: Multiple default methods declared.
- KF005: Key value is incompatible with the declared KeyType.
Creator Factories
- CF001: CreatorFactoryAttribute must annotate an interface or abstract class.
- CF002: Types with CreatorKeyAttribute must implement or inherit the annotated type.
- CF003: Types must be concrete and constructible.
- CF004: Duplicate keys detected.
- CF005: Mismatched key type.
Async Generation Guidelines
The generator uses the following rules:
- If all async capable methods return ValueTask, generated async APIs use ValueTask.
- If methods return Task, the generator attempts to wrap them in ValueTask where safe.
- If a signature cannot be rewritten safely, Task is preserved.
- If no async capable methods exist, async APIs are omitted.
This ensures minimal allocations and optimal performance while preserving correctness.
Next Steps
- Implement incremental generator skeleton.
- Implement semantic validation for keys and signatures.
- Generate synchronous and asynchronous factory methods.
- Generate ValueTask based async methods where appropriate.
- Add Roslyn diagnostics.
- Document usage and examples.
Feature: Factory Pattern Source Generator
A source generator that produces boilerplate free, GoF consistent factory implementations without requiring PatternKit at runtime. This generator lives in
PatternKit.Generatorsand allows developers to implement Factory Method and simple Abstract Factory patterns using attributes. The generator emits idiomatic C# that is easy to maintain and entirely self contained.Two factory models are supported:
Both models support synchronous and asynchronous variants, with ValueTask favored when generating async code.
Goals
Factory Pattern Coverage
1. Keyed Factory Method Generation
A keyed factory maps:
Keyed factories are appropriate when selecting behavior, not concrete types. All methods participating in a keyed factory must share the same signature and return type.
Supported behavior:
Arbitrary key types.
One or more input parameters.
Optional default method.
Async generation using:
ValueTask<TResult>whenever any mapping method returnsValueTask<TResult>ValueTask<TResult>when all methods returnTask<TResult>(generator rewrites to ValueTask to reduce allocations)Task<TResult>only when required (for example, when method signatures already expose Task based APIs the generator cannot rewrite)Generated methods include:
Create(...)TryCreate(...)CreateAsync(...)returningValueTask<TResult>TryCreateAsync(...)returningValueTask<(bool Success, TResult Result)>2. Creator Based Type Factory Generation
This model mirrors the GoF Factory Method: a base product type and one or more concrete implementations. The generator produces a factory (Creator) capable of constructing the appropriate instance based on a key.
Supported behavior:
The generated factory uses ValueTask for async methods whenever possible.
Generated members include:
Create(key)TryCreate(key, out TProduct)CreateAsync(key)returningValueTask<TProduct>TryCreateAsync(key)returningValueTask<(bool Success, TProduct Result)>Async generation rules:
Task<T>orValueTask<T>, the generator uses ValueTask in the generated signatures.Source Generator API
All attributes reside in
PatternKit.Generators.Factories.A. Keyed Factory Attributes
KeyedFactoryAttributeFactoryCaseAttributeFactoryDefaultAttributeB. Creator Factory Attributes
CreatorFactoryAttributeCreatorKeyAttributeGenerator Behavior
Keyed Factory Example
Input:
Generated synchronous output:
If any mapping method is async, the generator emits:
ValueTask is used unless Task based signatures cannot be rewritten.
Creator Factory Example
Input:
Generated output:
Async variants:
Async result types are ValueTask unless underlying implementations force the use of Task.
Diagnostics
Keyed Factories
Creator Factories
Async Generation Guidelines
The generator uses the following rules:
This ensures minimal allocations and optimal performance while preserving correctness.
Next Steps