From 195a17cef64ed968127e261d8560e5e1b4ab3ee2 Mon Sep 17 00:00:00 2001 From: jtheisen Date: Wed, 25 Jan 2023 21:40:32 +0100 Subject: [PATCH] Refactor setup code, struggling with ConcurrentList --- Moldinium.Tests/Moldinium/CombinedTests.cs | 2 +- .../Moldinium/MoldiniumTestsBase.cs | 2 +- Moldinium/Baking/NullableContextAttribute.cs | 2 +- Moldinium/Combined/Common.cs | 124 ++++++++++++++++-- Moldinium/Defaults/Defaults.cs | 26 ++-- Moldinium/Misc/ConcurrentList.cs | 19 ++- Moldinium/Tracking/Command.cs | 41 ------ Moldinium/Tracking/Trackables.cs | 6 +- SampleApp.AspNetMvc/Program.cs | 16 +-- SampleApp.BlazorWebAssembly/Program.cs | 14 +- SampleApp.Wpf/App.xaml.cs | 14 +- 11 files changed, 161 insertions(+), 105 deletions(-) delete mode 100644 Moldinium/Tracking/Command.cs diff --git a/Moldinium.Tests/Moldinium/CombinedTests.cs b/Moldinium.Tests/Moldinium/CombinedTests.cs index 05e2eb3..9445ad3 100644 --- a/Moldinium.Tests/Moldinium/CombinedTests.cs +++ b/Moldinium.Tests/Moldinium/CombinedTests.cs @@ -24,7 +24,7 @@ public void InterfaceTypeWithParameterizedFactoryInstanceTest() IDependencyProvider GetProvider() => DependencyProvider.Create( new DefaultDependencyProviderConfiguration( - Baking: DefaultDependencyProviderBakingMode.Tracking + Mode: MoldiniumDefaultMode.Tracking ) ); diff --git a/Moldinium.Tests/Moldinium/MoldiniumTestsBase.cs b/Moldinium.Tests/Moldinium/MoldiniumTestsBase.cs index cf33a12..b377ea6 100644 --- a/Moldinium.Tests/Moldinium/MoldiniumTestsBase.cs +++ b/Moldinium.Tests/Moldinium/MoldiniumTestsBase.cs @@ -2,7 +2,7 @@ public class MoldiniumTestsBase { - protected T CreateTestModel(DefaultDependencyProviderBakingMode mode = DefaultDependencyProviderBakingMode.TrackingAndNotifyPropertyChanged, Boolean logReport = false) + protected T CreateTestModel(MoldiniumDefaultMode mode = MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged, Boolean logReport = false) { var config = new DefaultDependencyProviderConfiguration(mode) { IsMoldiniumType = t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I") }; diff --git a/Moldinium/Baking/NullableContextAttribute.cs b/Moldinium/Baking/NullableContextAttribute.cs index b5a3335..fdd52ad 100644 --- a/Moldinium/Baking/NullableContextAttribute.cs +++ b/Moldinium/Baking/NullableContextAttribute.cs @@ -101,7 +101,7 @@ public static void SetNullableAttributes(Action target, arg = moreArgs.Select(a => (Byte)a.Value!).ToArray(); } - var builder = new CustomAttributeBuilder(attribute.Constructor, new Object[] { arg } ); + var builder = new CustomAttributeBuilder(attribute.Constructor, new Object[] { arg! } ); target(builder); } diff --git a/Moldinium/Combined/Common.cs b/Moldinium/Combined/Common.cs index 9ab56fb..ebe4dd4 100644 --- a/Moldinium/Combined/Common.cs +++ b/Moldinium/Combined/Common.cs @@ -2,11 +2,12 @@ using Moldinium.Injection; using System; using System.Collections.Generic; +using System.Reflection.Emit; namespace Moldinium; [Flags] -public enum DefaultDependencyProviderBakingMode +public enum MoldiniumDefaultMode { Basic = 0, @@ -53,12 +54,42 @@ public IDependencyProvider Build() } } +public class MoldiniumConfigurationBuilder +{ + MoldiniumDefaultMode mode; + Type? defaultIListAndICollationType; + Predicate? isModliniumType; + + public MoldiniumConfigurationBuilder SetMode(MoldiniumDefaultMode mode) + => Modify(() => this.mode = mode); + + public MoldiniumConfigurationBuilder SetIListAndICollectionImplementationType(Type defaultIListAndICollationType) + => Modify(() => this.defaultIListAndICollationType = defaultIListAndICollationType); + + public MoldiniumConfigurationBuilder IdentifyMoldiniumTypes(Predicate isModliniumType) + => Modify(() => this.isModliniumType = isModliniumType); + + MoldiniumConfigurationBuilder Modify(Action action) + { + action(); + return this; + } + + public DefaultDependencyProviderConfiguration Build(IServiceProvider services) + => new DefaultDependencyProviderConfiguration(mode, + DefaultIListAndICollationType: defaultIListAndICollationType, + IsMoldiniumType: isModliniumType, + Services: services + ); +} + public record DefaultDependencyProviderConfiguration( - DefaultDependencyProviderBakingMode? Baking = DefaultDependencyProviderBakingMode.Basic, + MoldiniumDefaultMode? Mode = MoldiniumDefaultMode.Basic, Boolean BakeAbstract = false, Boolean InitializeInits = true, Boolean EnableFactories = true, Boolean AcceptDefaultConstructibles = false, + Type? DefaultIListAndICollationType = null, Action? Build = null, IServiceProvider? Services = null, Predicate? IsMoldiniumType = null @@ -89,13 +120,31 @@ public static IDependencyProvider Create(DefaultDependencyProviderConfiguration providers.Add(new AcceptingDefaultConstructiblesDependencyProvider()); } - if (config.Baking is DefaultDependencyProviderBakingMode bakingMode) + if (config.Mode is MoldiniumDefaultMode mode) { - var componentGenerators = CreateBakeryComponentGenerators(bakingMode); + var componentGenerators = CreateBakeryComponentGenerators(mode); - var bakeryconfiguration = new BakeryConfiguration(componentGenerators, Defaults.GetDefaultDefaultProvider(), config.BakeAbstract); + String moduleNameSuffix = ""; - providers.Add(new BakeryDependencyProvider(new Bakery("TestBakery", bakeryconfiguration), config.IsMoldiniumType)); + Type? genericCollectionType; + + if (config.DefaultIListAndICollationType is not null) + { + moduleNameSuffix = $".{config.DefaultIListAndICollationType.Name}"; + genericCollectionType = config.DefaultIListAndICollationType; + } + else + { + genericCollectionType = GetDefaultGenericCollectionType(mode); + } + + var defaultProvider = Defaults.GetDefaultDefaultProvider(genericCollectionType); + + var bakeryconfiguration = new BakeryConfiguration(componentGenerators, defaultProvider, config.BakeAbstract); + + var bakery = new Bakery($"MoldiniumTypes.{mode}{moduleNameSuffix}", bakeryconfiguration); + + providers.Add(new BakeryDependencyProvider(bakery, config.IsMoldiniumType)); } if (config.EnableFactories) @@ -113,15 +162,23 @@ public static IDependencyProvider Create(DefaultDependencyProviderConfiguration return new CombinedDependencyProvider(providers.ToArray()); } - static ComponentGenerators CreateBakeryComponentGenerators(DefaultDependencyProviderBakingMode mode) => mode switch + static Type GetDefaultGenericCollectionType(MoldiniumDefaultMode mode) => mode switch { - DefaultDependencyProviderBakingMode.Basic => ComponentGenerators.Create( + MoldiniumDefaultMode.Basic => typeof(List<>), + MoldiniumDefaultMode.NotifyPropertyChanged => typeof(LiveList<>), // just because ObservableCollection doesn't implement IList<> + MoldiniumDefaultMode.Tracking or MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged => typeof(TrackableList<>), + _ => throw new NotImplementedException() + }; + + static ComponentGenerators CreateBakeryComponentGenerators(MoldiniumDefaultMode mode) => mode switch + { + MoldiniumDefaultMode.Basic => ComponentGenerators.Create( typeof(SimplePropertyImplementation<>), typeof(GenericEventImplementation<>)), - DefaultDependencyProviderBakingMode.Tracking => ComponentGenerators.Create( + MoldiniumDefaultMode.Tracking => ComponentGenerators.Create( typeof(TrackedPropertyImplementation<>), typeof(TrackedComputedPropertyImplementation<,>)), - DefaultDependencyProviderBakingMode.TrackingAndNotifyPropertyChanged => ComponentGenerators.Create( + MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged => ComponentGenerators.Create( typeof(TrackedNotifyingPropertyImplementation<,>), typeof(TrackedNotifyingComputedPropertyImplementation<,,>)), _ => throw new NotImplementedException() @@ -134,17 +191,58 @@ public static partial class Extensions /// Checks whether starts with and that the /// first letter following that prefix is in upper case. /// Eg. name.StartsWithFollowedByCapital("I") detects interfaces - /// or styleguide violations + /// or styleguide violations such as /// public static Boolean StartsWithFollowedByCapital(this String name, String prefix) => name.Length > prefix.Length && name.StartsWith(prefix) && Char.IsUpper(name[prefix.Length]); + static IDependencyProvider GetProvider(this Action build, IServiceProvider services) + { + var builder = new MoldiniumConfigurationBuilder(); + build(builder); + var configuration = builder.Build(services); + return DependencyProvider.Create(configuration); + } - public static IServiceCollection AddMoldiniumRoot(this IServiceCollection services, DefaultDependencyProviderConfiguration configuration) + /// + /// Adds the type as a resolvable type. It also adds as a singleton + /// that can be resolved for early validation and to get the resolution report. + /// + public static IServiceCollection AddSingletonMoldiniumRoot(this IServiceCollection services, Action build) where T : class => services - .AddSingleton>(sp => new Scope(DependencyProvider.Create(configuration), DependencyRuntimeMaturity.InitializedInstance)) + .AddSingleton>(sp => new Scope(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance)) .AddSingleton(sp => (T)sp.GetRequiredService>().CreateRuntimeScope().Root) ; + + /// + /// Adds the type as a resolvable type. It also adds as a singleton + /// that can be resolved for early validation and to get the resolution report. + /// + public static IServiceCollection AddScopedMoldiniumRoot(this IServiceCollection services, Action build) + where T : class => services + + .AddSingleton>(sp => new Scope(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance)) + .AddScoped(sp => (T)sp.GetRequiredService>().CreateRuntimeScope().Root) + + ; + + /// + /// Adds the type as a resolvable type. It also adds as a singleton + /// that can be resolved for early validation and to get the resolution report. + /// + public static IServiceCollection AddTransientMoldiniumRoot(this IServiceCollection services, Action build) + where T : class => services + + .AddSingleton>(sp => new Scope(build.GetProvider(sp), DependencyRuntimeMaturity.InitializedInstance)) + .AddTransient(sp => (T)sp.GetRequiredService>().CreateRuntimeScope().Root) + + ; + + /// + /// Validates dependencies early and gives a dependency report + /// + public static String ValidateMoldiniumRoot(this IServiceProvider services) + => services.GetRequiredService>().CreateDependencyReport(); } diff --git a/Moldinium/Defaults/Defaults.cs b/Moldinium/Defaults/Defaults.cs index 0170004..ab58a4f 100644 --- a/Moldinium/Defaults/Defaults.cs +++ b/Moldinium/Defaults/Defaults.cs @@ -46,11 +46,11 @@ public interface IDefaultProvider public class DefaultDefaultProvider : IDefaultProvider { - private readonly bool useTrackableList; + private readonly Type? genericCollectionType; - public DefaultDefaultProvider(Boolean useTrackableList = true) + public DefaultDefaultProvider(Type? genericCollectionType) { - this.useTrackableList = useTrackableList; + this.genericCollectionType = genericCollectionType; } public Type? GetDefaultType(Type type) @@ -73,11 +73,11 @@ public DefaultDefaultProvider(Boolean useTrackableList = true) if (interfaces.DoesTypeImplement(typeof(ICollection<>))) { - if (useTrackableList) + if (genericCollectionType is not null) { - var trackableListDefaultType = GetDefaultImplementationTypeForTrackableList(type); + var defaultImplementationType = GetDefaultImplementationTypeForCollectionType(genericCollectionType, type); - return trackableListDefaultType; + return defaultImplementationType; } else if (traits.IsDefaultConstructible) { @@ -88,23 +88,23 @@ public DefaultDefaultProvider(Boolean useTrackableList = true) return null; } - Type GetDefaultImplementationTypeForTrackableList(Type type) + Type GetDefaultImplementationTypeForCollectionType(Type genericCollectionType, Type valueType) { - var arguments = type.GetGenericArguments(); + var arguments = valueType.GetGenericArguments(); if (arguments.Length <= 1) { var typeArgument = arguments.Length == 1 ? arguments[0] : typeof(Object); - var implementationType = typeof(TrackableList<>).MakeGenericType(typeArgument); + var implementationType = genericCollectionType.MakeGenericType(typeArgument); - var defaultImplementationType = typeof(DefaultDefaultConstructible<,>).MakeGenericType(type, implementationType); + var defaultImplementationType = typeof(DefaultDefaultConstructible<,>).MakeGenericType(valueType, implementationType); return defaultImplementationType; } else { - throw new Exception($"Expected generic collection type {type} to have at most one type paramter"); + throw new Exception($"Expected generic collection type {valueType} to have at most one type paramter"); } } } @@ -114,6 +114,6 @@ public static class Defaults public static Type CreateConcreteDefaultImplementationType(Type type, Type valueType) => type.IsGenericTypeDefinition ? type.MakeGenericType(valueType) : type; - public static IDefaultProvider GetDefaultDefaultProvider() - => new DefaultDefaultProvider(); + public static IDefaultProvider GetDefaultDefaultProvider(Type? genericCollectionType = null) + => new DefaultDefaultProvider(genericCollectionType); } diff --git a/Moldinium/Misc/ConcurrentList.cs b/Moldinium/Misc/ConcurrentList.cs index ff09d05..5a7f69f 100644 --- a/Moldinium/Misc/ConcurrentList.cs +++ b/Moldinium/Misc/ConcurrentList.cs @@ -1,5 +1,6 @@ using System.Collections; using System.Collections.Immutable; +using System.Threading; namespace Moldinium.Misc; @@ -7,29 +8,39 @@ struct LockHelper : IDisposable { SpinLock spinLock; - Int32 checkCounter; + Int32 depth; public LockHelper Lock() { + if (++depth > 1) + { + if (!spinLock.IsHeldByCurrentThread) throw new Exception($"Expected to hold the lock"); + + return this; + } + Boolean lockTaken = false; spinLock.Enter(ref lockTaken); if (!lockTaken) throw new Exception("Can't take lock"); - if (++checkCounter != 1) throw new Exception("Unexpected log check counter"); - return this; } public void Dispose() { - if (--checkCounter != 0) throw new Exception("Unexpected log check counter"); + if (--depth > 0) return; spinLock.Exit(); } } +ref struct LockHelperRef +{ + public ref int lockHelper; +} + /// /// A simple but likely quite inefficient implementation of a concurrent list that /// is meant only for demonstration purposes. diff --git a/Moldinium/Tracking/Command.cs b/Moldinium/Tracking/Command.cs deleted file mode 100644 index 8ea5412..0000000 --- a/Moldinium/Tracking/Command.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using System.Windows.Input; - -namespace Moldinium; - -public delegate Boolean DCommandImplementation(Boolean simulate); - -public class Command : ICommand, IDisposable -{ - DCommandImplementation action; - - SerialTrackSubscription? subscriptions = null; - - public Command(DCommandImplementation action) - { - this.action = action; - } - - public Command(Action action) - { - this.action = simulate => { if (simulate) return true; action(); return true; }; - } - - public event EventHandler? CanExecuteChanged; - - public bool CanExecute(Object? parameter) - { - return Repository.Instance.EvaluateAndSubscribe("command", ref subscriptions, Evaluate, NotifyChange); - } - - public void Execute(Object? parameter) - { - action(false); - } - - public void Dispose() => subscriptions?.Dispose(); - - void NotifyChange() => CanExecuteChanged?.Invoke(this, EventArgs.Empty); - - Boolean Evaluate() => action(true); -} diff --git a/Moldinium/Tracking/Trackables.cs b/Moldinium/Tracking/Trackables.cs index df157b9..a1146fb 100644 --- a/Moldinium/Tracking/Trackables.cs +++ b/Moldinium/Tracking/Trackables.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; - -namespace Moldinium; +namespace Moldinium; interface ITrackSubscription : IDisposable { diff --git a/SampleApp.AspNetMvc/Program.cs b/SampleApp.AspNetMvc/Program.cs index fae0dd1..579806c 100644 --- a/SampleApp.AspNetMvc/Program.cs +++ b/SampleApp.AspNetMvc/Program.cs @@ -1,7 +1,6 @@ using Moldinium; -using Moldinium.Injection; +using Moldinium.Misc; using SampleApp; -using System.Diagnostics; var builder = WebApplication.CreateBuilder(args); @@ -9,18 +8,15 @@ services.AddControllersWithViews().AddRazorRuntimeCompilation(); -var configuration = new DefaultDependencyProviderConfiguration( - Baking: DefaultDependencyProviderBakingMode.Basic, - IsMoldiniumType: t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I") +services.AddSingletonMoldiniumRoot(c => c + .SetMode(MoldiniumDefaultMode.Basic) + .SetIListAndICollectionImplementationType(typeof(ConcurrentList<>)) + .IdentifyMoldiniumTypes(t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I")) ); -services.AddMoldiniumRoot(configuration); - var app = builder.Build(); -var scope = app.Services.GetRequiredService>(); - -Debug.WriteLine(scope.CreateDependencyReport()); +app.Services.ValidateMoldiniumRoot(); // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) diff --git a/SampleApp.BlazorWebAssembly/Program.cs b/SampleApp.BlazorWebAssembly/Program.cs index 7ee0ade..5746c97 100644 --- a/SampleApp.BlazorWebAssembly/Program.cs +++ b/SampleApp.BlazorWebAssembly/Program.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using Moldinium; -using Moldinium.Injection; using SampleApp; using SampleApp.BlazorWebAssembly; @@ -13,14 +12,13 @@ services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -var configuration = new DefaultDependencyProviderConfiguration( - Baking: DefaultDependencyProviderBakingMode.Tracking, - BakeAbstract: false, - IsMoldiniumType: t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I") +services.AddSingletonMoldiniumRoot(c => c + .SetMode(MoldiniumDefaultMode.Tracking) + .IdentifyMoldiniumTypes(t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I")) ); -var provider = DependencyProvider.Create(configuration); +var host = builder.Build(); -services.AddScoped(sp => provider.CreateInstance()); +host.Services.ValidateMoldiniumRoot(); -await builder.Build().RunAsync(); +await host.RunAsync(); diff --git a/SampleApp.Wpf/App.xaml.cs b/SampleApp.Wpf/App.xaml.cs index e304ce9..dcda94b 100644 --- a/SampleApp.Wpf/App.xaml.cs +++ b/SampleApp.Wpf/App.xaml.cs @@ -20,19 +20,17 @@ protected override void OnStartup(StartupEventArgs e) base.OnStartup(e); var services = new ServiceCollection(); + services.AddSingleton(this); - var serviceProvider = services.BuildServiceProvider(); - var configuration = new DefaultDependencyProviderConfiguration( - Baking: DefaultDependencyProviderBakingMode.TrackingAndNotifyPropertyChanged, - BakeAbstract: false, - Services: serviceProvider, - IsMoldiniumType: t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I") + services.AddSingletonMoldiniumRoot(c => c + .SetMode(MoldiniumDefaultMode.TrackingAndNotifyPropertyChanged) + .IdentifyMoldiniumTypes(t => t.IsInterface && !t.Name.StartsWithFollowedByCapital("I")) ); - var provider = DependencyProvider.Create(configuration); + var serviceProvider = services.BuildServiceProvider(); - JobList = provider.CreateInstance(); + JobList = serviceProvider.GetRequiredService(); } void ILogger.Log(string message)