From 5e9d7cd805a2e4e6a83f297c6707baa6c92bc4f9 Mon Sep 17 00:00:00 2001 From: "Frank R. Haugen" Date: Sat, 24 Feb 2024 23:59:16 +0100 Subject: [PATCH] Add advanced channel configuration options and update docs Expanded the channel configuration options in the dependency injection library, allowing more control over channel settings. Bounded and unbounded channels can now be created and configured with various parameters. Updated documentation in README.md to include examples of advanced usage of the channels. Introduced a new ChannelSettings class for handling these configurations. Also, refactored some parts of service collection extension methods for better code organization and efficiency. --- .../ChannelFactory.cs | 31 ++++- .../ChannelType.cs | 7 ++ .../IChannelFactory.cs | 6 +- .../ServiceCollectionExtensions.cs | 109 +++++++++++++++--- README.md | 20 ++++ 5 files changed, 148 insertions(+), 25 deletions(-) create mode 100644 Frank.Channels.DependencyInjection/ChannelType.cs diff --git a/Frank.Channels.DependencyInjection/ChannelFactory.cs b/Frank.Channels.DependencyInjection/ChannelFactory.cs index 2508ad6..9fe3477 100644 --- a/Frank.Channels.DependencyInjection/ChannelFactory.cs +++ b/Frank.Channels.DependencyInjection/ChannelFactory.cs @@ -3,16 +3,37 @@ namespace Frank.Channels.DependencyInjection; -public class ChannelFactory : IChannelFactory +internal class ChannelFactory : IChannelFactory { private readonly ConcurrentDictionary _cache = new(); - public Channel CreateChannel() where T : class => _cache.GetOrAdd(typeof(T).Name, Value) as Channel ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found"); + /// + public Channel CreateUnboundedChannel(ChannelSettings? options = null) where T : class => _cache.GetOrAdd(typeof(T).Name, name => CreateUnbounded(name, options ?? new ChannelSettings())) as Channel ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found"); - private static Channel Value(string arg) where T : class => + /// + public Channel CreateBoundedChannel(ChannelSettings? options = null) where T : class => _cache.GetOrAdd(typeof(T).Name, name => CreateBounded(name, options ?? new ChannelSettings())) as Channel ?? throw new InvalidOperationException($"Channel<{typeof(T).Name}> not found"); + + private static Channel CreateBounded(string name, ChannelSettings options) where T : class => + Channel.CreateBounded(new BoundedChannelOptions(options.BoundedCapacity) + { + FullMode = options.BoundedFullMode + }); + + private static Channel CreateUnbounded(string arg, ChannelSettings options) where T : class => Channel.CreateUnbounded(new UnboundedChannelOptions() { - SingleReader = true, - SingleWriter = true + SingleReader = options.SingleReader, + SingleWriter = options.SingleWriter }); } + +public class ChannelSettings +{ + public bool SingleReader { get; set; } = true; + + public bool SingleWriter { get; set; } = true; + + public int BoundedCapacity { get; set; } = 100; + + public BoundedChannelFullMode BoundedFullMode { get; set; } = BoundedChannelFullMode.Wait; +} \ No newline at end of file diff --git a/Frank.Channels.DependencyInjection/ChannelType.cs b/Frank.Channels.DependencyInjection/ChannelType.cs new file mode 100644 index 0000000..63bcd76 --- /dev/null +++ b/Frank.Channels.DependencyInjection/ChannelType.cs @@ -0,0 +1,7 @@ +namespace Frank.Channels.DependencyInjection; + +public enum ChannelType +{ + Unbounded, + Bounded +} \ No newline at end of file diff --git a/Frank.Channels.DependencyInjection/IChannelFactory.cs b/Frank.Channels.DependencyInjection/IChannelFactory.cs index 9d5a450..c8f933f 100644 --- a/Frank.Channels.DependencyInjection/IChannelFactory.cs +++ b/Frank.Channels.DependencyInjection/IChannelFactory.cs @@ -2,7 +2,9 @@ namespace Frank.Channels.DependencyInjection; -public interface IChannelFactory +internal interface IChannelFactory { - Channel CreateChannel() where T : class; + Channel CreateUnboundedChannel(ChannelSettings? options = null) where T : class; + + Channel CreateBoundedChannel(ChannelSettings? options = null) where T : class; } \ No newline at end of file diff --git a/Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs b/Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs index 02bde51..e6a9576 100644 --- a/Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs +++ b/Frank.Channels.DependencyInjection/ServiceCollectionExtensions.cs @@ -5,20 +5,8 @@ namespace Frank.Channels.DependencyInjection; public static class ServiceCollectionExtensions { - internal static bool Contains(this IServiceCollection services) - { - return services.Any(descriptor => descriptor.ServiceType == typeof(TService)); - } - - internal static IServiceCollection AddSingletonIfNotExists(this IServiceCollection services) where TService : class where TImplementation : class, TService - { - if (!services.Contains()) - services.AddSingleton(); - return services; - } - /// - /// Adds a channel of type to the service collection. + /// Adds an unbounded channel of type to the service collection, with default settings. /// /// /// The channel is added as a singleton with its reader and writer as singletons, and injected as follows: @@ -31,12 +19,97 @@ internal static IServiceCollection AddSingletonIfNotExists /// /// - public static IServiceCollection AddChannel(this IServiceCollection services) where T : class + public static IServiceCollection AddChannel(this IServiceCollection services) where T : class => + services.AddChannel(ChannelType.Unbounded, new ChannelSettings()); + + /// + /// Adds a channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The type of channel to add. + /// The same instance of the IServiceCollection after the channel has been added. + /// + /// This method adds a channel of type to the IServiceCollection. + /// It allows specifying the channel type (unbounded or bounded) and optional settings for the channel. + /// After the channel is added, it will be available for injection as a singleton instance of Channel{T}. + /// The respective ChannelReader{T} and ChannelWriter{T} instances are also added as singletons. + /// + public static IServiceCollection AddChannel(this IServiceCollection services, ChannelType channelType) where T : class => + services.AddChannel(channelType, new ChannelSettings()); + + /// + /// Adds an unbounded channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The same instance of the IServiceCollection after the channel has been added. + public static IServiceCollection AddUnboundedChannel(this IServiceCollection services) where T : class => + services.AddChannel(ChannelType.Unbounded, new ChannelSettings()); + + /// + /// Adds an unbounded channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The same instance of the IServiceCollection after the channel has been added. + public static IServiceCollection AddUnboundedChannel(this IServiceCollection services, ChannelSettings settings) where T : class => + services.AddChannel(ChannelType.Unbounded, settings); + + /// + /// Adds a bounded channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The same instance of the IServiceCollection after the channel has been added. + public static IServiceCollection AddBoundedChannel(this IServiceCollection services) where T : class => + services.AddChannel(ChannelType.Bounded, new ChannelSettings()); + + /// + /// Adds a bounded channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The settings for the channel. + /// The same instance of the IServiceCollection after the channel has been added. + public static IServiceCollection AddBoundedChannel(this IServiceCollection services, ChannelSettings settings) where T : class => + services.AddChannel(ChannelType.Bounded, settings); + + /// + /// Adds a channel of type to the IServiceCollection. + /// + /// The type of the channel. + /// The IServiceCollection to add the channel to. + /// The type of channel to add. + /// The settings for the channel. + /// The same instance of the IServiceCollection after the channel has been added. + public static IServiceCollection AddChannel(this IServiceCollection services, ChannelType channelType, ChannelSettings settings) where T : class => + services + .ThrowIfContains>() + .AddSingletonIfNotExists() + .AddSingleton>(provider => channelType switch + { + ChannelType.Unbounded => provider.GetRequiredService().CreateUnboundedChannel(settings), + ChannelType.Bounded => provider.GetRequiredService().CreateBoundedChannel(settings), + _ => throw new ArgumentOutOfRangeException(nameof(channelType), channelType, null) + }) + .AddSingleton>(provider => provider.GetRequiredService>().Reader) + .AddSingleton>(provider => provider.GetRequiredService>().Writer); + + private static IServiceCollection AddSingletonIfNotExists(this IServiceCollection services) where TService : class where TImplementation : class, TService { - services.AddSingletonIfNotExists(); - services.AddSingleton>(provider => provider.GetRequiredService().CreateChannel()); - services.AddSingleton>(provider => provider.GetRequiredService>().Reader); - services.AddSingleton>(provider => provider.GetRequiredService>().Writer); + if (!services.Contains()) + services.AddSingleton(); return services; } + + private static IServiceCollection ThrowIfContains(this IServiceCollection services) where TService : class + { + if (services.Contains()) + throw new InvalidOperationException($"Service of type {typeof(TService).Name} already exists in the service collection."); + return services; + } + + private static bool Contains(this IServiceCollection services) + => services.Any(descriptor => descriptor.ServiceType == typeof(TService)); } \ No newline at end of file diff --git a/README.md b/README.md index 89c8888..345f6aa 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,26 @@ using Frank.Channels.DependencyInjection; // Register the channel of a type as a dependency: services.AddChannel(); +// Use the channel as a dependency in various ways: +var channel = provider.GetRequiredService>(); +var channelWriter = provider.GetRequiredService>(); +var channelReader = provider.GetRequiredService>(); +``` + +## Advanced usage + +```csharp +using Frank.Channels.DependencyInjection; + +// Register the channel of a type as a dependency with a custom configuration: +services.AddChannel(options => +{ + options.BoundedCapacity = 100; + options.FullMode = BoundedChannelFullMode.Wait; + options.SingleReader = true; + options.SingleWriter = true; +}); + // Use the channel as a dependency in various ways: var channel = provider.GetRequiredService>(); var channelWriter = provider.GetRequiredService>();