-
Notifications
You must be signed in to change notification settings - Fork 0
feat(generators): Add Singleton pattern generator #107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
ca2ea8a
feat(generators): Add Singleton pattern generator
JerrettDavis 6b6b755
test(singleton): Add generator tests
JerrettDavis e74e640
docs(singleton): Add generator documentation
JerrettDavis 606093a
feat(examples): Add SingletonGeneratorDemo
JerrettDavis ed58e4b
fix(singleton): Address PR review comments
JerrettDavis 6a60103
fix(singleton): Address second round of PR review comments
JerrettDavis ccb7756
fix(singleton): Address third round of PR review comments
JerrettDavis File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
26 changes: 26 additions & 0 deletions
26
src/PatternKit.Examples/SingletonGeneratorDemo/AppClock.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| using PatternKit.Generators.Singleton; | ||
|
|
||
| namespace PatternKit.Examples.SingletonGeneratorDemo; | ||
|
|
||
| /// <summary> | ||
| /// Application clock singleton using eager initialization. | ||
| /// Provides a consistent time source throughout the application, | ||
| /// which is especially useful for testing (can be mocked). | ||
| /// </summary> | ||
| [Singleton] // Defaults to eager initialization | ||
| public partial class AppClock | ||
| { | ||
| /// <summary>Gets the current UTC time.</summary> | ||
| public DateTime UtcNow => DateTime.UtcNow; | ||
|
|
||
| /// <summary>Gets the current local time.</summary> | ||
| public DateTimeOffset Now => DateTimeOffset.Now; | ||
|
|
||
| /// <summary>Gets the current date (UTC).</summary> | ||
| public DateOnly Today => DateOnly.FromDateTime(DateTime.UtcNow); | ||
|
|
||
| /// <summary>Gets the Unix timestamp in seconds.</summary> | ||
| public long UnixTimestamp => DateTimeOffset.UtcNow.ToUnixTimeSeconds(); | ||
|
|
||
| private AppClock() { } | ||
| } |
57 changes: 57 additions & 0 deletions
57
src/PatternKit.Examples/SingletonGeneratorDemo/ConfigManager.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| using PatternKit.Generators.Singleton; | ||
|
|
||
| namespace PatternKit.Examples.SingletonGeneratorDemo; | ||
|
|
||
| /// <summary> | ||
| /// Configuration manager singleton using lazy initialization with custom factory. | ||
| /// Demonstrates real-world pattern for managing application configuration. | ||
| /// </summary> | ||
| [Singleton(Mode = SingletonMode.Lazy)] | ||
| public partial class ConfigManager | ||
| { | ||
| /// <summary>Gets the application name.</summary> | ||
| public string AppName { get; } | ||
|
|
||
| /// <summary>Gets the current environment (Development, Staging, Production).</summary> | ||
| public string Environment { get; } | ||
|
|
||
| /// <summary>Gets the database connection string.</summary> | ||
| public string ConnectionString { get; } | ||
|
|
||
| /// <summary>Gets whether debug logging is enabled.</summary> | ||
| public bool DebugLogging { get; } | ||
|
|
||
| /// <summary>Gets the configuration load timestamp.</summary> | ||
| public DateTime LoadedAt { get; } | ||
|
|
||
| private ConfigManager(string appName, string environment, string connectionString, bool debugLogging) | ||
| { | ||
| AppName = appName; | ||
| Environment = environment; | ||
| ConnectionString = connectionString; | ||
| DebugLogging = debugLogging; | ||
| LoadedAt = DateTime.UtcNow; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Factory method that loads configuration from environment variables. | ||
| /// In a real application, this might read from a config file or service. | ||
| /// </summary> | ||
| [SingletonFactory] | ||
| private static ConfigManager Create() | ||
| { | ||
| // In a real app, this would read from appsettings.json, environment, etc. | ||
| var appName = System.Environment.GetEnvironmentVariable("APP_NAME") ?? "PatternKitDemo"; | ||
| var environment = System.Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Development"; | ||
| var connectionString = System.Environment.GetEnvironmentVariable("CONNECTION_STRING") ?? "Server=localhost;Database=Demo"; | ||
| var debugLogging = System.Environment.GetEnvironmentVariable("DEBUG_LOGGING") == "true"; | ||
|
|
||
| return new ConfigManager(appName, environment, connectionString, debugLogging); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Returns a string representation of the current configuration. | ||
| /// </summary> | ||
| public override string ToString() => | ||
| $"ConfigManager[App={AppName}, Env={Environment}, Debug={DebugLogging}, LoadedAt={LoadedAt:HH:mm:ss}]"; | ||
| } |
83 changes: 83 additions & 0 deletions
83
src/PatternKit.Examples/SingletonGeneratorDemo/ServiceRegistry.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,83 @@ | ||
| using PatternKit.Generators.Singleton; | ||
| using System.Collections.Concurrent; | ||
|
|
||
| namespace PatternKit.Examples.SingletonGeneratorDemo; | ||
|
|
||
| /// <summary> | ||
| /// Simple service registry singleton demonstrating thread-safe lazy initialization. | ||
| /// In production, consider using a proper DI container like Microsoft.Extensions.DependencyInjection. | ||
| /// </summary> | ||
| [Singleton(Mode = SingletonMode.Lazy, Threading = SingletonThreading.ThreadSafe)] | ||
| public partial class ServiceRegistry | ||
| { | ||
| // Use Lazy<object> for thread-safe single-call factory semantics | ||
| private readonly ConcurrentDictionary<Type, Lazy<object>> _services = new(); | ||
|
|
||
| private ServiceRegistry() { } | ||
|
|
||
| /// <summary> | ||
| /// Registers a service instance. | ||
| /// </summary> | ||
| public void Register<TService>(TService service) where TService : class | ||
| { | ||
| ArgumentNullException.ThrowIfNull(service); | ||
| _services[typeof(TService)] = new Lazy<object>(() => service); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Registers a factory for lazy service creation. | ||
| /// The factory is guaranteed to be called at most once, even under concurrent access. | ||
| /// </summary> | ||
| public void RegisterFactory<TService>(Func<TService> factory) where TService : class | ||
| { | ||
| ArgumentNullException.ThrowIfNull(factory); | ||
| _services[typeof(TService)] = new Lazy<object>(() => factory()); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Resolves a registered service. | ||
| /// </summary> | ||
| /// <exception cref="InvalidOperationException">Service not registered.</exception> | ||
| public TService Resolve<TService>() where TService : class | ||
| { | ||
| var type = typeof(TService); | ||
|
|
||
| if (_services.TryGetValue(type, out var lazy)) | ||
| { | ||
| return (TService)lazy.Value; | ||
| } | ||
|
|
||
| throw new InvalidOperationException($"Service {type.Name} is not registered."); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Tries to resolve a service, returning null if not found. | ||
| /// </summary> | ||
| public TService? TryResolve<TService>() where TService : class | ||
| { | ||
| var type = typeof(TService); | ||
|
|
||
| if (_services.TryGetValue(type, out var lazy)) | ||
| { | ||
| return (TService)lazy.Value; | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Checks if a service is registered. | ||
| /// </summary> | ||
| public bool IsRegistered<TService>() where TService : class | ||
| { | ||
| return _services.ContainsKey(typeof(TService)); | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Clears all registrations. Useful for testing. | ||
| /// </summary> | ||
| public void Clear() | ||
| { | ||
| _services.Clear(); | ||
| } | ||
| } |
98 changes: 98 additions & 0 deletions
98
src/PatternKit.Generators.Abstractions/Singleton/SingletonAttributes.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| namespace PatternKit.Generators.Singleton; | ||
|
|
||
| /// <summary> | ||
| /// Marks a partial class or record class for Singleton pattern code generation. | ||
| /// Generates a thread-safe singleton instance accessor with configurable initialization mode. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// The generator supports two initialization modes: | ||
| /// <list type="bullet"> | ||
| /// <item><description>Eager: Instance is created when the type is first accessed (static field initializer)</description></item> | ||
| /// <item><description>Lazy: Instance is created on first access to the Instance property</description></item> | ||
| /// </list> | ||
| /// For Lazy mode, thread-safety is configurable via the <see cref="Threading"/> property. | ||
| /// </remarks> | ||
| [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] | ||
| public sealed class SingletonAttribute : Attribute | ||
| { | ||
| /// <summary> | ||
| /// Gets or sets the singleton initialization mode. | ||
| /// Default is <see cref="SingletonMode.Eager"/>. | ||
| /// </summary> | ||
| public SingletonMode Mode { get; set; } = SingletonMode.Eager; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the threading model for singleton access. | ||
| /// Default is <see cref="SingletonThreading.ThreadSafe"/>. | ||
| /// Only applies when <see cref="Mode"/> is <see cref="SingletonMode.Lazy"/>. | ||
| /// </summary> | ||
| public SingletonThreading Threading { get; set; } = SingletonThreading.ThreadSafe; | ||
|
|
||
| /// <summary> | ||
| /// Gets or sets the name of the generated singleton instance property. | ||
| /// Default is "Instance". | ||
| /// </summary> | ||
| public string InstancePropertyName { get; set; } = "Instance"; | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Specifies when the singleton instance is created. | ||
| /// </summary> | ||
| public enum SingletonMode | ||
| { | ||
| /// <summary> | ||
| /// Instance is created when the type is first accessed. | ||
| /// Uses a static field initializer for simple, thread-safe initialization. | ||
| /// </summary> | ||
| Eager = 0, | ||
|
|
||
| /// <summary> | ||
| /// Instance is created on first access to the Instance property. | ||
| /// Uses <see cref="System.Lazy{T}"/> for thread-safe lazy initialization. | ||
| /// </summary> | ||
| Lazy = 1 | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Specifies the threading model for singleton instance access. | ||
| /// </summary> | ||
| public enum SingletonThreading | ||
| { | ||
| /// <summary> | ||
| /// Thread-safe singleton access using locks or Lazy<T>. | ||
| /// Recommended for most scenarios. | ||
| /// </summary> | ||
| ThreadSafe = 0, | ||
|
|
||
| /// <summary> | ||
| /// No thread synchronization. Faster but only safe in single-threaded scenarios. | ||
| /// WARNING: May result in multiple instance creation if accessed from multiple threads. | ||
| /// </summary> | ||
| SingleThreadedFast = 1 | ||
| } | ||
|
|
||
| /// <summary> | ||
| /// Marks a static method as the factory for creating the singleton instance. | ||
| /// The method must be static, parameterless, and return the containing type. | ||
| /// </summary> | ||
| /// <remarks> | ||
| /// Use this when the singleton requires custom initialization logic | ||
| /// beyond what a parameterless constructor can provide. | ||
| /// Only one method in a type may be marked with this attribute. | ||
| /// </remarks> | ||
| /// <example> | ||
| /// <code> | ||
| /// [Singleton(Mode = SingletonMode.Lazy)] | ||
| /// public partial class ConfigManager | ||
| /// { | ||
| /// private ConfigManager(string path) { } | ||
| /// | ||
| /// [SingletonFactory] | ||
| /// private static ConfigManager Create() => new ConfigManager("config.json"); | ||
| /// } | ||
| /// </code> | ||
| /// </example> | ||
| [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)] | ||
| public sealed class SingletonFactoryAttribute : Attribute | ||
| { | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.