diff --git a/.editorconfig b/.editorconfig index 1ffed87a..da3c3836 100644 --- a/.editorconfig +++ b/.editorconfig @@ -109,12 +109,13 @@ dotnet_naming_symbols.static_fields.required_modifiers = static dotnet_naming_style.static_field_style.capitalization = camel_case dotnet_naming_style.static_field_style.required_prefix = s_ -# Instance fields are camelCase and start with _ -dotnet_naming_rule.instance_fields_should_be_camel_case.severity = suggestion -dotnet_naming_rule.instance_fields_should_be_camel_case.symbols = instance_fields -dotnet_naming_rule.instance_fields_should_be_camel_case.style = instance_field_style +# Non-public instance fields are camelCase and start with _ +dotnet_naming_rule.non_public_instance_fields_should_be_camel_case.severity = suggestion +dotnet_naming_rule.non_public_instance_fields_should_be_camel_case.symbols = non_public_instance_fields +dotnet_naming_rule.non_public_instance_fields_should_be_camel_case.style = instance_field_style -dotnet_naming_symbols.instance_fields.applicable_kinds = field +dotnet_naming_symbols.non_public_instance_fields.applicable_kinds = field +dotnet_naming_symbols.non_public_instance_fields.applicable_accessibilities = protected, internal, protected_internal, private_protected, private dotnet_naming_style.instance_field_style.capitalization = camel_case dotnet_naming_style.instance_field_style.required_prefix = _ @@ -226,10 +227,18 @@ csharp_preserve_single_line_statements = true # Custom bits ########################################## -# Always have accessibility keyword +# IDE0007: Use var instead of explicit type +dotnet_diagnostic.IDE0007.severity = warning +csharp_style_var_for_built_in_types = true:warning +csharp_style_var_when_type_is_apparent = true:warning +csharp_style_var_elsewhere = true:warning + +# IDE0040: Add accessibility modifiers +dotnet_diagnostic.IDE0040.severity = warning dotnet_style_require_accessibility_modifiers = for_non_interface_members:warning -# Use file scoped namespaces +# IDE0161: Use file-scoped namespace +dotnet_diagnostic.IDE0161.severity = warning csharp_style_namespace_declarations = file_scoped:warning # CA1019: Define accessors for attribute arguments @@ -237,6 +246,7 @@ dotnet_diagnostic.CA1019.severity = none # IDE0305: Simplify collection initialization dotnet_diagnostic.IDE0305.severity = none +dotnet_style_prefer_collection_expression = false # Verify settings [*.{received,verified}.{cs,txt}] diff --git a/AutoCtor.slnx b/AutoCtor.slnx index 27fdb8c1..fc96fc2f 100644 --- a/AutoCtor.slnx +++ b/AutoCtor.slnx @@ -1,5 +1,6 @@ + @@ -30,7 +31,9 @@ - + + + diff --git a/readme.md b/readme.md index 06691225..7e8b4f65 100644 --- a/readme.md +++ b/readme.md @@ -50,6 +50,7 @@ public partial class AService * [NuGet packages](#nuget-packages) * [Code Fixes (new in 3.0)](#code-fixes-new-in-30) + * [Service Provider Source Generator (new in 4.0)](#service-provider-source-generator-new-in-40) * [Examples](#examples) * [Basic](#basic) * [Inherited](#inherited) @@ -60,11 +61,15 @@ public partial class AService * [Optional Parameters](#optional-parameters) * [Argument Guards](#argument-guards) * [Keyed Services](#keyed-services) + * [Service Provider](#service-provider) + * [Scanning](#scanning) + * [Keyed Services](#keyed-services-1) + * [Factories](#factories) + * [Falling back to another provider](#falling-back-to-another-provider) + * [Modules](#modules) * [Other](#other) * [Embedding The Attributes](#embedding-the-attributes) - * [Keeping Attributes In Code](#keeping-attributes-in-code) - * [Star History](#star-history) - * [Stats](#stats) + * [Keeping Attributes In Code](#keeping-attributes-in-code) ## NuGet packages @@ -76,6 +81,10 @@ Starting in version 3.0, a new diagnostic has been introduced: `ACTR007 Use [Aut In 3.1 another code fix was added: `Add [AutoConstruct] to type`. This will add `[AutoConstruct]` to a type with no constructor and relevant fields. This is useful when working on a new type. +## Service Provider Source Generator (new in 4.0) + +In version 4.0 you can now generate your own Service Provider that will work with the constructors generated by AutoCtor. See [Service Provider](#service-provider) + ## Examples ### Basic @@ -107,17 +116,14 @@ public partial class Basic partial class Basic { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Basic(global::IService service) { this._service = service; } } ``` -snippet source | anchor +snippet source | anchor @@ -160,10 +166,7 @@ public partial class Inherited : BaseClass partial class Inherited { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Inherited( global::IAnotherService anotherService, global::IService service @@ -173,7 +176,7 @@ partial class Inherited } } ``` -snippet source | anchor +snippet source | anchor @@ -212,10 +215,7 @@ public string RedirectedProperty => InitializerProperty; partial class Properties { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Properties( string getProperty, string protectedProperty, @@ -230,7 +230,7 @@ partial class Properties } } ``` -snippet source | anchor +snippet source | anchor @@ -271,10 +271,7 @@ public partial class PostConstruct partial class PostConstruct { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstruct(global::IService service) { this._service = service; @@ -282,7 +279,7 @@ partial class PostConstruct } } ``` -snippet source | anchor +snippet source | anchor @@ -322,10 +319,7 @@ public partial class PostConstructWithParameter partial class PostConstructWithParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithParameter( global::IService service, global::IInitializeService initialiseService @@ -336,7 +330,7 @@ partial class PostConstructWithParameter } } ``` -snippet source | anchor +snippet source | anchor @@ -378,10 +372,7 @@ public partial class PostConstructWithOutParameter partial class PostConstructWithOutParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithOutParameter( global::IOtherService otherService, global::IServiceProvider services @@ -392,7 +383,7 @@ partial class PostConstructWithOutParameter } } ``` -snippet source | anchor +snippet source | anchor @@ -428,17 +419,14 @@ public partial class PostConstructWithDefaultParameter partial class PostConstructWithDefaultParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithDefaultParameter(global::Service service = default) { Initialize(service); } } ``` -snippet source | anchor +snippet source | anchor @@ -490,17 +478,14 @@ public partial class Guarded partial class Guarded { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Guarded(global::IService service) { this._service = service ?? throw new global::System.ArgumentNullException("service"); } } ``` -snippet source | anchor +snippet source | anchor @@ -537,10 +522,7 @@ public partial class Keyed partial class Keyed { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Keyed( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService keyedService ) @@ -549,11 +531,1961 @@ partial class Keyed } } ``` -snippet source | anchor +snippet source | anchor +Back to Contents +## Service Provider + + + +```cs +[ServiceProvider] +[Singleton] +[Singleton] +public sealed partial class Container; + +[AutoConstruct] +public partial class Greeter : IGreeter +{ + private readonly IClock _clock; +} +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: Container.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class Container : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + private volatile global::IGreeter? _S1; + private global::IGreeter S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Greeter(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock Container.IResolver.Get() + => S0(); + global::IGreeter Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly Container _root; + public Scope(Container root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IGreeter S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock Container.IResolver.Get() + => S0(); + global::IGreeter Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +Add `[ServiceProvider]` to a blank partial class and register services with `[Singleton]`, `[Transient]` or `[Scoped]` attributes. + +The service provider is fully generated at compile time so there is no runtime reflection. + +Multiple registrations for the same service can be returned with `IEnumerable`. When getting just one the last registration wins. + + +### Scanning + +As well as registering services individually there is also an option to scan for services. Scanning is performed at compile time. + + + +```cs +[ServiceProvider] +[ScanTransient(typeof(IHandler<>))] +public sealed partial class OrderContainer; + +public interface IHandler; + +public class CreateOrderHandler : IHandler; +public class CancelOrderHandler : IHandler; +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: OrderContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class OrderContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + OrderContainer.IResolver>, + OrderContainer.IResolver>, + OrderContainer.IResolver>>, + OrderContainer.IResolver>>, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IHandler OrderContainer.IResolver>.Get() + => S0(); + global::IHandler OrderContainer.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.IServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService OrderContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + OrderContainer.IResolver>, + OrderContainer.IResolver>, + OrderContainer.IResolver>>, + OrderContainer.IResolver>>, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly OrderContainer _root; + public Scope(OrderContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IHandler OrderContainer.IResolver>.Get() + => S0(); + global::IHandler OrderContainer.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.IServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService OrderContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +The scan attributes `[ScanSingleton]`, `[ScanTransient]`, and `[ScanScoped]` have some options. + +- `As` determines what to register the service as: `ScanAs.Service`, the service being scanned for (default); `ScanAs.Self`, the type itself; `ScanAs.ImplementedInterfaces`, all the interfaces the type has. +- `FromAssembliesOf` can be used to scan other assemblies instead of the one the provider is in; add the provider too to keep it in the list of assemblies to scan. +- `TypeNameFilter` only includes types whose name matches, where `*` matches anything and `?` any one character, e.g. `"*Repository"`. The service can be left out to select by name alone. + +### Keyed Services + + + +```cs +[ServiceProvider] +[Singleton(Key = "live")] +[Singleton(Key = "sandbox")] +[Singleton] +public sealed partial class PaymentContainer; + +public interface IPaymentGateway; + +public class PaymentGateway : IPaymentGateway +{ + public PaymentGateway([AutoKeyedService("live")] IClient client) { } +} +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: PaymentContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PaymentContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + PaymentContainer.IResolver, + PaymentContainer.IResolver>, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClient? _S0; + private global::IClient S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::LiveClient()); + } + private volatile global::IClient? _S1; + private global::IClient S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SandboxClient()); + } + private volatile global::IPaymentGateway? _S2; + private global::IPaymentGateway S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::PaymentGateway(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IClient) && object.Equals(key, "live")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClient) && object.Equals(key, "sandbox")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "live")) + return probe ? this : (object)(new global::IClient[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sandbox")) + return probe ? this : (object)(new global::IClient[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IClient[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPaymentGateway PaymentContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PaymentContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S2() }; + global::System.IServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PaymentContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + PaymentContainer.IResolver, + PaymentContainer.IResolver>, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PaymentContainer _root; + public Scope(PaymentContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClient S0() => _root.S0(); + private global::IClient S1() => _root.S1(); + private global::IPaymentGateway S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IClient) && object.Equals(key, "live")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClient) && object.Equals(key, "sandbox")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "live")) + return probe ? this : (object)(new global::IClient[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sandbox")) + return probe ? this : (object)(new global::IClient[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IClient[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPaymentGateway PaymentContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PaymentContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S2() }; + global::System.IServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PaymentContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +### Factories + + + +```cs +[ServiceProvider] +[Singleton(Factory = nameof(CreateClock))] +[Singleton(Factory = nameof(_banner))] +[Scoped(Factory = nameof(CreateAuditLog))] +public sealed partial class HostContainer +{ + private readonly IBanner _banner = new Banner("AutoCtor"); + + private IClock CreateClock() => new SystemClock("UTC"); + + private IAuditLog CreateAuditLog(System.IServiceProvider services) => new AuditLog(services); +} +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: HostContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class HostContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(this.CreateClock())); + } + private volatile global::IBanner? _S1; + private global::IBanner S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(this._banner)); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock HostContainer.IResolver.Get() + => S0(); + global::IBanner HostContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IBanner[] { S1() }; + global::System.IServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HostContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly HostContainer _root; + public Scope(HostContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IBanner S1() => _root.S1(); + private volatile global::IAuditLog? _S2; + private global::IAuditLog S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= Track(_root.CreateAuditLog(this))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IAuditLog)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAuditLog[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock HostContainer.IResolver.Get() + => S0(); + global::IBanner HostContainer.IResolver.Get() + => S1(); + global::IAuditLog HostContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IBanner[] { S1() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IAuditLog[] { S2() }; + global::System.IServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HostContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +### Falling back to another provider + +`Fallback` on `[ServiceProvider]` names a member to use when there is no registration for that service. + + + +```cs +[ServiceProvider(Fallback = nameof(_host))] +[Singleton] +public sealed partial class FallbackContainer +{ + private readonly System.IServiceProvider _host; + + public FallbackContainer(System.IServiceProvider host) => _host = host; +} +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: FallbackContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class FallbackContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + FallbackContainer.IResolver, + FallbackContainer.IResolver>, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IGreeter? _S0; + private global::IGreeter S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Greeter(Required(GetKeyedService(typeof(global::ILogSink), null)))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => this._host; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IGreeter FallbackContainer.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable FallbackContainer.IResolver>.Get() + => new global::IGreeter[] { S0() }; + global::System.IServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService FallbackContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + FallbackContainer.IResolver, + FallbackContainer.IResolver>, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly FallbackContainer _root; + public Scope(FallbackContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IGreeter S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? _fallback; + private global::System.IServiceProvider? Fallback { get { lock (_lock) return _fallback ??= _root._host is global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory f ? Track(f.CreateScope()).ServiceProvider : _root._host; } } + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IGreeter FallbackContainer.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable FallbackContainer.IResolver>.Get() + => new global::IGreeter[] { S0() }; + global::System.IServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService FallbackContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +### Modules + +`[Import]` (or `[Import(typeof(TModule))]`) adds all the registrations from another type. Modules just act as markers so no code is generated for them. + + + +```cs +[Singleton] +[Scoped(Factory = nameof(CreateLedger))] +public class PaymentsModule +{ + public static ILedger CreateLedger(IPaymentGateway gateway) => new Ledger(gateway); +} + +[ServiceProvider] +[Import] +[Singleton] +public sealed partial class TestContainer; +``` +snippet source | anchor + + +
What gets generated + + + +```cs +//HintName: TestContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TestContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + TestContainer.IResolver, + TestContainer.IResolver>, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPaymentGateway? _S0; + private global::IPaymentGateway S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::StripeGateway()); + } + private volatile global::IPaymentGateway? _S2; + private global::IPaymentGateway S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::FakeGateway()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S0(), S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPaymentGateway TestContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S0(), S2() }; + global::System.IServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService TestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver>, + TestContainer.IResolver>, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TestContainer _root; + public Scope(TestContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPaymentGateway S0() => _root.S0(); + private volatile global::ILedger? _S1; + private global::ILedger S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(global::PaymentsModule.CreateLedger(S2()))); + } + private global::IPaymentGateway S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S0(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPaymentGateway TestContainer.IResolver.Get() + => S2(); + global::ILedger TestContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S0(), S2() }; + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::ILedger[] { S1() }; + global::System.IServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService TestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} +``` +snippet source | anchor + + + + +A module can live in another assembly, but the registration attributes are `[Conditional]`, so that assembly has to define `AUTOCTOR_USAGES` (see [Keeping Attributes In Code](#keeping-attributes-in-code)) for them to be there to read. + Back to Contents ## Other @@ -583,6 +2515,8 @@ Your project file should look like this: ``` +The generic forms of `[Singleton]`, `[Transient]` and `[Scoped]` are a C# 11 feature, so they are not embedded by default. If your project targets C# 11 or later and you want them, also define `AUTOCTOR_EMBED_GENERIC_ATTRIBUTES`. +
What gets generated @@ -598,7 +2532,6 @@ Your project file should look like this: #if AUTOCTOR_EMBED_ATTRIBUTES namespace AutoCtor { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] internal enum GuardSetting { @@ -606,47 +2539,124 @@ namespace AutoCtor Disabled, Enabled } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] internal sealed class AutoConstructAttribute : global::System.Attribute { - public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) - { - } + public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) { } } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class AutoPostConstructAttribute : global::System.Attribute { } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] internal sealed class AutoConstructIgnoreAttribute : global::System.Attribute { } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property | global::System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] internal sealed class AutoKeyedServiceAttribute : global::System.Attribute { public object Key { get; } public AutoKeyedServiceAttribute(object key) => Key = key; } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + internal sealed class ServiceProviderAttribute : global::System.Attribute + { + public string Fallback { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + internal abstract class ServiceAttribute : global::System.Attribute + { + public object Key { get; set; } + public string Factory { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute + { + public SingletonAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute + { + public TransientAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute + { + public ScopedAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ImportAttribute : global::System.Attribute + { + public ImportAttribute(global::System.Type module) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.Flags] + internal enum ScanAs + { + Service = 1, + Self = 2, + ImplementedInterfaces = 4 + } + internal abstract class ScanAttribute : global::System.Attribute + { + public ScanAs As { get; set; } + public global::System.Type[] FromAssembliesOf { get; set; } + public string TypeNameFilter { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanSingletonAttribute : ScanAttribute + { + public ScanSingletonAttribute(global::System.Type service = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanTransientAttribute : ScanAttribute + { + public ScanTransientAttribute(global::System.Type service = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanScopedAttribute : ScanAttribute + { + public ScanScopedAttribute(global::System.Type service = null) { } + } + #if AUTOCTOR_EMBED_GENERIC_ATTRIBUTES + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ImportAttribute : global::System.Attribute { } + #endif } #endif ``` -snippet source | anchor +snippet source | anchor
@@ -669,10 +2679,3 @@ If you wish to preserve these attributes in the build output, add the define con ``` Back to Contents -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=distantcam/autoctor&type=date&legend=top-left)](https://www.star-history.com/#distantcam/autoctor&type=date&legend=top-left) - -## Stats - -![Alt](https://repobeats.axiom.co/api/embed/8d02b2c004a5f958b4365abad3d4d1882dca200f.svg "Repobeats analytics image") diff --git a/readme.nuget.md b/readme.nuget.md index 527032d0..3373a6cd 100644 --- a/readme.nuget.md +++ b/readme.nuget.md @@ -37,3 +37,23 @@ public partial class AService - } } ``` + +# Service Providers + +AutoCtor can also build the container. Register services with attributes on a partial class, and the whole object graph is resolved during compilation and emitted as plain C#. + +```c# +[ServiceProvider] +[Singleton] +[Scoped] +[Transient] +public partial class Container; +``` + +You get a real `IServiceProvider` with scopes, and disposal in reverse construction order. There is no reflection, no container and no registration API at run time -- every construction site is a literal `new`, so there is nothing for trimming or AOT to preserve. + +Because the graph is decided when the compiler runs, the problems Microsoft's container finds when someone resolves a service are build errors instead: a dependency nothing is registered for, a circular dependency, a scoped service captured by a singleton. + +Singletons, scoped services, transients, keyed services, open generics, `IEnumerable` injection, factory members and `IAsyncDisposable` are all supported. AutoCtor adds no package dependency of its own: where `Microsoft.Extensions.DependencyInjection.Abstractions` is referenced the generated types implement the interfaces from it that fit, and where it is not, those interfaces are left off and every member behind them stands on its own. A provider can also fall back to another `IServiceProvider` for anything it does not register, which is how it sits under a host. + +See the [full readme](https://github.com/distantcam/AutoCtor#service-provider) for details. diff --git a/readme.source.md b/readme.source.md index 1d1cbaee..8a8fb47b 100644 --- a/readme.source.md +++ b/readme.source.md @@ -50,6 +50,10 @@ Starting in version 3.0, a new diagnostic has been introduced: `ACTR007 Use [Aut In 3.1 another code fix was added: `Add [AutoConstruct] to type`. This will add `[AutoConstruct]` to a type with no constructor and relevant fields. This is useful when working on a new type. +## Service Provider Source Generator (new in 4.0) + +In version 4.0 you can now generate your own Service Provider that will work with the constructors generated by AutoCtor. See [Service Provider](#service-provider) + ## Examples ### Basic @@ -171,6 +175,88 @@ snippet: Keyed.cs#Keyed.g.verified.cs +Back to Contents +## Service Provider + +snippet: ServiceProvider + +
What gets generated + +snippet: ServiceProvider.cs#Container.ServiceProvider.g.verified.cs + +
+ +Add `[ServiceProvider]` to a blank partial class and register services with `[Singleton]`, `[Transient]` or `[Scoped]` attributes. + +The service provider is fully generated at compile time so there is no runtime reflection. + +Multiple registrations for the same service can be returned with `IEnumerable`. When getting just one the last registration wins. + + +### Scanning + +As well as registering services individually there is also an option to scan for services. Scanning is performed at compile time. + +snippet: ServiceProviderScan + +
What gets generated + +snippet: ServiceProviderScan.cs#OrderContainer.ServiceProvider.g.verified.cs + +
+ +The scan attributes `[ScanSingleton]`, `[ScanTransient]`, and `[ScanScoped]` have some options. + +- `As` determines what to register the service as: `ScanAs.Service`, the service being scanned for (default); `ScanAs.Self`, the type itself; `ScanAs.ImplementedInterfaces`, all the interfaces the type has. +- `FromAssembliesOf` can be used to scan other assemblies instead of the one the provider is in; add the provider too to keep it in the list of assemblies to scan. +- `TypeNameFilter` only includes types whose name matches, where `*` matches anything and `?` any one character, e.g. `"*Repository"`. The service can be left out to select by name alone. + +### Keyed Services + +snippet: ServiceProviderKeyed + +
What gets generated + +snippet: ServiceProviderKeyed.cs#PaymentContainer.ServiceProvider.g.verified.cs + +
+ +### Factories + +snippet: ServiceProviderFactory + +
What gets generated + +snippet: ServiceProviderFactory.cs#HostContainer.ServiceProvider.g.verified.cs + +
+ +### Falling back to another provider + +`Fallback` on `[ServiceProvider]` names a member to use when there is no registration for that service. + +snippet: ServiceProviderFallback + +
What gets generated + +snippet: ServiceProviderFallback.cs#FallbackContainer.ServiceProvider.g.verified.cs + +
+ +### Modules + +`[Import]` (or `[Import(typeof(TModule))]`) adds all the registrations from another type. Modules just act as markers so no code is generated for them. + +snippet: ServiceProviderModule + +
What gets generated + +snippet: ServiceProviderModule.cs#TestContainer.ServiceProvider.g.verified.cs + +
+ +A module can live in another assembly, but the registration attributes are `[Conditional]`, so that assembly has to define `AUTOCTOR_USAGES` (see [Keeping Attributes In Code](#keeping-attributes-in-code)) for them to be there to read. + Back to Contents ## Other @@ -200,6 +286,8 @@ Your project file should look like this: ``` +The generic forms of `[Singleton]`, `[Transient]` and `[Scoped]` are a C# 11 feature, so they are not embedded by default. If your project targets C# 11 or later and you want them, also define `AUTOCTOR_EMBED_GENERIC_ATTRIBUTES`. +
What gets generated snippet: GeneratedAttributeTests.cs#AutoConstructAttribute.g.verified.cs @@ -224,10 +312,3 @@ If you wish to preserve these attributes in the build output, add the define con ``` Back to Contents -## Star History - -[![Star History Chart](https://api.star-history.com/svg?repos=distantcam/autoctor&type=date&legend=top-left)](https://www.star-history.com/#distantcam/autoctor&type=date&legend=top-left) - -## Stats - -![Alt](https://repobeats.axiom.co/api/embed/8d02b2c004a5f958b4365abad3d4d1882dca200f.svg "Repobeats analytics image") diff --git a/src/AutoCtor.Attributes/IoCAttributes.cs b/src/AutoCtor.Attributes/IoCAttributes.cs new file mode 100644 index 00000000..41de74a7 --- /dev/null +++ b/src/AutoCtor.Attributes/IoCAttributes.cs @@ -0,0 +1,134 @@ +using System.Diagnostics; +using static System.AttributeTargets; + +namespace AutoCtor; + +/// +/// Generates a compile time from the registration attributes on this class. +/// +[AttributeUsage(Class, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ServiceProviderAttribute : Attribute +{ + /// + /// A field, property or parameterless method (named with nameof) returning an + /// to ask for anything not registered. + /// + public string? Fallback { get; set; } +} + +public abstract class ServiceAttribute : Attribute +{ + public object? Key { get; set; } + + /// + /// A method, property or field (named with nameof) that builds the service instead + /// of its constructor. A method's parameters are resolved like constructor parameters. + /// + public string? Factory { get; set; } +} + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class SingletonAttribute(Type service, Type? implementation = null) : ServiceAttribute +{ + public Type Service { get; } = service; + public Type? Implementation { get; } = implementation; +} + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class SingletonAttribute : ServiceAttribute; + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class SingletonAttribute : ServiceAttribute; + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class TransientAttribute(Type service, Type? implementation = null) : ServiceAttribute +{ + public Type Service { get; } = service; + public Type? Implementation { get; } = implementation; +} + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class TransientAttribute : ServiceAttribute; + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class TransientAttribute : ServiceAttribute; + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScopedAttribute(Type service, Type? implementation = null) : ServiceAttribute +{ + public Type Service { get; } = service; + public Type? Implementation { get; } = implementation; +} + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScopedAttribute : ServiceAttribute; + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScopedAttribute : ServiceAttribute; + +/// +/// Adds every registration, scan and import on another class, in place. A module in another +/// assembly is only seen when that assembly defines AUTOCTOR_USAGES. +/// +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ImportAttribute(Type module) : Attribute +{ + public Type Module { get; } = module; +} + +/// +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ImportAttribute : Attribute; + +[Flags] +public enum ScanAs +{ + /// The scanned for type, closed over the match for an open generic. + Service = 1, + /// The implementation itself. + Self = 2, + /// Every interface of the implementation. + ImplementedInterfaces = 4, +} + +/// +/// Registers every accessible, concrete, non-generic class assignable to , +/// and matching . At least one of the two is required. +/// +public abstract class ScanAttribute(Type? service) : Attribute +{ + /// + /// An open generic such as typeof(IHandler<>) matches every construction of it. + /// Without one, registers each type as itself. + /// + public Type? Service { get; } = service; + public ScanAs As { get; set; } = ScanAs.Service; + /// Scans the assemblies of these types instead of the provider's own. + public Type[]? FromAssembliesOf { get; set; } + /// Only types whose name matches, where * matches anything and ? any one character, such as "*Repository". + public string? TypeNameFilter { get; set; } +} + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScanSingletonAttribute(Type? service = null) : ScanAttribute(service); + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScanTransientAttribute(Type? service = null) : ScanAttribute(service); + +[AttributeUsage(Class, AllowMultiple = true, Inherited = false)] +[Conditional("AUTOCTOR_USAGES")] +public sealed class ScanScopedAttribute(Type? service = null) : ScanAttribute(service); diff --git a/src/AutoCtor.Example/AutoCtor.Example.csproj b/src/AutoCtor.Example/AutoCtor.Example.csproj deleted file mode 100644 index 7dae911e..00000000 --- a/src/AutoCtor.Example/AutoCtor.Example.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net10.0 - disable - false - CS0169 - - - - - - - - - - - \ No newline at end of file diff --git a/src/AutoCtor.Roslyn3.11/AutoConstructSourceGenerator.cs b/src/AutoCtor.Roslyn3.11/AutoConstructSourceGenerator.cs index e1917de1..7f5b297d 100644 --- a/src/AutoCtor.Roslyn3.11/AutoConstructSourceGenerator.cs +++ b/src/AutoCtor.Roslyn3.11/AutoConstructSourceGenerator.cs @@ -9,6 +9,7 @@ private sealed class SyntaxContextReceiver(CancellationToken cancellationToken) { public List? TypeModels { get; private set; } public List? MarkedMethods { get; private set; } + public List? ServiceProviders { get; private set; } public void OnVisitSyntaxNode(GeneratorSyntaxContext context) { @@ -16,11 +17,13 @@ public void OnVisitSyntaxNode(GeneratorSyntaxContext context) IMethodSymbol? method; if (GeneratorUtilities.IsTypeDeclarationWithAttributes(context.Node, cancellationToken) - && (type = GeneratorUtilities.GetSymbol(context, cancellationToken)) != null - - && Utilities.HasAttribute(type, AttributeNames.AutoConstruct)) + && (type = GeneratorUtilities.GetPrimarySymbol(context, cancellationToken)) != null) { - (TypeModels ??= []).Add(TypeModel.Create(type)); + if (Utilities.HasAttribute(type, AttributeNames.AutoConstruct)) + (TypeModels ??= []).Add(TypeModel.Create(type)); + + if (Utilities.HasAttribute(type, AttributeNames.ServiceProvider)) + (ServiceProviders ??= []).Add(ServiceProviderModel.Create(type)); } else if (GeneratorUtilities.IsMethodDeclarationWithAttributes(context.Node, cancellationToken) @@ -43,7 +46,7 @@ public void Initialize(GeneratorInitializationContext context) public void Execute(GeneratorExecutionContext context) { if (context.SyntaxContextReceiver is not SyntaxContextReceiver receiver - || receiver.TypeModels == null) + || (receiver.TypeModels == null && receiver.ServiceProviders == null)) return; var enableGuards = false; @@ -55,10 +58,11 @@ public void Execute(GeneratorExecutionContext context) projectGuardSetting.Equals("enable", StringComparison.OrdinalIgnoreCase); } - var models = ( - receiver.TypeModels.ToImmutableArray(), - receiver.MarkedMethods?.ToImmutableArray() ?? ImmutableArray.Empty - ); - Emitter.GenerateSource(context, (models, enableGuards)); + Emitter.GenerateSource(context, ( + receiver.TypeModels?.ToImmutableArray() ?? ImmutableArray.Empty, + receiver.MarkedMethods?.ToImmutableArray() ?? ImmutableArray.Empty, + enableGuards, + receiver.ServiceProviders?.ToImmutableArray() ?? ImmutableArray.Empty, + DuckTypes.Create(context.Compilation))); } } diff --git a/src/AutoCtor.Roslyn4.0/AutoConstructSourceGenerator.cs b/src/AutoCtor.Roslyn4.0/AutoConstructSourceGenerator.cs index 15d2f473..0bd0bf99 100644 --- a/src/AutoCtor.Roslyn4.0/AutoConstructSourceGenerator.cs +++ b/src/AutoCtor.Roslyn4.0/AutoConstructSourceGenerator.cs @@ -16,7 +16,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) var types = context.SyntaxProvider.CreateSyntaxProvider( GeneratorUtilities.IsTypeDeclarationWithAttributes, - GeneratorUtilities.GetSymbol) + GeneratorUtilities.GetPrimarySymbol) .Where(static x => Utilities.HasAttribute(x, AttributeNames.AutoConstruct)) .Select(static (x, _) => TypeModel.Create(x!)) .Collect(); @@ -28,8 +28,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .Select(static (x, _) => PostCtorModel.Create(x!)) .Collect(); + var serviceProviders = context.SyntaxProvider.CreateSyntaxProvider( + GeneratorUtilities.IsTypeDeclarationWithAttributes, + GeneratorUtilities.GetPrimarySymbol) + .Where(static x => Utilities.HasAttribute(x, AttributeNames.ServiceProvider)) + .Select(static (x, _) => ServiceProviderModel.Create(x!)) + .Collect(); + + var duckTypes = context.CompilationProvider.Select(static (c, ct) => DuckTypes.Create(c)); + context.RegisterSourceOutput( - types.Combine(postCtorMethods).Combine(properties), + types.Combine(postCtorMethods).Combine(properties).Combine(serviceProviders).Combine(duckTypes) + .Select(static (x, _) => (x.Left.Left.Left.Left, x.Left.Left.Left.Right, x.Left.Left.Right, x.Left.Right, x.Right)), Emitter.GenerateSource); } } diff --git a/src/AutoCtor.Roslyn4.4/AutoConstructSourceGenerator.cs b/src/AutoCtor.Roslyn4.4/AutoConstructSourceGenerator.cs index a0cbe3c0..07834385 100644 --- a/src/AutoCtor.Roslyn4.4/AutoConstructSourceGenerator.cs +++ b/src/AutoCtor.Roslyn4.4/AutoConstructSourceGenerator.cs @@ -29,8 +29,18 @@ public void Initialize(IncrementalGeneratorInitializationContext context) .WithTrackingName(TrackingNames.PostCtorMethods) .Collect(); + var serviceProviders = context.SyntaxProvider.ForAttributeWithMetadataName( + AttributeNames.ServiceProvider, + GeneratorUtilities.IsTypeDeclarationWithAttributes, + static (c, ct) => ServiceProviderModel.Create((INamedTypeSymbol)c.TargetSymbol)) + .WithTrackingName(TrackingNames.ServiceProviders) + .Collect(); + + var duckTypes = context.CompilationProvider.Select(static (c, ct) => DuckTypes.Create(c)); + context.RegisterSourceOutput( - types.Combine(postCtorMethods).Combine(properties), + types.Combine(postCtorMethods).Combine(properties).Combine(serviceProviders).Combine(duckTypes) + .Select(static (x, _) => (x.Left.Left.Left.Left, x.Left.Left.Left.Right, x.Left.Left.Right, x.Left.Right, x.Right)), Emitter.GenerateSource); } } diff --git a/src/Benchmarks/AutoCtorBenchmarks.cs b/src/Benchmarks/AutoCtorBenchmarks.cs deleted file mode 100644 index e318c66c..00000000 --- a/src/Benchmarks/AutoCtorBenchmarks.cs +++ /dev/null @@ -1,88 +0,0 @@ -using AutoCtor; -using BenchmarkDotNet.Attributes; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Testing; - -namespace AutoCtor.Benchmarks; - -[MemoryDiagnoser] -public class AutoCtorBenchmarks -{ - private static IReadOnlyList? s_references; - - private CSharpCompilation _compilation = null!; - private GeneratorDriver _warmDriver = null!; - - [Params(100, 500, 1000)] - public int FileCount { get; set; } - - [Params(5, 10)] - public int FieldCount { get; set; } - - [GlobalSetup] - public async Task Setup() - { - s_references ??= (await ReferenceAssemblies.NetStandard.NetStandard20 - .ResolveAsync(LanguageNames.CSharp, CancellationToken.None) - .ConfigureAwait(false)) - .Add(MetadataReference.CreateFromFile(typeof(AutoConstructAttribute).Assembly.Location)); - - _compilation = CSharpCompilation.Create( - "BenchmarkAssembly", - Enumerable.Range(0, FileCount) - .Select(i => CSharpSyntaxTree.ParseText(BuildCode(i, FieldCount))) - .ToArray(), - s_references, - new CSharpCompilationOptions( - OutputKind.DynamicallyLinkedLibrary, - nullableContextOptions: NullableContextOptions.Enable)); - - _warmDriver = CreateDriver().RunGenerators(_compilation); - } - - [Benchmark(Baseline = true)] - public GeneratorDriverRunResult Cold() - { - return CreateDriver().RunGenerators(_compilation).GetRunResult(); - } - - [Benchmark] - public GeneratorDriverRunResult Cached() - { - _warmDriver = _warmDriver.RunGenerators(_compilation); - return _warmDriver.GetRunResult(); - } - - private static CSharpGeneratorDriver CreateDriver() - { - return CSharpGeneratorDriver.Create( - [new AutoConstructSourceGenerator().AsSourceGenerator()], - parseOptions: CSharpParseOptions.Default); - } - - private static string BuildCode(int fileIndex, int fieldCount) - { - var interfaces = string.Join( - "\n", - Enumerable.Range(1, fieldCount) - .Select(i => $"public interface IService{fileIndex}_{i} {{ }}")); - - var fields = string.Join( - "\n", - Enumerable.Range(1, fieldCount) - .Select(i => $" private readonly IService{fileIndex}_{i} _service{i};")); - - return $$""" - using AutoCtor; - - {{interfaces}} - - [AutoConstruct] - public partial class TestService{{fileIndex}} - { - {{fields}} - } - """; - } -} diff --git a/src/Benchmarks/BasicComplexBenchmark.cs b/src/Benchmarks/BasicComplexBenchmark.cs new file mode 100644 index 00000000..ef4b98de --- /dev/null +++ b/src/Benchmarks/BasicComplexBenchmark.cs @@ -0,0 +1,118 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +[BenchmarkCategory(Categories.Complex)] +[ShortRunJob] +[MemoryDiagnoser] +public partial class BasicComplexBenchmark +{ + // One loop length. The 100 and 1000 runs measured the same per-resolve cost, and + // resolving one, two or three services differed from each other by a multiplier. + private const int GetCount = 1000; + + private readonly ServiceProvider _provider; + private readonly JabComplexProvider _jabProvider = new(); + private readonly AutoCtorProvider _autoCtorProvider = new(); + + public BasicComplexBenchmark() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + _provider = serviceCollection.BuildServiceProvider(); + } + + [BenchmarkCategory(Categories.MEDI)] + [Benchmark] + public void MEDI() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _provider.CreateScope(); + + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + } + } + + [BenchmarkCategory(Categories.Jab)] + [Benchmark(Baseline = true)] + public void Jab() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _jabProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [BenchmarkCategory(Categories.AutoCtor)] + [Benchmark] + public void AutoCtor() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _autoCtorProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [ServiceProvider] + [Scoped(typeof(IComplex1), typeof(Complex1))] + [Scoped(typeof(IComplex2), typeof(Complex2))] + [Scoped(typeof(IComplex3), typeof(Complex3))] + [Transient(typeof(IService1), typeof(Service1))] + [Transient(typeof(IService2), typeof(Service2))] + [Transient(typeof(IService3), typeof(Service3))] + [Transient(typeof(IMix1), typeof(Mix1))] + [Transient(typeof(IMix2), typeof(Mix2))] + [Transient(typeof(IMix3), typeof(Mix3))] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class JabComplexProvider; + + [AutoCtor.ServiceProvider] + [AutoCtor.Scoped(typeof(IComplex1), typeof(Complex1))] + [AutoCtor.Scoped(typeof(IComplex2), typeof(Complex2))] + [AutoCtor.Scoped(typeof(IComplex3), typeof(Complex3))] + [AutoCtor.Transient(typeof(IService1), typeof(Service1))] + [AutoCtor.Transient(typeof(IService2), typeof(Service2))] + [AutoCtor.Transient(typeof(IService3), typeof(Service3))] + [AutoCtor.Transient(typeof(IMix1), typeof(Mix1))] + [AutoCtor.Transient(typeof(IMix2), typeof(Mix2))] + [AutoCtor.Transient(typeof(IMix3), typeof(Mix3))] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorProvider; +} diff --git a/src/Benchmarks/BasicMixedBenchmark.cs b/src/Benchmarks/BasicMixedBenchmark.cs new file mode 100644 index 00000000..65e09e25 --- /dev/null +++ b/src/Benchmarks/BasicMixedBenchmark.cs @@ -0,0 +1,100 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +[BenchmarkCategory(Categories.Mixed)] +[ShortRunJob] +[MemoryDiagnoser] +public partial class BasicMixedBenchmark +{ + // One loop length. The 100 and 1000 runs measured the same per-resolve cost, and + // resolving one, two or three services differed from each other by a multiplier. + private const int GetCount = 1000; + + private readonly ServiceProvider _provider; + private readonly JabMixProvider _jabProvider = new(); + private readonly AutoCtorProvider _autoCtorProvider = new(); + + public BasicMixedBenchmark() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + _provider = serviceCollection.BuildServiceProvider(); + } + + [BenchmarkCategory(Categories.MEDI)] + [Benchmark] + public void MEDI() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _provider.CreateScope(); + + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + } + } + + [BenchmarkCategory(Categories.Jab)] + [Benchmark(Baseline = true)] + public void Jab() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _jabProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [BenchmarkCategory(Categories.AutoCtor)] + [Benchmark] + public void AutoCtor() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _autoCtorProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [ServiceProvider] + [Transient(typeof(IMix1), typeof(Mix1))] + [Transient(typeof(IMix2), typeof(Mix2))] + [Transient(typeof(IMix3), typeof(Mix3))] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class JabMixProvider; + + [AutoCtor.ServiceProvider] + [AutoCtor.Transient(typeof(IMix1), typeof(Mix1))] + [AutoCtor.Transient(typeof(IMix2), typeof(Mix2))] + [AutoCtor.Transient(typeof(IMix3), typeof(Mix3))] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorProvider; +} diff --git a/src/Benchmarks/BasicScopedBenchmark.cs b/src/Benchmarks/BasicScopedBenchmark.cs new file mode 100644 index 00000000..5a91fa1f --- /dev/null +++ b/src/Benchmarks/BasicScopedBenchmark.cs @@ -0,0 +1,82 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +[BenchmarkCategory(Categories.Scoped)] +[ShortRunJob] +[MemoryDiagnoser] +public partial class BasicScopedBenchmark +{ + // One loop length. The 100 and 1000 runs measured the same per-resolve cost, and + // resolving one, two or three services differed from each other by a multiplier. + private const int GetCount = 1000; + + private readonly ServiceProvider _provider; + private readonly JabScopedProvider _jabProvider = new(); + private readonly AutoCtorProvider _autoCtorProvider = new(); + + public BasicScopedBenchmark() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + _provider = serviceCollection.BuildServiceProvider(); + } + + [BenchmarkCategory(Categories.MEDI)] + [Benchmark] + public void MEDI() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _provider.CreateScope(); + + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + scope.ServiceProvider.GetService(); + } + } + + [BenchmarkCategory(Categories.Jab)] + [Benchmark(Baseline = true)] + public void Jab() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _jabProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [BenchmarkCategory(Categories.AutoCtor)] + [Benchmark] + public void AutoCtor() + { + for (var i = 0; i < GetCount; i++) + { + using var scope = _autoCtorProvider.CreateScope(); + + scope.GetService(); + scope.GetService(); + scope.GetService(); + } + } + + [ServiceProvider] + [Scoped(typeof(IScoped1), typeof(Scoped1))] + [Scoped(typeof(IScoped2), typeof(Scoped2))] + [Scoped(typeof(IScoped3), typeof(Scoped3))] + internal sealed partial class JabScopedProvider; + + [AutoCtor.ServiceProvider] + [AutoCtor.Scoped(typeof(IScoped1), typeof(Scoped1))] + [AutoCtor.Scoped(typeof(IScoped2), typeof(Scoped2))] + [AutoCtor.Scoped(typeof(IScoped3), typeof(Scoped3))] + internal sealed partial class AutoCtorProvider; +} diff --git a/src/Benchmarks/BasicSingletonBenchmark.cs b/src/Benchmarks/BasicSingletonBenchmark.cs new file mode 100644 index 00000000..2a916114 --- /dev/null +++ b/src/Benchmarks/BasicSingletonBenchmark.cs @@ -0,0 +1,76 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +[BenchmarkCategory(Categories.Singleton)] +[ShortRunJob] +[MemoryDiagnoser] +public partial class BasicSingletonBenchmark +{ + // One loop length. The 100 and 1000 runs measured the same per-resolve cost, and + // resolving one, two or three services differed from each other by a multiplier. + private const int GetCount = 1000; + + private readonly ServiceProvider _provider; + private readonly JabSingletonProvider _jabProvider = new(); + private readonly AutoCtorProvider _autoCtorProvider = new(); + + public BasicSingletonBenchmark() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + _provider = serviceCollection.BuildServiceProvider(); + } + + [BenchmarkCategory(Categories.MEDI)] + [Benchmark] + public void MEDI() + { + for (var i = 0; i < GetCount; i++) + { + _provider.GetService(); + _provider.GetService(); + _provider.GetService(); + } + } + + [BenchmarkCategory(Categories.Jab)] + [Benchmark(Baseline = true)] + public void Jab() + { + for (var i = 0; i < GetCount; i++) + { + _jabProvider.GetService(); + _jabProvider.GetService(); + _jabProvider.GetService(); + } + } + + [BenchmarkCategory(Categories.AutoCtor)] + [Benchmark] + public void AutoCtor() + { + for (var i = 0; i < GetCount; i++) + { + _autoCtorProvider.GetService(); + _autoCtorProvider.GetService(); + _autoCtorProvider.GetService(); + } + } + + [ServiceProvider] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class JabSingletonProvider; + + [AutoCtor.ServiceProvider] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorProvider; +} diff --git a/src/Benchmarks/BasicTransientBenchmark.cs b/src/Benchmarks/BasicTransientBenchmark.cs new file mode 100644 index 00000000..2f876377 --- /dev/null +++ b/src/Benchmarks/BasicTransientBenchmark.cs @@ -0,0 +1,76 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +[BenchmarkCategory(Categories.Transient)] +[ShortRunJob] +[MemoryDiagnoser] +public partial class BasicTransientBenchmark +{ + // One loop length. The 100 and 1000 runs measured the same per-resolve cost, and + // resolving one, two or three services differed from each other by a multiplier. + private const int GetCount = 1000; + + private readonly ServiceProvider _provider; + private readonly JabTransientProvider _jabProvider = new(); + private readonly AutoCtorProvider _autoCtorProvider = new(); + + public BasicTransientBenchmark() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + _provider = serviceCollection.BuildServiceProvider(); + } + + [BenchmarkCategory(Categories.MEDI)] + [Benchmark] + public void MEDI() + { + for (var i = 0; i < GetCount; i++) + { + _provider.GetService(); + _provider.GetService(); + _provider.GetService(); + } + } + + [BenchmarkCategory(Categories.Jab)] + [Benchmark(Baseline = true)] + public void Jab() + { + for (var i = 0; i < GetCount; i++) + { + _jabProvider.GetService(); + _jabProvider.GetService(); + _jabProvider.GetService(); + } + } + + [BenchmarkCategory(Categories.AutoCtor)] + [Benchmark] + public void AutoCtor() + { + for (var i = 0; i < GetCount; i++) + { + _autoCtorProvider.GetService(); + _autoCtorProvider.GetService(); + _autoCtorProvider.GetService(); + } + } + + [ServiceProvider] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + private sealed partial class JabTransientProvider; + + [AutoCtor.ServiceProvider] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + private sealed partial class AutoCtorProvider; +} diff --git a/src/Benchmarks/AutoCtor.Benchmarks.csproj b/src/Benchmarks/Benchmarks.csproj similarity index 73% rename from src/Benchmarks/AutoCtor.Benchmarks.csproj rename to src/Benchmarks/Benchmarks.csproj index 7c43dc46..7ab5e483 100644 --- a/src/Benchmarks/AutoCtor.Benchmarks.csproj +++ b/src/Benchmarks/Benchmarks.csproj @@ -4,19 +4,21 @@ Exe net10.0 false - CA1515 + default + + - + diff --git a/src/Benchmarks/Categories.cs b/src/Benchmarks/Categories.cs new file mode 100644 index 00000000..6c5179a8 --- /dev/null +++ b/src/Benchmarks/Categories.cs @@ -0,0 +1,14 @@ +namespace Benchmarks; + +internal class Categories +{ + public const string Singleton = "Singleton"; + public const string Transient = "Transient"; + public const string Scoped = "Scoped"; + public const string Mixed = "Mixed"; + public const string Complex = "Complex"; + + public const string MEDI = "MEDI"; + public const string Jab = "Jab"; + public const string AutoCtor = "AutoCtor"; +} diff --git a/src/Benchmarks/Complex.cs b/src/Benchmarks/Complex.cs new file mode 100644 index 00000000..5a717200 --- /dev/null +++ b/src/Benchmarks/Complex.cs @@ -0,0 +1,127 @@ +namespace Benchmarks; + +public interface IComplex1 +{ + void Do1(); +} + +public interface IComplex2 +{ + void Do2(); +} + +public interface IComplex3 +{ + void Do3(); +} + +public interface IService1 +{ + void Do1(); +} + +public interface IService2 +{ + void Do2(); +} + +public interface IService3 +{ + void Do3(); +} + +public class Service1(ITransient1 transient1) : IService1 +{ + public void Do1() + { + transient1.Do1(); + } +} + +public class Service2(ITransient2 transient2) : IService2 +{ + public void Do2() + { + transient2.Do2(); + } +} + +public class Service3(ITransient3 transient3) : IService3 +{ + public void Do3() + { + transient3.Do3(); + } +} + +public class Complex1( + IService1 service1, + IService2 service2, + IService3 service3, + IMix1 mix1, + IMix2 mix2, + IMix3 mix3, + ISingleton1 singleton1, + ITransient1 transient1 +) : IComplex1 +{ + public void Do1() + { + service1.Do1(); + service2.Do2(); + service3.Do3(); + mix1.Do1(); + mix2.Do2(); + mix3.Do3(); + singleton1.Do1(); + transient1.Do1(); + } +} + +public class Complex2( + IService1 service1, + IService2 service2, + IService3 service3, + IMix1 mix1, + IMix2 mix2, + IMix3 mix3, + ISingleton2 singleton2, + ITransient2 transient2 +) : IComplex2 +{ + public void Do2() + { + service1.Do1(); + service2.Do2(); + service3.Do3(); + mix1.Do1(); + mix2.Do2(); + mix3.Do3(); + singleton2.Do2(); + transient2.Do2(); + } +} + +public class Complex3( + IService1 service1, + IService2 service2, + IService3 service3, + IMix1 mix1, + IMix2 mix2, + IMix3 mix3, + ISingleton3 singleton3, + ITransient3 transient3 +) : IComplex3 +{ + public void Do3() + { + service1.Do1(); + service2.Do2(); + service3.Do3(); + mix1.Do1(); + mix2.Do2(); + mix3.Do3(); + singleton3.Do3(); + transient3.Do3(); + } +} diff --git a/src/Benchmarks/Mixed.cs b/src/Benchmarks/Mixed.cs new file mode 100644 index 00000000..f57e4eb3 --- /dev/null +++ b/src/Benchmarks/Mixed.cs @@ -0,0 +1,43 @@ +namespace Benchmarks; + +public interface IMix1 +{ + void Do1(); +} + +public interface IMix2 +{ + void Do2(); +} + +public interface IMix3 +{ + void Do3(); +} + +public class Mix1(ISingleton1 singleton1, ITransient1 transient1) : IMix1 +{ + public void Do1() + { + singleton1.Do1(); + transient1.Do1(); + } +} + +public class Mix2(ISingleton2 singleton2, ITransient2 transient2) : IMix2 +{ + public void Do2() + { + singleton2.Do2(); + transient2.Do2(); + } +} + +public class Mix3(ISingleton3 singleton3, ITransient3 transient3) : IMix3 +{ + public void Do3() + { + singleton3.Do3(); + transient3.Do3(); + } +} diff --git a/src/Benchmarks/Program.cs b/src/Benchmarks/Program.cs index 17a3237f..0b525519 100644 --- a/src/Benchmarks/Program.cs +++ b/src/Benchmarks/Program.cs @@ -1,8 +1,10 @@ -using AutoCtor.Benchmarks; +using System.Reflection; using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; using BenchmarkDotNet.Running; var config = ManualConfig.Create(DefaultConfig.Instance) - .WithOptions(ConfigOptions.DisableOptimizationsValidator); + .WithOptions(ConfigOptions.DisableLogFile) + .WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)); -BenchmarkRunner.Run(config, args); +BenchmarkRunner.Run(Assembly.GetExecutingAssembly(), config); diff --git a/src/Benchmarks/Scoped.cs b/src/Benchmarks/Scoped.cs new file mode 100644 index 00000000..168271a3 --- /dev/null +++ b/src/Benchmarks/Scoped.cs @@ -0,0 +1,31 @@ +namespace Benchmarks; + +public interface IScoped1 +{ + void Do1(); +} + +public interface IScoped2 +{ + void Do2(); +} + +public interface IScoped3 +{ + void Do3(); +} + +public class Scoped1 : IScoped1 +{ + public void Do1() { } +} + +public class Scoped2 : IScoped2 +{ + public void Do2() { } +} + +public class Scoped3 : IScoped3 +{ + public void Do3() { } +} diff --git a/src/Benchmarks/Singletons.cs b/src/Benchmarks/Singletons.cs new file mode 100644 index 00000000..5257978e --- /dev/null +++ b/src/Benchmarks/Singletons.cs @@ -0,0 +1,31 @@ +namespace Benchmarks; + +public interface ISingleton1 +{ + void Do1(); +} + +public interface ISingleton2 +{ + void Do2(); +} + +public interface ISingleton3 +{ + void Do3(); +} + +public class Singleton1 : ISingleton1 +{ + public void Do1() { } +} + +public class Singleton2 : ISingleton2 +{ + public void Do2() { } +} + +public class Singleton3 : ISingleton3 +{ + public void Do3() { } +} diff --git a/src/Benchmarks/StartupBenchmark.cs b/src/Benchmarks/StartupBenchmark.cs new file mode 100644 index 00000000..4cb3136a --- /dev/null +++ b/src/Benchmarks/StartupBenchmark.cs @@ -0,0 +1,232 @@ +using BenchmarkDotNet.Attributes; +using Jab; +using Microsoft.Extensions.DependencyInjection; + +namespace Benchmarks; + +// What it costs to stand a container up, and nothing else. Every method returns the +// provider rather than resolving from it: BenchmarkDotNet consumes the returned value, so +// nothing is optimised away, and no service is constructed. Resolving anything here would +// fold a lookup into the number -- and for AutoCtor and Jab that lookup is far larger than +// the construction it would be hiding inside. +[ShortRunJob] +[MemoryDiagnoser] +public partial class StartupBenchmark +{ + [BenchmarkCategory(Categories.Singleton)] + [Benchmark] + public IServiceProvider Jab_Singleton() => new JabStartupSingleton(); + + [BenchmarkCategory(Categories.Singleton)] + [Benchmark] + public IServiceProvider AutoCtor_Singleton() => new AutoCtorStartupSingleton(); + + [BenchmarkCategory(Categories.Singleton)] + [Benchmark] + public IServiceProvider MEDI_Singleton() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.BuildServiceProvider(); + } + + [ServiceProvider] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class JabStartupSingleton; + + [AutoCtor.ServiceProvider] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorStartupSingleton; + + // ------------------------------------------------------------------------ + + [BenchmarkCategory(Categories.Scoped)] + [Benchmark] + public IServiceProvider Jab_Scoped() => new JabStartupScoped(); + + [BenchmarkCategory(Categories.Scoped)] + [Benchmark] + public IServiceProvider AutoCtor_Scoped() => new AutoCtorStartupScoped(); + + [BenchmarkCategory(Categories.Scoped)] + [Benchmark] + public IServiceProvider MEDI_Scoped() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + return serviceCollection.BuildServiceProvider(); + } + + [ServiceProvider] + [Scoped(typeof(IScoped1), typeof(Scoped1))] + [Scoped(typeof(IScoped2), typeof(Scoped2))] + [Scoped(typeof(IScoped3), typeof(Scoped3))] + private partial class JabStartupScoped; + + [AutoCtor.ServiceProvider] + [AutoCtor.Scoped(typeof(IScoped1), typeof(Scoped1))] + [AutoCtor.Scoped(typeof(IScoped2), typeof(Scoped2))] + [AutoCtor.Scoped(typeof(IScoped3), typeof(Scoped3))] + private sealed partial class AutoCtorStartupScoped; + + // ------------------------------------------------------------------------ + + [BenchmarkCategory(Categories.Transient)] + [Benchmark] + public IServiceProvider Jab_Transient() => new JabStartupTransient(); + + [BenchmarkCategory(Categories.Transient)] + [Benchmark] + public IServiceProvider AutoCtor_Transient() => new AutoCtorStartupTransient(); + + [BenchmarkCategory(Categories.Transient)] + [Benchmark] + public IServiceProvider MEDI_Transient() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + return serviceCollection.BuildServiceProvider(); + } + + [ServiceProvider] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + private partial class JabStartupTransient; + + [AutoCtor.ServiceProvider] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + private sealed partial class AutoCtorStartupTransient; + + // ------------------------------------------------------------------------ + + [BenchmarkCategory(Categories.Mixed)] + [Benchmark] + public IServiceProvider Jab_Mixed() => new JabStartupMixed(); + + [BenchmarkCategory(Categories.Mixed)] + [Benchmark] + public IServiceProvider AutoCtor_Mixed() => new AutoCtorStartupMixed(); + + [BenchmarkCategory(Categories.Mixed)] + [Benchmark] + public IServiceProvider MEDI_Mixed() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.BuildServiceProvider(); + } + + [ServiceProvider] + [Transient(typeof(IMix1), typeof(Mix1))] + [Transient(typeof(IMix2), typeof(Mix2))] + [Transient(typeof(IMix3), typeof(Mix3))] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private partial class JabStartupMixed; + + [AutoCtor.ServiceProvider] + [AutoCtor.Transient(typeof(IMix1), typeof(Mix1))] + [AutoCtor.Transient(typeof(IMix2), typeof(Mix2))] + [AutoCtor.Transient(typeof(IMix3), typeof(Mix3))] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorStartupMixed; + + // ------------------------------------------------------------------------ + + [BenchmarkCategory(Categories.Complex)] + [Benchmark] + public IServiceProvider Jab_Complex() => new JabStartupComplex(); + + [BenchmarkCategory(Categories.Complex)] + [Benchmark] + public IServiceProvider AutoCtor_Complex() => new AutoCtorStartupComplex(); + + [BenchmarkCategory(Categories.Complex)] + [Benchmark] + public IServiceProvider MEDI_Complex() + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddScoped(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + return serviceCollection.BuildServiceProvider(); + } + + [ServiceProvider] + [Scoped(typeof(IComplex1), typeof(Complex1))] + [Scoped(typeof(IComplex2), typeof(Complex2))] + [Scoped(typeof(IComplex3), typeof(Complex3))] + [Transient(typeof(IService1), typeof(Service1))] + [Transient(typeof(IService2), typeof(Service2))] + [Transient(typeof(IService3), typeof(Service3))] + [Transient(typeof(IMix1), typeof(Mix1))] + [Transient(typeof(IMix2), typeof(Mix2))] + [Transient(typeof(IMix3), typeof(Mix3))] + [Transient(typeof(ITransient1), typeof(Transient1))] + [Transient(typeof(ITransient2), typeof(Transient2))] + [Transient(typeof(ITransient3), typeof(Transient3))] + [Singleton(typeof(ISingleton1), typeof(Singleton1))] + [Singleton(typeof(ISingleton2), typeof(Singleton2))] + [Singleton(typeof(ISingleton3), typeof(Singleton3))] + private partial class JabStartupComplex; + + [AutoCtor.ServiceProvider] + [AutoCtor.Scoped(typeof(IComplex1), typeof(Complex1))] + [AutoCtor.Scoped(typeof(IComplex2), typeof(Complex2))] + [AutoCtor.Scoped(typeof(IComplex3), typeof(Complex3))] + [AutoCtor.Transient(typeof(IService1), typeof(Service1))] + [AutoCtor.Transient(typeof(IService2), typeof(Service2))] + [AutoCtor.Transient(typeof(IService3), typeof(Service3))] + [AutoCtor.Transient(typeof(IMix1), typeof(Mix1))] + [AutoCtor.Transient(typeof(IMix2), typeof(Mix2))] + [AutoCtor.Transient(typeof(IMix3), typeof(Mix3))] + [AutoCtor.Transient(typeof(ITransient1), typeof(Transient1))] + [AutoCtor.Transient(typeof(ITransient2), typeof(Transient2))] + [AutoCtor.Transient(typeof(ITransient3), typeof(Transient3))] + [AutoCtor.Singleton(typeof(ISingleton1), typeof(Singleton1))] + [AutoCtor.Singleton(typeof(ISingleton2), typeof(Singleton2))] + [AutoCtor.Singleton(typeof(ISingleton3), typeof(Singleton3))] + private sealed partial class AutoCtorStartupComplex; +} diff --git a/src/Benchmarks/Transient.cs b/src/Benchmarks/Transient.cs new file mode 100644 index 00000000..53163f57 --- /dev/null +++ b/src/Benchmarks/Transient.cs @@ -0,0 +1,31 @@ +namespace Benchmarks; + +public interface ITransient1 +{ + void Do1(); +} + +public interface ITransient2 +{ + void Do2(); +} + +public interface ITransient3 +{ + void Do3(); +} + +public class Transient1 : ITransient1 +{ + public void Do1() { } +} + +public class Transient2 : ITransient2 +{ + public void Do2() { } +} + +public class Transient3 : ITransient3 +{ + public void Do3() { } +} diff --git a/src/CompileBenchmarks/CompileBenchmarks.csproj b/src/CompileBenchmarks/CompileBenchmarks.csproj new file mode 100644 index 00000000..74d5f896 --- /dev/null +++ b/src/CompileBenchmarks/CompileBenchmarks.csproj @@ -0,0 +1,35 @@ + + + + Exe + net10.0 + false + default + + + + local + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/CompileBenchmarks/LocalCompare.cs b/src/CompileBenchmarks/LocalCompare.cs new file mode 100644 index 00000000..26d10402 --- /dev/null +++ b/src/CompileBenchmarks/LocalCompare.cs @@ -0,0 +1,85 @@ +using System.Text; +using AutoCtor; +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Jobs; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; + +namespace CompileBenchmarks; + +[Config(typeof(Config))] +public class LocalCompare +{ + [Params(100, 1000)] + public int ServiceCount { get; set; } + + [Params(100, 1000)] + public int TargetsCount { get; set; } + + private class Config : ManualConfig + { + public Config() + { + AddJob(Job.ShortRun + .WithMsBuildArguments("/p:AutoCtorVersion=local") + .WithId("local")); + AddJob(Job.ShortRun + .WithMsBuildArguments("/p:AutoCtorVersion=3.1.1") + .WithId("3.1.1") + .AsBaseline()); + + HideColumns(Column.Arguments); + AddLogicalGroupRules(BenchmarkLogicalGroupRule.ByParams); + } + } + + private CSharpCompilation _compilation = null!; + private GeneratorDriver _driver = null!; + + [GlobalSetup] + public async Task GlobalSetup() + { + var frameworkReferences = await ReferenceAssemblies.Net.Net100 + .ResolveAsync(CSharpParseOptions.Default.Language, CancellationToken.None) + .ConfigureAwait(false); + + _compilation = CSharpCompilation.Create(nameof(LocalCompare)) + .WithOptions(new(OutputKind.DynamicallyLinkedLibrary)) + .AddReferences(frameworkReferences) + .AddReferences(MetadataReference + .CreateFromFile(typeof(AutoConstructAttribute).Assembly.Location)) + .AddSyntaxTrees(Enumerable.Range(1, ServiceCount) + .Select(i => CSharpSyntaxTree.ParseText($"public interface IService{i:D4};"))) + .AddSyntaxTrees(Enumerable.Range(1, TargetsCount).Select(TargetClassGenerator)); + + _driver = CSharpGeneratorDriver.Create(new AutoConstructSourceGenerator()); + } + + private SyntaxTree TargetClassGenerator(int i) + { + var rng = new Random(i); + + var sb = new StringBuilder(); + + sb.AppendLine("[AutoCtor.AutoConstruct]"); + sb.AppendLine($"public class Target{i:D4}"); + sb.AppendLine("{"); + + var count = rng.Next(ServiceCount); + var items = rng.GetItems(Enumerable.Range(1, ServiceCount).ToArray(), count); + foreach (var item in items) + { + sb.Append($" private readonly IService{item:D4} _service{item:D4};"); + } + + sb.AppendLine("}"); + + return CSharpSyntaxTree.ParseText(sb.ToString()); + } + + [Benchmark] + public GeneratorDriver Standard() => _driver.RunGenerators(_compilation); +} diff --git a/src/CompileBenchmarks/Program.cs b/src/CompileBenchmarks/Program.cs new file mode 100644 index 00000000..1e9f259f --- /dev/null +++ b/src/CompileBenchmarks/Program.cs @@ -0,0 +1,12 @@ +using System.Reflection; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using BenchmarkDotNet.Running; + +// Microsoft.CodeAnalysis.Analyzer.Testing ships non-optimized. It is only used to resolve +// reference assemblies during setup, never on a measured path, so the validator is disabled. +var config = ManualConfig.Create(DefaultConfig.Instance) + .WithOptions(ConfigOptions.DisableLogFile | ConfigOptions.DisableOptimizationsValidator) + .WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)); + +BenchmarkRunner.Run(Assembly.GetExecutingAssembly(), config); diff --git a/src/CompileBenchmarks/Scanner.cs b/src/CompileBenchmarks/Scanner.cs new file mode 100644 index 00000000..6d5c6239 --- /dev/null +++ b/src/CompileBenchmarks/Scanner.cs @@ -0,0 +1,44 @@ +using AutoCtor; +using BenchmarkDotNet.Attributes; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Testing; + +namespace CompileBenchmarks; + +[ShortRunJob] +public class Scanner +{ + [Params(500, 1000, 5000)] + public int PluginCount { get; set; } + + private CSharpCompilation _compilation = null!; + private GeneratorDriver _driver = null!; + + [GlobalSetup] + public async Task GlobalSetup() + { + var frameworkReferences = await ReferenceAssemblies.Net.Net100 + .ResolveAsync(CSharpParseOptions.Default.Language, CancellationToken.None) + .ConfigureAwait(false); + + _compilation = CSharpCompilation.Create(nameof(LocalCompare)) + .WithOptions(new(OutputKind.DynamicallyLinkedLibrary)) + .AddReferences(frameworkReferences) + .AddReferences(MetadataReference + .CreateFromFile(typeof(AutoConstructAttribute).Assembly.Location)) + .AddSyntaxTrees(CSharpSyntaxTree.ParseText("public interface IPlugin;")) + .AddSyntaxTrees(Enumerable.Range(1, PluginCount) + .Select(i => CSharpSyntaxTree.ParseText($"public class Plugin{i:D4} : IPlugin;"))) + .AddSyntaxTrees(CSharpSyntaxTree.ParseText(@" +[AutoCtor.ServiceProvider] +[AutoCtor.ScanSingleton(typeof(IPlugin), FromAssembliesOf = new[] { typeof(Container) })] +public partial class Container; +")); + + _driver = CSharpGeneratorDriver.Create(new AutoConstructSourceGenerator()); + } + + [Benchmark] + public GeneratorDriver Scan() => _driver.RunGenerators(_compilation); +} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 851d0745..a016be45 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -5,13 +5,15 @@ + - + + @@ -19,4 +21,4 @@ - \ No newline at end of file + diff --git a/src/Shared/AnalyzerReleases.Unshipped.md b/src/Shared/AnalyzerReleases.Unshipped.md index 62bfdc21..ab365b59 100644 --- a/src/Shared/AnalyzerReleases.Unshipped.md +++ b/src/Shared/AnalyzerReleases.Unshipped.md @@ -1,2 +1,28 @@ -; Unshipped analyzer release +; Unshipped analyzer release ; https://github.com/dotnet/roslyn/blob/main/src/RoslynAnalyzers/Microsoft.CodeAnalysis.Analyzers/ReleaseTrackingAnalyzers.Help.md + +### New Rules + +Rule ID | Category | Severity | Notes +--------|----------|----------|------- +ACTR010 | AutoCtor | Error | ServiceImplementationMustHaveSinglePublicConstructor +ACTR011 | AutoCtor | Error | ServiceImplementationCannotBeInstantiated +ACTR012 | AutoCtor | Error | ServiceDependencyNotRegistered +ACTR013 | AutoCtor | Error | CircularServiceDependency +ACTR014 | AutoCtor | Error | KeyedServiceDependencyNotRegistered +ACTR015 | AutoCtor | Error | ImplementationNotAssignableToService +ACTR016 | AutoCtor | Error | InvalidOpenGenericRegistration +ACTR017 | AutoCtor | Error | InvalidServiceProviderType +ACTR018 | AutoCtor | Warning | TransientServiceCaptured +ACTR019 | AutoCtor | Error | ScopedServiceCapturedBySingleton +ACTR020 | AutoCtor | Error | OpenGenericConstraintNotSatisfied +ACTR021 | AutoCtor | Error | InvalidServiceFactory +ACTR022 | AutoCtor | Error | ServiceFactoryReturnTypeNotAssignable +ACTR023 | AutoCtor | Error | OpenGenericServiceFactory +ACTR024 | AutoCtor | Error | InvalidProviderFallback +ACTR025 | AutoCtor | Error | ProviderFallbackNotAServiceProvider +ACTR026 | AutoCtor | Warning | ScanFoundNoTypes +ACTR027 | AutoCtor | Warning | ImportedModuleHasNoRegistrations +ACTR028 | AutoCtor | Error | ModuleFactoryMustBeStatic +ACTR029 | AutoCtor | Error | ServiceProviderMustBeSealed +ACTR030 | AutoCtor | Error | ScanHasNoFilter diff --git a/src/Shared/AttributeSourceGenerator/Emitter.cs b/src/Shared/AttributeSourceGenerator/Emitter.cs index d021c88b..0e2aebcb 100644 --- a/src/Shared/AttributeSourceGenerator/Emitter.cs +++ b/src/Shared/AttributeSourceGenerator/Emitter.cs @@ -17,7 +17,7 @@ public static SourceText GenerateSource() source.AppendLine("#if AUTOCTOR_EMBED_ATTRIBUTES"); using (source.StartBlock("namespace AutoCtor")) { - source.AddGeneratedAttributes(AttributeTargets.Enum); + source.AddGeneratedCodeAttribute(); using (source.StartBlock("internal enum GuardSetting")) { source.AppendLine("Default,"); @@ -25,37 +25,122 @@ public static SourceText GenerateSource() source.AppendLine("Enabled"); } - source.AddGeneratedAttributes(AttributeTargets.Class); - source.AppendLine("[global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)]"); - source.AppendLine("internal sealed class AutoConstructAttribute : global::System.Attribute"); - using (source.StartBlock()) + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, "Class", "Struct"); + using (StartAttribute(source, "AutoConstructAttribute")) { - source.AppendLine("public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default)"); - source.StartBlock().Dispose(); + source.AppendLine("public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) { }"); } - source.AddGeneratedAttributes(AttributeTargets.Class); - source.AppendLine("[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]"); - source.AppendLine("internal sealed class AutoPostConstructAttribute : global::System.Attribute"); - source.StartBlock().Dispose(); + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, "Method"); + StartAttribute(source, "AutoPostConstructAttribute").Dispose(); - source.AddGeneratedAttributes(AttributeTargets.Class); - source.AppendLine("[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)]"); - source.AppendLine("internal sealed class AutoConstructIgnoreAttribute : global::System.Attribute"); - source.StartBlock().Dispose(); + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, "Field", "Property"); + StartAttribute(source, "AutoConstructIgnoreAttribute").Dispose(); - source.AddGeneratedAttributes(AttributeTargets.Class); - source.AppendLine("[global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property | global::System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]"); - source.AppendLine("internal sealed class AutoKeyedServiceAttribute : global::System.Attribute"); - using (source.StartBlock()) + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, "Field", "Property", "Parameter"); + using (StartAttribute(source, "AutoKeyedServiceAttribute")) { source.AppendLine("public object Key { get; }"); source.AppendLine("public AutoKeyedServiceAttribute(object key) => Key = key;"); } + + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, "Class"); + using (StartAttribute(source, "ServiceProviderAttribute")) + { + source.AppendLine("public string Fallback { get; set; }"); + } + + source.AddGeneratedCodeAttribute(); + using (source.StartBlock("internal abstract class ServiceAttribute : global::System.Attribute")) + { + source.AppendLine("public object Key { get; set; }"); + source.AppendLine("public string Factory { get; set; }"); + } + + string[] lifetimes = ["Singleton", "Transient", "Scoped"]; + + foreach (var lifetime in lifetimes) + { + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + using (StartAttribute(source, lifetime + "Attribute", "ServiceAttribute")) + { + source.AppendLine($"public {lifetime}Attribute(global::System.Type service, global::System.Type implementation = null) {{ }}"); + } + } + + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + using (StartAttribute(source, "ImportAttribute")) + { + source.AppendLine("public ImportAttribute(global::System.Type module) { }"); + } + + source.AddGeneratedCodeAttribute(); + source.AppendLine("[global::System.Flags]"); + using (source.StartBlock("internal enum ScanAs")) + { + source.AppendLine("Service = 1,"); + source.AppendLine("Self = 2,"); + source.AppendLine("ImplementedInterfaces = 4"); + } + + using (source.StartBlock("internal abstract class ScanAttribute : global::System.Attribute")) + { + source.AppendLine("public ScanAs As { get; set; }"); + source.AppendLine("public global::System.Type[] FromAssembliesOf { get; set; }"); + source.AppendLine("public string TypeNameFilter { get; set; }"); + } + + foreach (var lifetime in lifetimes) + { + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + using (StartAttribute(source, $"Scan{lifetime}Attribute", "ScanAttribute")) + { + source.AppendLine($"public Scan{lifetime}Attribute(global::System.Type service = null) {{ }}"); + } + } + + source.AppendLine("#if AUTOCTOR_EMBED_GENERIC_ATTRIBUTES"); + foreach (var lifetime in lifetimes) + { + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + source.AppendLine($"internal sealed class {lifetime}Attribute : ServiceAttribute {{ }}"); + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + source.AppendLine($"internal sealed class {lifetime}Attribute : ServiceAttribute {{ }}"); + } + source.AddGeneratedCodeAttribute(); + EmitAttributeUsage(source, true, false, "Class"); + source.AppendLine("internal sealed class ImportAttribute : global::System.Attribute { }"); + source.AppendLine("#endif"); } source.AppendLine("#endif"); return source; } + + private static void EmitAttributeUsage(CodeBuilder source, params string[] targets) + => EmitAttributeUsage(source, false, false, targets); + + private static void EmitAttributeUsage(CodeBuilder source, bool allowMultiple, bool inherited, params string[] targets) + { + var targetString = string.Join(" | ", targets.Select(t => $"global::System.AttributeTargets.{t}")); + + source.AppendLine($"[global::System.AttributeUsage({targetString}, AllowMultiple = {allowMultiple}, Inherited = {inherited})]"); + } + + private static IDisposable StartAttribute(CodeBuilder source, string typeName, string baseType = "global::System.Attribute") + { + source.AppendLine($"internal sealed class {typeName} : {baseType}"); + return source.StartBlock(); + } } } diff --git a/src/Shared/AutoConstructSourceGenerator/Emitter.cs b/src/Shared/AutoConstructSourceGenerator/Emitter.cs index 42f68bff..508fa605 100644 --- a/src/Shared/AutoConstructSourceGenerator/Emitter.cs +++ b/src/Shared/AutoConstructSourceGenerator/Emitter.cs @@ -18,13 +18,26 @@ private static class Emitter { public static void GenerateSource( EmitterContext context, - ((ImmutableArray Types, ImmutableArray PostCtorMethods) Models, - bool Guards) input) + (ImmutableArray Types, + ImmutableArray PostCtorMethods, + bool Guards, + ImmutableArray Providers, + DuckTypes DuckTypes) input) { - if (input.Models.Types.IsDefaultOrEmpty) return; + if (input.Types.IsDefaultOrEmpty && input.Providers.IsDefaultOrEmpty) return; var ctorMaps = new Dictionary(); - var orderedTypes = input.Models.Types.OrderBy(static t => t.Depth); + var orderedTypes = input.Types.OrderBy(static t => t.Depth); + + // Indexed once rather than scanned per type: a marked method on every + // [AutoConstruct] type in a large solution makes that scan quadratic. + var postCtorsByType = new Dictionary>(); + foreach (var method in input.PostCtorMethods) + { + if (!postCtorsByType.TryGetValue(method.TypeKey, out var marked)) + postCtorsByType.Add(method.TypeKey, marked = []); + marked.Add(method); + } foreach (var type in orderedTypes) { @@ -67,9 +80,7 @@ public static void GenerateSource( } } - var postCtorMethods = input.Models.PostCtorMethods - .Where(m => m.TypeKey == type.TypeKey) - .ToImmutableArray(); + postCtorsByType.TryGetValue(type.TypeKey, out var postCtorMethods); var (source, parameters) = GenerateSource(context, type, postCtorMethods, baseParameters, input.Guards); @@ -80,12 +91,16 @@ public static void GenerateSource( context.AddSource($"{type.HintName}.g.cs", source); } + + // After the loop above, as ctorMaps holds the constructors AutoCtor generates. + foreach (var provider in input.Providers) + IoCEmitter.Generate(context, provider, ctorMaps, input.DuckTypes); } private static (SourceText?, ParameterList?) GenerateSource( EmitterContext context, TypeModel type, - ImmutableArray markedPostCtorMethods, + List? markedPostCtorMethods, IEnumerable? baseParameters, bool guards) { @@ -118,14 +133,13 @@ private static (SourceText?, ParameterList?) GenerateSource( using (source.StartPartialType(type)) { - source - .AddGeneratedAttributes(AttributeTargets.Method); + source.AddGeneratedCodeAttribute(); source.AppendIndent() - .Append($"public {type.Name}({parameters.CtorParameterDeclarations:commaindent})") - .Append(parameters.HasBaseParameters, - $" : base({parameters.BaseParameters:commaindent})") - .AppendLine(); + .Append($"public {type.Name}({parameters.CtorParameterDeclarations:commaindent})"); + if (parameters.HasBaseParameters) + source.Append($" : base({parameters.BaseParameters:commaindent})"); + source.AppendLine(); using (source.StartBlock()) { @@ -142,10 +156,10 @@ private static (SourceText?, ParameterList?) GenerateSource( && !item.IsNullableAnnotated; source.AppendIndent() - .Append($"{item.IdentifierName} = {parameter}") - .Append(addGuard, - $" ?? throw new global::System.ArgumentNullException(\"{parameter}\")") - .Append(";") + .Append($"{item.IdentifierName} = {parameter}"); + if (addGuard) + source.Append($" ?? throw new global::System.ArgumentNullException(\"{parameter}\")"); + source.Append(";") .AppendLine(); } if (postCtorMethod.HasValue) @@ -176,7 +190,7 @@ private static ITypeSymbol FindTypeForArgument( return type; } - private static ITypeSymbol SetGenerics( + internal static ITypeSymbol SetGenerics( ITypeSymbol type, EquatableList parameters, EquatableList arguments) @@ -204,10 +218,13 @@ private static ITypeSymbol SetGenerics( private static PostCtorModel? GetPostCtorMethod( EmitterContext context, TypeModel type, - ImmutableArray markedPostCtorMethods) + List? markedPostCtorMethods) { + if (markedPostCtorMethods is null) + return null; + // ACTR001 - if (markedPostCtorMethods.Length > 1) + if (markedPostCtorMethods.Count > 1) { foreach (var m in markedPostCtorMethods) { @@ -216,7 +233,7 @@ private static ITypeSymbol SetGenerics( return null; } - if (markedPostCtorMethods.Length != 1) + if (markedPostCtorMethods.Count != 1) return null; var method = markedPostCtorMethods[0]; diff --git a/src/Shared/AutoConstructSourceGenerator/IoCEmitter.cs b/src/Shared/AutoConstructSourceGenerator/IoCEmitter.cs new file mode 100644 index 00000000..9beaeaa5 --- /dev/null +++ b/src/Shared/AutoConstructSourceGenerator/IoCEmitter.cs @@ -0,0 +1,542 @@ +using Microsoft.CodeAnalysis; +using static AutoCtor.Diagnostics; + +#if ROSLYN_3 +using EmitterContext = Microsoft.CodeAnalysis.GeneratorExecutionContext; +#elif ROSLYN_4 +using EmitterContext = Microsoft.CodeAnalysis.SourceProductionContext; +#endif + +namespace AutoCtor; + +public partial class AutoConstructSourceGenerator +{ + private static class IoCEmitter + { + private const string DI = "global::Microsoft.Extensions.DependencyInjection."; + + private sealed class Node(int id, int order, RegistrationModel registration, ITypeSymbol service, INamedTypeSymbol implementation) + { + public string Name { get; } = $"S{id}"; + // Registration order, which decides the winner of a single resolve. + public int Order { get; } = order; + public RegistrationModel R { get; } = registration; + public string Service { get; } = service.ToDisplayString(FullyQualifiedFormat); + public INamedTypeSymbol Implementation { get; } = implementation; + public List Dependencies { get; } = []; + public string Create { get; set; } = ""; + public bool Scoped { get; set; } = registration.Lifetime == Lifetime.Scoped; + public int VisitState { get; set; } + } + + public static void Generate( + EmitterContext context, + ServiceProviderModel provider, + Dictionary ctorMaps, + DuckTypes duck) + { + if (ReportDiagnostics(context, provider.Diagnostics)) + return; + + if (provider.Registrations.Count == 0) + return; + + List builtIns = ["global::System.IServiceProvider"]; + if (duck.DI) + { + builtIns.Add(DI + "IServiceScopeFactory"); + builtIns.Add(DI + "IServiceProviderIsService"); + } + if (duck.Keyed) + { + builtIns.Add(DI + "IKeyedServiceProvider"); + builtIns.Add(DI + "IServiceProviderIsKeyedService"); + } + + var nodes = CreateNodes(context, provider, ctorMaps, duck, builtIns, out var failed); + + foreach (var node in nodes) + failed |= !VerifyNodes(context, node); + + if (failed) + return; + + // Registrations first, so they win over collections and built ins of the same type. + var singles = new List<(string Type, string Key, string Value, bool Scoped)>(); + var collections = new List<(string Type, string Key, string Value, bool Scoped)>(); + + foreach (var group in nodes.GroupBy(n => (n.Service, n.R.Key))) + { + var ordered = group.OrderBy(n => n.Order).ToList(); + var last = ordered[ordered.Count - 1]; + singles.Add((group.Key.Service, group.Key.Key ?? "null", $"{last.Name}()", last.Scoped)); + collections.Add(($"global::System.Collections.Generic.IEnumerable<{group.Key.Service}>", group.Key.Key ?? "null", + NewArray(group.Key.Service, ordered), ordered.Any(n => n.Scoped))); + } + + if (duck.Keyed) + { + // KeyedService.AnyKey asks for every keyed registration of a service. + foreach (var group in nodes.Where(n => n.R.Key is not null).GroupBy(n => n.Service)) + { + var ordered = group.OrderBy(n => n.Order).ToList(); + collections.Add(($"global::System.Collections.Generic.IEnumerable<{group.Key}>", DI + "KeyedService.AnyKey", + NewArray(group.Key, ordered), ordered.Any(n => n.Scoped))); + } + } + + var entries = singles.Concat(collections).Concat(builtIns.Select(b => (b, "null", "this", false))).ToList(); + + var source = new CodeBuilder() + .AppendHeader() + .AppendLine() + .AppendLine("#nullable enable") + .AppendLine(); + + List baseTypes = [ + .. builtIns, + "global::System.IDisposable" + ]; + if (duck.Async) + baseTypes.Add("global::System.IAsyncDisposable"); + + using (source.StartPartialType(provider, [ + .. baseTypes, + .. Resolvers(entries, false).Select(t => $"{provider.Name}.IResolver<{t}>")])) + { + if (duck.DI) + baseTypes.Add(DI + "IServiceScope"); + + EmitContainer(source, provider, nodes, entries, duck, inScope: false); + source.AppendLine(); + source.AddGeneratedCodeAttribute(); + using (source.StartType("public sealed class Scope", [ + .. baseTypes, + .. Resolvers(entries, true).Select(t => $"{provider.Name}.IResolver<{t}>")])) + EmitContainer(source, provider, nodes, entries, duck, inScope: true); + } + + context.AddSource($"{provider.HintName}.ServiceProvider.g.cs", source); + } + + private static bool ReportDiagnostics(EmitterContext context, IEnumerable diagnostics) + { + var hasError = false; + foreach (var diagnostic in diagnostics) + { + context.ReportDiagnostic(diagnostic, diagnostic.Descriptor, [.. diagnostic.Args]); + hasError = hasError || diagnostic.Descriptor.DefaultSeverity == DiagnosticSeverity.Error; + } + return hasError; + } + + // The first registration of each unkeyed type wins, the same as in Resolve. + private static IEnumerable Resolvers(List<(string Type, string Key, string Value, bool Scoped)> entries, bool inScope) + => entries.Where(e => e.Key == "null" && (inScope || !e.Scoped)).Select(e => e.Type).Distinct(); + + // Every registration of a service, closing any open generic rule that matches it. + // A rule whose constraints the type arguments break is skipped, and named in unsatisfied. + private static List Find( + ServiceProviderModel provider, + List nodes, + ITypeSymbol type, + string? key, + out RegistrationModel? unsatisfied) + { + var registrations = provider.Registrations; + unsatisfied = null; + var name = type.ToDisplayString(FullyQualifiedFormat); + if (type is INamedTypeSymbol { IsGenericType: true } closed) + { + var definition = closed.OriginalDefinition.ToDisplayString(FullyQualifiedFormat); + for (var i = 0; i < registrations.Count; i++) + { + var r = registrations[i]; + if (r.IsOpenGeneric && r.Key == key + && r.Service.ToString() == definition + && r.Implementation.TypeSymbol is INamedTypeSymbol open + && open.Arity == closed.Arity + && !nodes.Any(n => n.Order == i && n.Service == name)) + { + if (!SatisfiesConstraints(open, closed.TypeArguments)) + { + unsatisfied = r; + continue; + } + nodes.Add(new(nodes.Count, i, r, closed, open.Construct([.. closed.TypeArguments]))); + } + } + } + return nodes.Where(n => n.Service == name && n.R.Key == key).OrderBy(n => n.Order).ToList(); + } + + private static List CreateNodes( + EmitterContext context, + ServiceProviderModel provider, + Dictionary ctorMaps, + DuckTypes duck, + List builtIns, + out bool failed) + { + failed = false; + + var nodes = new List(); + + for (var i = 0; i < provider.Registrations.Count; i++) + { + var r = provider.Registrations[i]; + if (!r.IsOpenGeneric) + nodes.Add(new( + id: nodes.Count, + order: i, + registration: r, + service: r.Service.TypeSymbol, + implementation: (INamedTypeSymbol)r.Implementation.TypeSymbol)); + } + + // Closing an open generic adds a node, so this walks the list as it grows. + for (var i = 0; i < nodes.Count; i++) + { + var node = nodes[i]; + IEnumerable parameters = node.R.Parameters; + if (node.R.Factory is null) + { + if (node.R.IsAutoConstruct + && ctorMaps.TryGetValue(TypeModel.CreateKey(node.Implementation), out var predicted)) + { + // The constructor AutoCtor is about to generate. + parameters = predicted.Distinct(); + } + else if (node.R.PublicConstructorCount != 1) + { + context.ReportDiagnostic(node.R, + ACTR010_ServiceImplementationMustHaveSinglePublicConstructor); + failed = true; + continue; + } + } + + var typeParameters = node.Implementation.OriginalDefinition.TypeParameters + .Select(ConvertToEquatable) + .ToEquatableList(); + var typeArguments = node.Implementation.TypeArguments + .Select(ConvertToEquatable) + .ToEquatableList(); + var argList = parameters + .Select(p => Argument( + context, + provider, + duck, + builtIns, + nodes, + node, + type: Emitter.SetGenerics(p.Type.TypeSymbol, typeParameters, typeArguments), + p.KeyedService, + p.ErrorName)) + .ToList(); + + if (argList.Contains(null)) + failed = true; + + var args = string.Join(", ", argList); + + node.Create = node.R.Factory is { } factory + ? node.R.FactoryIsMethod ? $"{factory}({args})" : factory + : $"new {node.Implementation.ToDisplayString(FullyQualifiedFormat)}({args})"; + } + + return nodes; + } + + // The expression that resolves one constructor argument, or null after reporting why it can't. + private static string? Argument( + EmitterContext context, + ServiceProviderModel provider, + DuckTypes duck, + List builtIns, + List nodes, + Node node, + ITypeSymbol type, + string? key, + string parameterName) + { + var name = type.ToDisplayString(FullyQualifiedFormat); + var typeName = type.ToDisplayString(MinimallyQualifiedFormat); + var found = Find(provider, nodes, type, key, out var unsatisfied); + if (found.Count > 0) + { + node.Dependencies.Add(found[found.Count - 1]); + return $"{found[found.Count - 1].Name}()"; + } + if (unsatisfied is { } rule) + { + context.ReportDiagnostic(rule, ACTR020_OpenGenericConstraintNotSatisfied, typeName, parameterName, rule.ErrorName); + return null; + } + + if (type is INamedTypeSymbol { ConstructedFrom.SpecialType: SpecialType.System_Collections_Generic_IEnumerable_T } enumerable) + { + var elements = Find(provider, nodes, enumerable.TypeArguments[0], key, out _); + node.Dependencies.AddRange(elements); + return NewArray(enumerable.TypeArguments[0].ToDisplayString(FullyQualifiedFormat), elements); + } + + if (key is null && builtIns.Contains(name)) + return "this"; + + if (provider.Fallback is not null && (key is null || duck.Keyed)) + return $"Required<{name}>(GetKeyedService(typeof({name}), {key ?? "null"}))"; + + if (key is null) + context.ReportDiagnostic(node.R, ACTR012_ServiceDependencyNotRegistered, typeName, node.R.ErrorName); + else + context.ReportDiagnostic(node.R, ACTR014_KeyedServiceDependencyNotRegistered, typeName, node.R.ErrorName, key); + return null; + } + + // Cycles, and which services can only live in a scope. False when an error was reported. + private static bool VerifyNodes(EmitterContext context, Node node) + { + if (node.VisitState == 2) + return true; + if (node.VisitState == 1) + { + context.ReportDiagnostic(node.R, ACTR013_CircularServiceDependency); + return false; + } + + var ok = true; + node.VisitState = 1; + foreach (var dependency in node.Dependencies) + { + ok &= VerifyNodes(context, dependency); + node.Scoped |= dependency.Scoped; + if (node.R.Lifetime != Lifetime.Singleton) + continue; + + if (dependency.Scoped) + { + context.ReportDiagnostic(node.R, ACTR019_ScopedServiceCapturedBySingleton, dependency.R.ErrorName, node.R.ErrorName); + ok = false; + } + else if (dependency.R.Lifetime == Lifetime.Transient) + { + context.ReportDiagnostic(node.R, ACTR018_TransientServiceCaptured, dependency.R.ErrorName, node.R.ErrorName); + } + } + node.VisitState = 2; + return ok; + } + + private static bool SatisfiesConstraints(INamedTypeSymbol definition, IReadOnlyList arguments) + { + var typeParameters = definition.TypeParameters + .Select(ConvertToEquatable) + .ToEquatableList(); + var typeArguments = arguments + .Select(ConvertToEquatable) + .ToEquatableList(); + + return definition.TypeParameters.Zip(arguments, (p, a) => + (!p.HasReferenceTypeConstraint || a.IsReferenceType) + && (!p.HasValueTypeConstraint || a.IsValueType) + && (!p.HasUnmanagedTypeConstraint || a.IsUnmanagedType) + && (!p.HasConstructorConstraint || a.IsValueType + || a is INamedTypeSymbol { IsAbstract: false } named && named.InstanceConstructors.Any(c => c.Parameters.Length == 0 && c.DeclaredAccessibility == Accessibility.Public)) + && p.ConstraintTypes.All(c => ServiceProviderModel.IsAssignable(a, Emitter.SetGenerics(c, typeParameters, typeArguments)))) + .All(ok => ok); + } + + private static string NewArray(string elementType, List elements) + => $"new {elementType}[] {{ {string.Join(", ", elements.Select(static n => $"{n.Name}()"))} }}"; + + private static void EmitContainer( + CodeBuilder source, + ServiceProviderModel provider, + List nodes, + List<(string Type, string Key, string Value, bool Scoped)> entries, + DuckTypes duck, + bool inScope) + { + var root = inScope ? "_root" : "this"; + + source.AppendLine("private readonly object _lock = new object();"); + source.AppendLine("private global::System.Collections.Generic.List? _disposables;"); + if (inScope) + { + source.AppendLine($"private readonly {provider.Name} _root;"); + source.AppendLine($"public Scope({provider.Name} root) => _root = root;"); + source.AppendLine("public global::System.IServiceProvider ServiceProvider => this;"); + } + source.AppendLine(); + + foreach (var node in nodes) + { + if (node.Scoped && !inScope) + continue; + + var (name, type, create) = (node.Name, node.Service, node.Create.Replace("{root}", root)); + var tracked = node.R.ImplementationIsDisposable ? $"Track<{type}>({create})" : create; + if (inScope && node.R.Lifetime == Lifetime.Singleton) + { + source.AppendLine($"private {type} {name}() => _root.{name}();"); + } + else if (node.R.Lifetime == Lifetime.Transient) + { + source.AppendLine($"private {type} {name}() => {tracked};"); + } + else + { + // Only the first resolve takes the lock. volatile needs a reference type, so a + // value type is held boxed. + var (field, cast) = node.R.ServiceIsReferenceType + ? ($"{type}?", "") + : ("object?", $"({type})"); + source.AppendLine($"private volatile {field} _{name};"); + using (source.StartBlock($"private {type} {name}()")) + { + source.AppendLine($"var service = _{name};"); + source.AppendLine("if (service is not null)") + .IncreaseIndent() + .AppendLine($"return {cast}service;") + .DecreaseIndent(); + source.AppendLine("lock (_lock)") + .IncreaseIndent() + .AppendLine($"return {cast}(_{name} ??= {tracked});") + .DecreaseIndent(); + } + } + } + source.AppendLine(); + + var visible = entries.Where(e => inScope || !e.Scoped).ToList(); + using (source.StartBlock("private object? Resolve(global::System.Type type, object? key, bool probe)")) + { + using (source.StartBlock("if (key is null)")) + { + foreach (var (type, _, value, _) in visible.Where(e => e.Key == "null")) + source.AppendLine($"if (type == typeof({type}))") + .IncreaseIndent() + .AppendLine($"return probe ? this : (object)({value});") + .DecreaseIndent(); + source.AppendLine("return null;"); + } + foreach (var (type, key, value, _) in visible.Where(e => e.Key != "null")) + source.AppendLine($"if (type == typeof({type}) && object.Equals(key, {key}))") + .IncreaseIndent() + .AppendLine($"return probe ? this : (object)({value});") + .DecreaseIndent(); + source.AppendLine("return null;"); + } + source.AppendLine(); + + // A scope gets its own scope of the fallback, so the fallback's scoped services work. + var fallback = provider.Fallback?.Replace("{root}", root) ?? "null"; + if (inScope && duck.DI && provider.Fallback is not null) + { + source.AppendLine("private global::System.IServiceProvider? _fallback;"); + source.AppendLine($"private global::System.IServiceProvider? Fallback {{ get {{ lock (_lock) return _fallback ??= {fallback} is {DI}IServiceScopeFactory f ? Track(f.CreateScope()).ServiceProvider : {fallback}; }} }}"); + } + else + { + source.AppendLine($"private global::System.IServiceProvider? Fallback => {fallback};"); + } + + var keyedFallback = duck.Keyed ? $"(Fallback as {DI}IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)" : "null"; + var fallbackProbe = !duck.DI ? "" + : $" || (serviceKey == null ? Fallback is {DI}IServiceProviderIsService s && s.IsService(serviceType) : " + + (duck.Keyed ? $"Fallback is {DI}IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey))" : "false)"); + + source.AppendLine() + .AppendLine("public object? GetService(global::System.Type serviceType)") + .AppendLine("\t=> GetKeyedService(serviceType, null);"); + + source.AppendLine() + .AppendLine("public object? GetKeyedService(global::System.Type serviceType, object? serviceKey)") + .AppendLine("\t=> Resolve(serviceType, serviceKey, false)") + .AppendLine($"\t?? (serviceKey == null ? Fallback?.GetService(serviceType) : {keyedFallback});"); + + source.AppendLine() + .AppendLine("public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey)") + .AppendLine("\t=> GetKeyedService(serviceType, serviceKey)") + .AppendLine("\t?? throw new global::System.InvalidOperationException(\"No service for type '\" + serviceType + \"' has been registered.\");"); + + // Typed lookups: an interface check instead of a chain of type comparisons. + source.AppendLine() + .AppendLine("public T? GetService()") + .AppendLine($"\t=> this is {provider.Name}.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default;"); + + source.AppendLine() + .AppendLine("public T GetRequiredService()") + .AppendLine($"\t=> this is {provider.Name}.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null);"); + + source.AppendLine(); + if (!inScope) + source.AppendLine("private interface IResolver { T Get(); }"); + foreach (var type in Resolvers(entries, inScope)) + source + .AppendLine($"{type} {provider.Name}.IResolver<{type}>.Get()") + .AppendLine($"\t=> {visible.First(e => e.Type == type && e.Key == "null").Value};"); + + source.AppendLine() + .AppendLine("public bool IsService(global::System.Type serviceType)") + .AppendLine("\t=> IsKeyedService(serviceType, null);"); + + source.AppendLine() + .AppendLine($"public bool IsKeyedService(global::System.Type serviceType, object? serviceKey)") + .AppendLine($"\t=> Resolve(serviceType, serviceKey, true) != null{fallbackProbe};"); + + source.AppendLine(); + source.AppendLine($"public Scope CreateScope() => new Scope({root});"); + if (duck.DI) + source.AppendLine($"{DI}IServiceScope {DI}IServiceScopeFactory.CreateScope() => CreateScope();"); + + source.AppendLine() + .AppendLine("private static T Required(object? service)") + .AppendLine("\t=> service is T t ? t : throw new global::System.InvalidOperationException(\"No service for type '\" + typeof(T) + \"' has been registered.\");"); + + source.AppendLine(); + using (source.StartBlock("private T Track(T service)")) + { + source.AppendLine($"if (service is global::System.IDisposable{(duck.Async ? " || service is global::System.IAsyncDisposable" : "")})"); + source.AppendLine("\tlock (_lock)"); + source.AppendLine("\t\t(_disposables ??= new global::System.Collections.Generic.List()).Add(service);"); + source.AppendLine("return service;"); + } + + // Disposed in reverse order of creation, and only once. + source.AppendLine(); + using (source.StartBlock("private object[] Drain()")) + using (source.StartBlock("lock (_lock)")) + { + source.AppendLine("var items = _disposables?.ToArray() ?? new object[0];"); + source.AppendLine("_disposables = null;"); + source.AppendLine("global::System.Array.Reverse(items);"); + source.AppendLine("return items;"); + } + + source.AppendLine(); + using (source.StartBlock("public void Dispose()")) + using (source.StartBlock("foreach (var item in Drain())")) + using (source.StartBlock("if (item is global::System.IDisposable disposable)")) + { + source.AppendLine("disposable.Dispose();"); + } + + if (duck.Async) + { + source.AppendLine(); + using (source.StartBlock("public async global::System.Threading.Tasks.ValueTask DisposeAsync()")) + using (source.StartBlock("foreach (var item in Drain())")) + { + source.AppendLine("if (item is global::System.IAsyncDisposable d)"); + source.AppendLine("\tawait d.DisposeAsync().ConfigureAwait(false);"); + source.AppendLine("else"); + source.AppendLine("\t((global::System.IDisposable)item).Dispose();"); + } + } + } + + private static EquatableTypeSymbol ConvertToEquatable(ITypeSymbol typeSymbol) => new(typeSymbol); + } +} diff --git a/src/Shared/AutoConstructSourceGenerator/Tracking.cs b/src/Shared/AutoConstructSourceGenerator/Tracking.cs index 554f1e21..4dc6f3ea 100644 --- a/src/Shared/AutoConstructSourceGenerator/Tracking.cs +++ b/src/Shared/AutoConstructSourceGenerator/Tracking.cs @@ -10,11 +10,13 @@ public static class TrackingNames public static string BuildProperties => nameof(BuildProperties); public static string TypeModels => nameof(TypeModels); public static string PostCtorMethods => nameof(PostCtorMethods); + public static string ServiceProviders => nameof(ServiceProviders); public static IReadOnlyCollection AllTrackers { get; } = [ BuildProperties, TypeModels, PostCtorMethods, + ServiceProviders, ]; } } diff --git a/src/Shared/Constants/AttributeNames.cs b/src/Shared/Constants/AttributeNames.cs index b7aef2c5..5de3c6d4 100644 --- a/src/Shared/Constants/AttributeNames.cs +++ b/src/Shared/Constants/AttributeNames.cs @@ -4,4 +4,5 @@ public const string AutoPostConstruct = "AutoCtor.AutoPostConstructAttribute"; public const string AutoConstructIgnore = "AutoCtor.AutoConstructIgnoreAttribute"; public const string AutoKeyedService = "AutoCtor.AutoKeyedServiceAttribute"; + public const string ServiceProvider = "AutoCtor.ServiceProviderAttribute"; } diff --git a/src/Shared/Constants/Diagnostics.cs b/src/Shared/Constants/Diagnostics.cs index 73324787..24bf9864 100644 --- a/src/Shared/Constants/Diagnostics.cs +++ b/src/Shared/Constants/Diagnostics.cs @@ -112,4 +112,256 @@ internal static class Diagnostics category: "AutoCtor", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + /// + /// Id: ACTR010
+ /// Title: Service implementation must have a single public constructor + ///
+ public static readonly DiagnosticDescriptor ACTR010_ServiceImplementationMustHaveSinglePublicConstructor = new DiagnosticDescriptor( + id: "ACTR010", + title: "Service implementation must have a single public constructor", + messageFormat: "The implementation type '{0}' must have exactly one public constructor to be used as a service", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR012
+ /// Title: Service dependency is not registered + ///
+ public static readonly DiagnosticDescriptor ACTR012_ServiceDependencyNotRegistered = new DiagnosticDescriptor( + id: "ACTR012", + title: "Service dependency is not registered", + messageFormat: "No registration was found for the dependency '{0}' of '{1}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR013
+ /// Title: Circular dependency between registered services + ///
+ public static readonly DiagnosticDescriptor ACTR013_CircularServiceDependency = new DiagnosticDescriptor( + id: "ACTR013", + title: "Circular dependency between registered services", + messageFormat: "A circular dependency was detected involving '{0}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR014
+ /// Title: Keyed service dependency is not registered + ///
+ public static readonly DiagnosticDescriptor ACTR014_KeyedServiceDependencyNotRegistered = new DiagnosticDescriptor( + id: "ACTR014", + title: "Keyed service dependency is not registered", + messageFormat: "No registration with key {2} was found for the dependency '{0}' of '{1}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR018
+ /// Title: Transient service is captured by a singleton + ///
+ public static readonly DiagnosticDescriptor ACTR018_TransientServiceCaptured = new DiagnosticDescriptor( + id: "ACTR018", + title: "Transient service is captured by a singleton", + messageFormat: "The transient service '{0}' is captured by the singleton '{1}' and will not be recreated for each resolve", + category: "AutoCtor", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + /// + /// Id: ACTR019
+ /// Title: Scoped service cannot be resolved outside a scope + ///
+ public static readonly DiagnosticDescriptor ACTR019_ScopedServiceCapturedBySingleton = new DiagnosticDescriptor( + id: "ACTR019", + title: "Scoped service cannot be resolved outside a scope", + messageFormat: "The service '{0}' requires a scope and cannot be injected into the singleton '{1}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR026
+ /// Title: Service scan found no types + ///
+ public static readonly DiagnosticDescriptor ACTR026_ScanFoundNoTypes = new DiagnosticDescriptor( + id: "ACTR026", + title: "Service scan found no types", + messageFormat: "The scan for '{0}' found no accessible, concrete, non-generic class to register", + category: "AutoCtor", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + /// + /// Id: ACTR027
+ /// Title: Imported module has no registrations + ///
+ public static readonly DiagnosticDescriptor ACTR027_ImportedModuleHasNoRegistrations = new DiagnosticDescriptor( + id: "ACTR027", + title: "Imported module has no registrations", + messageFormat: "The module '{0}' has no registrations; a module in another assembly needs that assembly to define AUTOCTOR_USAGES", + category: "AutoCtor", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + /// + /// Id: ACTR011
+ /// Title: Service implementation cannot be instantiated + ///
+ public static readonly DiagnosticDescriptor ACTR011_ServiceImplementationCannotBeInstantiated = new DiagnosticDescriptor( + id: "ACTR011", + title: "Service implementation cannot be instantiated", + messageFormat: "The implementation type '{0}' cannot be instantiated; it must be a non-abstract, non-generic class", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR015
+ /// Title: Implementation type is not assignable to the service type + ///
+ public static readonly DiagnosticDescriptor ACTR015_ImplementationNotAssignableToService = new DiagnosticDescriptor( + id: "ACTR015", + title: "Implementation type is not assignable to the service type", + messageFormat: "The implementation type '{0}' is not assignable to the service type '{1}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR016
+ /// Title: Open generic registration is not valid + ///
+ public static readonly DiagnosticDescriptor ACTR016_InvalidOpenGenericRegistration = new DiagnosticDescriptor( + id: "ACTR016", + title: "Open generic registration is not valid", + messageFormat: "The open generic service '{0}' cannot be registered with '{1}'; the implementation must be an open generic type with the same number of type parameters", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR017
+ /// Title: Service provider type is not valid + ///
+ public static readonly DiagnosticDescriptor ACTR017_InvalidServiceProviderType = new DiagnosticDescriptor( + id: "ACTR017", + title: "Service provider type is not valid", + messageFormat: "The type '{0}' cannot be a service provider; it must not be generic, and must not be marked with [AutoConstruct]", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR020
+ /// Title: Type arguments do not satisfy the open generic implementation + ///
+ public static readonly DiagnosticDescriptor ACTR020_OpenGenericConstraintNotSatisfied = new DiagnosticDescriptor( + id: "ACTR020", + title: "Type arguments do not satisfy the open generic implementation", + messageFormat: "'{0}' is needed by '{1}', but its type arguments do not satisfy the constraints of '{2}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR021
+ /// Title: Service factory member is not valid + ///
+ public static readonly DiagnosticDescriptor ACTR021_InvalidServiceFactory = new DiagnosticDescriptor( + id: "ACTR021", + title: "Service factory member is not valid", + messageFormat: "The factory '{0}' cannot be used for the service '{1}'; it must name an accessible, non-generic method, property or field declared on the service provider that returns a value", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR022
+ /// Title: Service factory does not return the service type + ///
+ public static readonly DiagnosticDescriptor ACTR022_ServiceFactoryReturnTypeNotAssignable = new DiagnosticDescriptor( + id: "ACTR022", + title: "Service factory does not return the service type", + messageFormat: "The factory '{0}' returns '{1}', which is not assignable to the service type '{2}'", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR023
+ /// Title: Open generic registration cannot use a factory + ///
+ public static readonly DiagnosticDescriptor ACTR023_OpenGenericServiceFactory = new DiagnosticDescriptor( + id: "ACTR023", + title: "Open generic registration cannot use a factory", + messageFormat: "The open generic service '{0}' cannot use the factory '{1}'; a factory builds one type, and an open registration is closed on demand", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR024
+ /// Title: Service provider fallback member is not valid + ///
+ public static readonly DiagnosticDescriptor ACTR024_InvalidProviderFallback = new DiagnosticDescriptor( + id: "ACTR024", + title: "Service provider fallback member is not valid", + messageFormat: "The fallback '{0}' cannot be used by '{1}'; it must name an accessible, non-generic field, property or parameterless method declared on the service provider that returns a value", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR025
+ /// Title: Service provider fallback is not a service provider + ///
+ public static readonly DiagnosticDescriptor ACTR025_ProviderFallbackNotAServiceProvider = new DiagnosticDescriptor( + id: "ACTR025", + title: "Service provider fallback is not a service provider", + messageFormat: "The fallback '{0}' returns '{1}', which is not assignable to System.IServiceProvider", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR028
+ /// Title: Module factory must be static + ///
+ public static readonly DiagnosticDescriptor ACTR028_ModuleFactoryMustBeStatic = new DiagnosticDescriptor( + id: "ACTR028", + title: "Module factory must be static", + messageFormat: "The factory '{0}' on the module '{1}' must be static; a module is never instantiated", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR029
+ /// Title: Service provider type must be sealed + ///
+ public static readonly DiagnosticDescriptor ACTR029_ServiceProviderMustBeSealed = new DiagnosticDescriptor( + id: "ACTR029", + title: "Service provider type must be sealed", + messageFormat: "The type '{0}' must be sealed to be a service provider", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); + + /// + /// Id: ACTR030
+ /// Title: Service scan has no filter + ///
+ public static readonly DiagnosticDescriptor ACTR030_ScanHasNoFilter = new DiagnosticDescriptor( + id: "ACTR030", + title: "Service scan has no filter", + messageFormat: "'{0}' needs a service or a TypeNameFilter", + category: "AutoCtor", + DiagnosticSeverity.Error, + isEnabledByDefault: true); } diff --git a/src/Shared/Helpers/CodeBuilder.Block.cs b/src/Shared/Helpers/CodeBuilder.Block.cs index 2d52efc8..6cb16155 100644 --- a/src/Shared/Helpers/CodeBuilder.Block.cs +++ b/src/Shared/Helpers/CodeBuilder.Block.cs @@ -1,27 +1,30 @@ internal partial class CodeBuilder { - public IDisposable StartBlock() => StartIndent("{", "}"); + public IDisposable StartBlock() + { + AppendLine("{").IncreaseIndent(); + return new SingleBlockDisposable(this); + } + public IDisposable StartBlock(string line) { AppendLine(line); - return StartIndent("{", "}"); + return StartBlock(); } - public IDisposable StartIndent(string? startLine = null, string? endLine = null) + public IDisposable StartType(string typeDeclaration, IEnumerable? baseList = null) { - if (!string.IsNullOrEmpty(startLine)) - AppendLine(startLine!); + if (baseList is { }) + AppendLine($"{typeDeclaration} : {baseList:commaindent}"); + else + AppendLine(typeDeclaration); + AppendLine("{"); IncreaseIndent(); - return new DetentDisposable(this, endLine); + return new SingleBlockDisposable(this); } - private readonly struct DetentDisposable(CodeBuilder codeBuilder, string? endLine) : IDisposable + private readonly struct SingleBlockDisposable(CodeBuilder codeBuilder) : IDisposable { - public void Dispose() - { - codeBuilder.DecreaseIndent(); - if (!string.IsNullOrEmpty(endLine)) - codeBuilder.AppendLine(endLine!); - } + public void Dispose() => codeBuilder.DecreaseIndent().AppendLine("}"); } } diff --git a/src/Shared/Helpers/CodeBuilder.InterpolatedStringHandler.cs b/src/Shared/Helpers/CodeBuilder.InterpolatedStringHandler.cs index b0875840..a31ce596 100644 --- a/src/Shared/Helpers/CodeBuilder.InterpolatedStringHandler.cs +++ b/src/Shared/Helpers/CodeBuilder.InterpolatedStringHandler.cs @@ -9,36 +9,24 @@ public CodeBuilder Append( [InterpolatedStringHandlerArgument("")] ref CodeBuilderInterpolatedStringHandler builder) => this; - public CodeBuilder Append(bool enabled, - [InterpolatedStringHandlerArgument("", nameof(enabled))] - ref CodeBuilderInterpolatedStringHandler builder) => this; - public CodeBuilder AppendLineRaw( [InterpolatedStringHandlerArgument("")] ref CodeBuilderInterpolatedStringHandler builder) => AppendLine(); - public CodeBuilder AppendLineRaw(bool enabled, - [InterpolatedStringHandlerArgument("", nameof(enabled))] - ref CodeBuilderInterpolatedStringHandler builder) => enabled ? AppendLine() : this; - public CodeBuilder AppendLine( [InterpolatedStringHandlerArgument("")] IndentedCodeBuilderInterpolatedStringHandler builder) => AppendLine(); - public CodeBuilder AppendLine(bool enabled, - [InterpolatedStringHandlerArgument("", nameof(enabled))] - IndentedCodeBuilderInterpolatedStringHandler builder) => enabled ? AppendLine() : this; - private void AppendFormatted(IEnumerable items, string? format) { if (format == "comma") - AppendCommaSeparated(items.ToList()); + AppendCommaSeparated(items as IReadOnlyList ?? items.ToList()); else if (format == "commaindent") - AppendCommaIndented(items.ToList()); + AppendCommaIndented(items as IReadOnlyList ?? items.ToList()); } - private void AppendCommaSeparated(List items) + private void AppendCommaSeparated(IReadOnlyList items) { var comma = false; foreach (var item in items) @@ -50,7 +38,7 @@ private void AppendCommaSeparated(List items) } } - private void AppendCommaIndented(List items) + private void AppendCommaIndented(IReadOnlyList items) { var length = items.Sum(s => s.Length); if (length < 60) @@ -76,19 +64,18 @@ private void AppendCommaIndented(List items) [InterpolatedStringHandler] internal readonly struct CodeBuilderInterpolatedStringHandler( - int literalLength, int formattedCount, CodeBuilder codeBuilder, bool enabled = true) + int literalLength, int formattedCount, CodeBuilder codeBuilder) { - public readonly bool AppendLiteral(string s) - { if (enabled) codeBuilder.Append(s); return enabled; } - public readonly bool AppendFormatted(string s) - { if (enabled) codeBuilder.Append(s); return enabled; } - public readonly bool AppendFormatted(IEnumerable items, string? format) - { if (enabled) codeBuilder.AppendFormatted(items, format); return enabled; } + public readonly void AppendLiteral(string s) => codeBuilder.Append(s); + public readonly void AppendFormatted(bool s) => codeBuilder.Append(s); + public readonly void AppendFormatted(string s) => codeBuilder.Append(s); + public readonly void AppendFormatted(IEnumerable items, string? format) + => codeBuilder.AppendFormatted(items, format); } [InterpolatedStringHandler] internal sealed class IndentedCodeBuilderInterpolatedStringHandler( - int literalLength, int formattedCount, CodeBuilder codeBuilder, bool enabled = true) + int literalLength, int formattedCount, CodeBuilder codeBuilder) { private bool _hasIndented; @@ -102,11 +89,10 @@ private CodeBuilder EnsureIndent() return codeBuilder; } - public bool AppendLiteral(string s) - { if (enabled) EnsureIndent().Append(s); return enabled; } - public bool AppendFormatted(string s) - { if (enabled) EnsureIndent().Append(s); return enabled; } - public bool AppendFormatted(IEnumerable items, string? format) - { if (enabled) EnsureIndent().AppendFormatted(items, format); return enabled; } + public void AppendLiteral(string s) => EnsureIndent().Append(s); + public void AppendFormatted(bool s) => EnsureIndent().Append(s); + public void AppendFormatted(string s) => EnsureIndent().Append(s); + public void AppendFormatted(IEnumerable items, string? format) + => EnsureIndent().AppendFormatted(items, format); } } diff --git a/src/Shared/Helpers/CodeBuilder.PartialType.cs b/src/Shared/Helpers/CodeBuilder.PartialType.cs index b1c54e7a..821d2264 100644 --- a/src/Shared/Helpers/CodeBuilder.PartialType.cs +++ b/src/Shared/Helpers/CodeBuilder.PartialType.cs @@ -6,7 +6,7 @@ internal partial class CodeBuilder { - public IDisposable StartPartialType(IPartialTypeModel typeModel) + public IDisposable StartPartialType(IPartialTypeModel typeModel, IEnumerable? baseList = null) { if (!string.IsNullOrEmpty(typeModel.Namespace)) { @@ -17,7 +17,10 @@ public IDisposable StartPartialType(IPartialTypeModel typeModel) for (var i = 0; i < typeModel.TypeDeclarations.Count; i++) { - AppendLine(typeModel.TypeDeclarations[i]); + if (i == typeModel.TypeDeclarations.Count - 1 && baseList is { }) + AppendLine($"{typeModel.TypeDeclarations[i]} : {baseList:commaindent}"); + else + AppendLine(typeModel.TypeDeclarations[i]); AppendLine("{"); IncreaseIndent(); } diff --git a/src/Shared/Helpers/CodeBuilder.Templates.cs b/src/Shared/Helpers/CodeBuilder.Templates.cs index e437c7ca..ee3b1b70 100644 --- a/src/Shared/Helpers/CodeBuilder.Templates.cs +++ b/src/Shared/Helpers/CodeBuilder.Templates.cs @@ -26,51 +26,20 @@ static CodeBuilder() } } - // All - private void AddCompilerGeneratedAttribute() - => AppendLine("[global::System.Runtime.CompilerServices.CompilerGenerated]"); - - // All - private void AddGeneratedCodeAttribute() + public CodeBuilder AddGeneratedCodeAttribute() => AppendLine($"[global::System.CodeDom.Compiler.GeneratedCode(\"{s_assemblyName}\", \"{s_version}\")]"); - // Class | Struct | Constructor | Method | Property - private void AddDebuggerNonUserCodeAttribute() - => AppendLine("[global::System.Diagnostics.DebuggerNonUserCode]"); - - // Class | Struct | Constructor | Method | Property | Event - private void AddExcludeFromCodeCoverage() - => AppendLine($"[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]"); - - public CodeBuilder AddGeneratedAttributes(AttributeTargets attributeTargets) + public CodeBuilder AppendHeader() { - AddCompilerGeneratedAttribute(); - AddGeneratedCodeAttribute(); - - switch (attributeTargets) - { - case AttributeTargets.Class: - case AttributeTargets.Struct: - case AttributeTargets.Constructor: - case AttributeTargets.Method: - case AttributeTargets.Property: - AddDebuggerNonUserCodeAttribute(); - AddExcludeFromCodeCoverage(); - break; - case AttributeTargets.Event: - AddExcludeFromCodeCoverage(); - break; - } - + AppendLine($"//------------------------------------------------------------------------------"); + AppendLine($"// "); + if (!string.IsNullOrEmpty(s_packageProjectUrl)) + AppendLine($"// This code was generated by {s_packageProjectUrl!}"); + AppendLine($"// Version: {s_version}"); + if (!string.IsNullOrEmpty(s_gitSha)) + AppendLine($"// SHA: {s_gitSha!}"); + AppendLine($"// "); + AppendLine($"//------------------------------------------------------------------------------"); return this; } - - public CodeBuilder AppendHeader() => - AppendLine($"//------------------------------------------------------------------------------") - .AppendLine($"// ") - .AppendLine(!string.IsNullOrEmpty(s_packageProjectUrl), $"// This code was generated by {s_packageProjectUrl!}") - .AppendLine($"// Version: {s_version}") - .AppendLine(!string.IsNullOrEmpty(s_gitSha), $"// SHA: {s_gitSha!}") - .AppendLine($"// ") - .AppendLine($"//------------------------------------------------------------------------------"); } diff --git a/src/Shared/Helpers/CodeBuilder.cs b/src/Shared/Helpers/CodeBuilder.cs index 26eb665f..1742d329 100644 --- a/src/Shared/Helpers/CodeBuilder.cs +++ b/src/Shared/Helpers/CodeBuilder.cs @@ -6,21 +6,23 @@ internal partial class CodeBuilder private readonly StringBuilder _stringBuilder = new(); private int _indent; - public char IndentChar { get; set; } = '\t'; - public string Indent => new(IndentChar, _indent); + public char IndentChar = '\t'; + // Every emitted line asks for this, so the common tab indents are shared, not rebuilt. + private static readonly string[] s_tabIndents = [.. Enumerable.Range(0, 16).Select(static i => new string('\t', i))]; + public string Indent => _indent < s_tabIndents.Length + ? s_tabIndents[_indent] + : new(IndentChar, _indent); public CodeBuilder IncreaseIndent() { _indent++; return this; } public CodeBuilder DecreaseIndent() { if (_indent > 0) _indent--; return this; } + public CodeBuilder Append(bool value) { _stringBuilder.Append(value ? "true" : "false"); return this; } public CodeBuilder Append(string value) { _stringBuilder.Append(value); return this; } - public CodeBuilder Append(bool enabled, string value) => enabled ? Append(value) : this; public CodeBuilder AppendLine() { _stringBuilder.AppendLine(); return this; } public CodeBuilder AppendLine(string value) => AppendLineRaw(Indent + value); - public CodeBuilder AppendLine(bool enabled, string value) => enabled ? AppendLine(value) : this; public CodeBuilder AppendLineRaw(string value) { _stringBuilder.AppendLine(value); return this; } - public CodeBuilder AppendLineRaw(bool enabled, string value) => enabled ? AppendLineRaw(value) : this; public CodeBuilder AppendIndent() => Append(Indent); diff --git a/src/Shared/Helpers/EquatableTypeSymbol.cs b/src/Shared/Helpers/EquatableTypeSymbol.cs index ed2cb6ea..976fafde 100644 --- a/src/Shared/Helpers/EquatableTypeSymbol.cs +++ b/src/Shared/Helpers/EquatableTypeSymbol.cs @@ -1,8 +1,12 @@ -using Microsoft.CodeAnalysis; +using System.Runtime.CompilerServices; +using Microsoft.CodeAnalysis; internal readonly record struct EquatableTypeSymbol(ITypeSymbol TypeSymbol) { - private readonly string _fullyQualifiedString = TypeSymbol.ToDisplayString(FullyQualifiedFormat); + private static readonly ConditionalWeakTable s_displayStrings = new(); + + private readonly string _fullyQualifiedString = s_displayStrings.GetValue( + TypeSymbol, static t => t.ToDisplayString(FullyQualifiedFormat)); public override int GetHashCode() => ToString().GetHashCode(); public bool Equals(EquatableTypeSymbol other) => EqualityComparer.Default.Equals(ToString(), other.ToString()); diff --git a/src/Shared/Helpers/Extensions.cs b/src/Shared/Helpers/Extensions.cs index c3b093ce..8c2acd8f 100644 --- a/src/Shared/Helpers/Extensions.cs +++ b/src/Shared/Helpers/Extensions.cs @@ -9,6 +9,11 @@ internal static class Extensions { + public static EquatableList ToEquatableList(this IEnumerable source) + { + return new(source); + } + public static T? OnlyOrDefault(this IEnumerable source) { if (source is IList list) @@ -46,9 +51,9 @@ internal static class Extensions "MicrosoftCodeAnalysisCorrectness", "RS1035:Do not use APIs banned for analyzers", Justification = "Old generator still maintained")] - public static void ReportDiagnostic(this EmitterContext context, IHaveDiagnostics item, DiagnosticDescriptor diagnostic) + public static void ReportDiagnostic(this EmitterContext context, IHaveDiagnostics item, DiagnosticDescriptor diagnostic, params object?[] messageArgs) { foreach (var loc in item.Locations) - context.ReportDiagnostic(Diagnostic.Create(diagnostic, loc, item.ErrorName)); + context.ReportDiagnostic(Diagnostic.Create(diagnostic, loc, messageArgs.Length == 0 ? [item.ErrorName] : messageArgs)); } } diff --git a/src/Shared/Helpers/GeneratorUtilities.cs b/src/Shared/Helpers/GeneratorUtilities.cs index affe8236..9cae8a76 100644 --- a/src/Shared/Helpers/GeneratorUtilities.cs +++ b/src/Shared/Helpers/GeneratorUtilities.cs @@ -71,4 +71,15 @@ public static bool IsMethodDeclarationWithAttributes(SyntaxNode node, Cancellati public static TSymbol? GetSymbol(GeneratorSyntaxContext context, CancellationToken cancellationToken) where TSymbol : class, ISymbol => context.SemanticModel.GetDeclaredSymbol(context.Node, cancellationToken) as TSymbol; + + // Roslyn 3.11 and 4.0 visit every declaration of a partial type that has attributes, so + // only answer for the first of those. + public static TSymbol? GetPrimarySymbol(GeneratorSyntaxContext context, CancellationToken cancellationToken) where TSymbol : class, ISymbol + { + var symbol = GetSymbol(context, cancellationToken); + var primary = symbol?.DeclaringSyntaxReferences + .Select(r => r.GetSyntax(cancellationToken)) + .FirstOrDefault(n => IsTypeDeclarationWithAttributes(n, cancellationToken)); + return primary is null || (primary.SyntaxTree == context.Node.SyntaxTree && primary.Span == context.Node.Span) ? symbol : null; + } } diff --git a/src/Shared/Models/DuckTypes.cs b/src/Shared/Models/DuckTypes.cs new file mode 100644 index 00000000..0c8eb5a5 --- /dev/null +++ b/src/Shared/Models/DuckTypes.cs @@ -0,0 +1,10 @@ +using Microsoft.CodeAnalysis; + +internal readonly record struct DuckTypes(bool DI, bool Keyed, bool Async) +{ + public static DuckTypes Create(Compilation c) => new( + c.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.IServiceProviderIsService") is not null, + c.GetTypeByMetadataName("Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider") is not null, + c.GetTypeByMetadataName("System.IAsyncDisposable") is not null + && c.GetTypeByMetadataName("System.Threading.Tasks.ValueTask") is not null); +} diff --git a/src/Shared/Models/ParameterList.cs b/src/Shared/Models/ParameterList.cs index 82e667a8..72c62bff 100644 --- a/src/Shared/Models/ParameterList.cs +++ b/src/Shared/Models/ParameterList.cs @@ -7,7 +7,7 @@ using EmitterContext = Microsoft.CodeAnalysis.SourceProductionContext; #endif -internal sealed class ParameterListBuilder(IEnumerable fields, IEnumerable properties) +internal sealed class ParameterListBuilder(IReadOnlyList fields, IReadOnlyList properties) { private IEnumerable _baseParameters = []; private IEnumerable _postCtorParameters = []; @@ -20,13 +20,17 @@ public void SetPostCtorParameters(IEnumerable postCtorParameters public ParameterList Build(EmitterContext context) { + // Keyed by large structs: letting these regrow for a type with hundreds of members + // pushes their entry arrays onto the large object heap. + var memberCount = fields.Count + properties.Count; var baseParameters = new List(); var postCtorParameters = new List(); - var parametersMap = new Dictionary(); - var parameterModels = new List(); + // Keyed by IdentifierName (unique within a type) rather than the whole MemberModel. + var parametersMap = new Dictionary(memberCount); + var parameterModels = new List(memberCount); var nameHash = new HashSet(); - var uniqueNames = new Dictionary(); + var uniqueNames = new Dictionary(memberCount); foreach (var p in _baseParameters) { @@ -39,14 +43,15 @@ public ParameterList Build(EmitterContext context) foreach (var m in fields) { // ref/out from postctor - if (_postCtorParameters.Any(p => p.IsOutOrRef && m.Type == p.Type)) + if (IsOutOrRefPostCtorParameter(m)) continue; var p = ParameterModel.Create(m); GetUniqueName(p, nameHash, uniqueNames, out var name); parameterModels.Add(p); - parametersMap.Add(m, name); + // Indexer, not Add: code mid-edit can declare the same member name twice. + parametersMap[m.IdentifierName] = name; } foreach (var m in properties) { @@ -56,7 +61,8 @@ public ParameterList Build(EmitterContext context) GetUniqueName(p, nameHash, uniqueNames, out var name); parameterModels.Add(p); - parametersMap.Add(m, name); + // Indexer, not Add: code mid-edit can declare the same member name twice. + parametersMap[m.IdentifierName] = name; } foreach (var p in _postCtorParameters) @@ -79,7 +85,9 @@ public ParameterList Build(EmitterContext context) ? $"{p.RefKind.ToParameterPrefix()} {name}" : name); } - var constructorParameters = uniqueNames.Select(ConstructorParameterCSharp).ToList(); + var constructorParameters = new List(uniqueNames.Count); + foreach (var u in uniqueNames) + constructorParameters.Add(ConstructorParameterCSharp(u)); return new( constructorParameters, @@ -90,6 +98,17 @@ public ParameterList Build(EmitterContext context) ); } + // A loop rather than Any(lambda): capturing the member allocated a closure per field. + private bool IsOutOrRefPostCtorParameter(MemberModel m) + { + foreach (var p in _postCtorParameters) + { + if (p.IsOutOrRef && m.Type == p.Type) + return true; + } + return false; + } + private static string ConstructorParameterCSharp(KeyValuePair u) { var defaultValue = u.Key.HasExplicitDefaultValue @@ -123,7 +142,7 @@ internal sealed class ParameterList( IEnumerable ctorParameterDeclarations, IEnumerable baseParameters, IEnumerable postCtorParameters, - Dictionary parameterMap, + Dictionary parameterMap, IEnumerable parameterModels ) : IEnumerable { @@ -133,7 +152,7 @@ IEnumerable parameterModels public IEnumerable PostCtorParameters => postCtorParameters; public string? GetParameter(MemberModel m) => - parameterMap.TryGetValue(m, out var result) ? result : null; + parameterMap.TryGetValue(m.IdentifierName, out var result) ? result : null; public IEnumerator GetEnumerator() => parameterModels.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); diff --git a/src/Shared/Models/ServiceProviderModel.cs b/src/Shared/Models/ServiceProviderModel.cs new file mode 100644 index 00000000..6298fbc1 --- /dev/null +++ b/src/Shared/Models/ServiceProviderModel.cs @@ -0,0 +1,339 @@ +using System.Collections.Immutable; +using System.Text.RegularExpressions; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using static AutoCtor.Diagnostics; + +internal enum Lifetime { Singleton, Scoped, Transient } + +internal readonly record struct RegistrationModel( + Lifetime Lifetime, + EquatableTypeSymbol Service, + EquatableTypeSymbol Implementation, + // Both as C# source, the key comparable with ParameterModel.KeyedService. + string? Key, + // "{root}" is replaced by the container the factory is called from. + string? Factory, + bool FactoryIsMethod, + bool IsOpenGeneric, + bool IsAutoConstruct, + // Decide the cheapest way to cache and to dispose it. + bool ServiceIsReferenceType, + bool ImplementationIsDisposable, + int PublicConstructorCount, + EquatableList Parameters, + string ErrorName, + EquatableList Locations +) : IHaveDiagnostics; + +// Found while reading the attributes, where the symbols are at hand. +internal readonly record struct ModelDiagnostic(DiagnosticDescriptor Descriptor, EquatableList Args, EquatableList Locations) : IHaveDiagnostics +{ + public string ErrorName => Args[0]; +} + +internal readonly record struct ServiceProviderModel( + string? Namespace, + string Name, + string HintName, + EquatableList TypeDeclarations, + string? Fallback, + EquatableList Registrations, + EquatableList Diagnostics +) : IPartialTypeModel +{ + public static ServiceProviderModel Create(INamedTypeSymbol type) + { + var registrations = new List(); + var diagnostics = new List(); + AddRegistrations(type, type, new(SymbolEqualityComparer.Default) { type }, [], registrations, diagnostics); + + if (type.IsGenericType || Utilities.HasAttribute(type, AttributeNames.AutoConstruct)) + Report(diagnostics, ACTR017_InvalidServiceProviderType, type.Locations, DisplayName(type)); + + if (!type.IsSealed) + Report(diagnostics, ACTR029_ServiceProviderMustBeSealed, type.Locations, DisplayName(type)); + + var fallback = type.GetAttributes() + .First(a => a.AttributeClass?.ToDisplayString() == AttributeNames.ServiceProvider) + .NamedArguments.FirstOrDefault(n => n.Key == "Fallback").Value.Value as string; + string? fallbackAccess = null; + if (fallback is not null) + { + fallbackAccess = MemberAccess(type, fallback, out var member) + (member is IMethodSymbol ? "()" : ""); + var fallbackType = MemberType(type, member, allowParameters: false); + if (fallbackType is null) + Report(diagnostics, ACTR024_InvalidProviderFallback, type.Locations, fallback, DisplayName(type)); + else if (!fallbackType.AllInterfaces.Prepend(fallbackType).Any(i => i.ToDisplayString() == "System.IServiceProvider")) + Report(diagnostics, ACTR025_ProviderFallbackNotAServiceProvider, type.Locations, fallback, DisplayName(fallbackType)); + } + + return new( + Namespace: GeneratorUtilities.GetNamespace(type), + Name: type.Name, + HintName: GeneratorUtilities.GetHintName(type), + TypeDeclarations: GeneratorUtilities.GetTypeDeclarations(type), + Fallback: fallbackAccess, + Registrations: new(registrations), + Diagnostics: new(diagnostics)); + } + + // Walks the attributes in order, expanding scans and imports in place, so a later + // registration replaces an earlier one wherever it came from. + private static void AddRegistrations( + INamedTypeSymbol provider, + INamedTypeSymbol source, + HashSet visited, + Location[] importLocations, + List registrations, + List diagnostics) + { + // Syntax from another assembly can't be reported against, so use the import instead. + var ownSyntax = SymbolEqualityComparer.Default.Equals(source.ContainingAssembly, provider.ContainingAssembly); + + foreach (var attribute in source.GetAttributes()) + { + if (attribute.AttributeClass is not { ContainingNamespace.Name: "AutoCtor" } attributeClass) + continue; + + var name = attributeClass.Name; + var typeArgs = attributeClass.TypeArguments; + var ctorArgs = attribute.ConstructorArguments; + var locations = ownSyntax && attribute.ApplicationSyntaxReference is { } r + ? [Location.Create(r.SyntaxTree, r.Span)] + : importLocations; + var lifetime = name.Contains("Scoped") ? Lifetime.Scoped + : name.Contains("Transient") ? Lifetime.Transient + : Lifetime.Singleton; + var before = registrations.Count; + + if (name == "ImportAttribute") + { + // Visited once, so cycles end and a module imported twice isn't doubled up. + if ((typeArgs.Length > 0 ? typeArgs[0] : ctorArgs[0].Value) is INamedTypeSymbol module + && visited.Add(module)) + { + var diagnosticsBefore = diagnostics.Count; + AddRegistrations(provider, module, visited, locations, registrations, diagnostics); + // An empty scan or a bad registration inside already says why. + if (registrations.Count == before && diagnostics.Count == diagnosticsBefore) + Report(diagnostics, ACTR027_ImportedModuleHasNoRegistrations, locations, DisplayName(module)); + } + } + else if (name.StartsWith("Scan", StringComparison.Ordinal)) + { + // A scan needs a service or a name filter; one with neither would register every class. + var filter = ctorArgs.Length > 0 ? ctorArgs[0].Value as INamedTypeSymbol : null; + if (filter is null && ctorArgs.Length > 0 && !ctorArgs[0].IsNull) + continue; + if (filter is null && !attribute.NamedArguments.Any(n => n.Key == "TypeNameFilter" && n.Value.Value is string)) + { + Report(diagnostics, ACTR030_ScanHasNoFilter, locations, name.Substring(0, name.Length - "Attribute".Length)); + continue; + } + + foreach (var (service, implementation) in Scan(provider, source, attribute, filter)) + Add(provider, source, lifetime, service, implementation, null, null, locations, registrations, diagnostics); + + if (registrations.Count == before) + Report(diagnostics, ACTR026_ScanFoundNoTypes, locations, filter is null + ? attribute.NamedArguments.FirstOrDefault(n => n.Key == "TypeNameFilter").Value.Value as string ?? "*" + : DisplayName(filter)); + } + else if (name is "SingletonAttribute" or "TransientAttribute" or "ScopedAttribute") + { + var service = typeArgs.Length > 0 ? typeArgs[0] : ctorArgs[0].Value as ITypeSymbol; + var implementation = (typeArgs.Length > 1 + ? typeArgs[1] + : ctorArgs.Length > 1 + ? ctorArgs[1].Value as ITypeSymbol + : null) + ?? service; + var key = attribute.NamedArguments + .Where(n => n.Key == "Key" && !n.Value.IsNull) + .Select(n => n.Value.ToCSharpString()) + .FirstOrDefault(); + var factory = attribute.NamedArguments + .FirstOrDefault(n => n.Key == "Factory").Value.Value as string; + Add(provider, source, lifetime, service, implementation, key, factory, locations, registrations, diagnostics); + } + } + } + + private static void Add( + INamedTypeSymbol provider, + INamedTypeSymbol source, + Lifetime lifetime, + ITypeSymbol? service, + ITypeSymbol? implementation, + string? key, + string? factory, + Location[] locations, + List registrations, + List diagnostics) + { + if (service is null or IErrorTypeSymbol || implementation is not INamedTypeSymbol impl || impl is IErrorTypeSymbol) + return; + + var isOpen = (service as INamedTypeSymbol)?.IsUnboundGenericType == true; + if (isOpen) + { + service = service.OriginalDefinition; + impl = impl.OriginalDefinition; + } + + var ctors = impl.InstanceConstructors + .Where(c => c.DeclaredAccessibility == Accessibility.Public) + .ToList(); + var parameters = ctors.Count == 1 ? ctors[0].Parameters : ImmutableArray.Empty; + + ISymbol? member = null; + var factoryAccess = factory is null ? null : MemberAccess(source, factory, out member); + if (factory is not null) + parameters = member is IMethodSymbol method ? method.Parameters : ImmutableArray.Empty; + + var (serviceName, implName) = (DisplayName(service), DisplayName(impl)); + var factoryType = MemberType(provider, member, allowParameters: true); + (DiagnosticDescriptor, string[])? error = + isOpen && factory is not null ? (ACTR023_OpenGenericServiceFactory, [serviceName, factory]) + : isOpen && impl.Arity != ((INamedTypeSymbol)service).Arity ? (ACTR016_InvalidOpenGenericRegistration, [serviceName, implName]) + : !IsAssignable(impl, service, isOpen) ? (ACTR015_ImplementationNotAssignableToService, [implName, serviceName]) + : factory is null && (impl.IsAbstract || impl.IsStatic || impl.TypeKind is not (TypeKind.Class or TypeKind.Struct) + || !isOpen && impl.TypeArguments.Any(t => t is ITypeParameterSymbol)) ? (ACTR011_ServiceImplementationCannotBeInstantiated, [implName]) + : factory is null ? null + : factoryType is null ? (ACTR021_InvalidServiceFactory, [factory, serviceName]) + : !IsAssignable(factoryType, service) ? (ACTR022_ServiceFactoryReturnTypeNotAssignable, [factory, DisplayName(factoryType), serviceName]) + : member is { IsStatic: false } && !SymbolEqualityComparer.Default.Equals(source, provider) ? (ACTR028_ModuleFactoryMustBeStatic, [factory, DisplayName(source)]) + : null; + if (error is var (descriptor, args)) + { + Report(diagnostics, descriptor, locations, args); + return; + } + + registrations.Add(new( + Lifetime: lifetime, + Service: new(service), + Implementation: new(impl), + Key: key, + Factory: factoryAccess, + FactoryIsMethod: member is IMethodSymbol, + IsOpenGeneric: isOpen, + IsAutoConstruct: Utilities.HasAttribute(impl, AttributeNames.AutoConstruct), + ServiceIsReferenceType: service.IsReferenceType, + // A factory can hand back anything. + ImplementationIsDisposable: factory is not null || impl.AllInterfaces.Any(i => i.ToDisplayString() is "System.IDisposable" or "System.IAsyncDisposable"), + PublicConstructorCount: ctors.Count, + Parameters: new(parameters.Select(ParameterModel.Create)), + ErrorName: impl.ToDisplayString(MinimallyQualifiedFormat), + Locations: new(locations))); + } + + private static string DisplayName(ITypeSymbol type) => type.ToDisplayString(MinimallyQualifiedFormat); + + private static void Report(List diagnostics, DiagnosticDescriptor descriptor, IEnumerable locations, params string[] args) + => diagnostics.Add(new(descriptor, new(args), new(locations))); + + // The type, an interface it implements, or a base class. An open registration compares definitions. + internal static bool IsAssignable(ITypeSymbol type, ITypeSymbol target, bool byDefinition = false) + => target.SpecialType == SpecialType.System_Object + || type.AllInterfaces.Concat(type is INamedTypeSymbol named ? BaseTypes(named) : []).Prepend(type) + .Any(t => SymbolEqualityComparer.Default.Equals(byDefinition ? t.OriginalDefinition : t, byDefinition ? target.OriginalDefinition : target)); + + // What a named member produces, or null when generated code can't use it: not a value, + // generic, or out of reach from the provider. + private static ITypeSymbol? MemberType(INamedTypeSymbol provider, ISymbol? member, bool allowParameters) + { + if (member is null || member.DeclaredAccessibility is Accessibility.Private or Accessibility.Protected or Accessibility.ProtectedAndInternal + && !SymbolEqualityComparer.Default.Equals(member.ContainingType, provider)) + return null; + + var type = member switch + { + IMethodSymbol { MethodKind: MethodKind.Ordinary, IsGenericMethod: false, ReturnsVoid: false, RefKind: RefKind.None } m + when (allowParameters ? m.Parameters.All(p => p.RefKind == RefKind.None) : m.Parameters.Length == 0) => m.ReturnType, + IPropertySymbol { IsIndexer: false, GetMethod: not null } p => p.Type, + IFieldSymbol f => f.Type, + _ => null, + }; + return type is IErrorTypeSymbol ? null : type; + } + + // Static members are called through their type, instance members through the provider. + private static string MemberAccess(INamedTypeSymbol owner, string name, out ISymbol? member) + { + member = null; + for (var t = owner; t is not null && member is null; t = t.BaseType) + member = t.GetMembers(name).FirstOrDefault(); + + return member is { IsStatic: true } + ? $"{member.ContainingType.ToDisplayString(FullyQualifiedFormat)}.{name}" + : $"{{root}}.{name}"; + } + + // ponytail: walks every type in the scanned assemblies on each run; index by interface if it shows up. + private static IEnumerable<(ITypeSymbol, INamedTypeSymbol)> Scan( + INamedTypeSymbol provider, INamedTypeSymbol source, AttributeData attribute, INamedTypeSymbol? filter) + { + var scanAs = 1; + Regex? typeName = null; + var assemblies = new List(); + foreach (var named in attribute.NamedArguments) + { + if (named.Key == "As" && named.Value.Value is int flags) + scanAs = flags; + else if (named.Key == "FromAssembliesOf" && !named.Value.IsNull) + assemblies.AddRange(named.Value.Values.Select(v => (v.Value as ITypeSymbol)?.ContainingAssembly).OfType()); + else if (named.Key == "TypeNameFilter" && named.Value.Value is string pattern) + typeName = new Regex("^" + Regex.Escape(pattern).Replace(@"\*", ".*").Replace(@"\?", ".") + "$"); + } + if (assemblies.Count == 0) + assemblies.Add(source.ContainingAssembly); + + var isOpen = filter?.IsUnboundGenericType == true; + var target = isOpen ? filter!.OriginalDefinition : filter; + + var candidates = assemblies + .Distinct(SymbolEqualityComparer.Default) + .SelectMany(a => VisibleTypes(a.GlobalNamespace, + SymbolEqualityComparer.Default.Equals(a, provider.ContainingAssembly) || a.GivesAccessTo(provider.ContainingAssembly))) + .Where(t => t is { TypeKind: TypeKind.Class, IsAbstract: false, IsStatic: false, IsGenericType: false } + && !SymbolEqualityComparer.Default.Equals(t, provider) + && typeName?.IsMatch(t.Name) != false) + // Neither source nor metadata promise an order, and order decides which registration wins. + .OrderBy(t => t.ToDisplayString(FullyQualifiedFormat), StringComparer.Ordinal); + + foreach (var type in candidates) + { + var matches = target is null + ? [type] + : type.AllInterfaces.Concat(BaseTypes(type)).Prepend(type) + .Where(t => SymbolEqualityComparer.Default.Equals(isOpen ? t.OriginalDefinition : t, target)) + .ToList(); + if (matches.Count == 0) + continue; + + var services = new List(); + if ((scanAs & 1) != 0) services.AddRange(matches); + if ((scanAs & 2) != 0) services.Add(type); + if ((scanAs & 4) != 0) services.AddRange(type.AllInterfaces); + + foreach (var service in services.Distinct(SymbolEqualityComparer.Default)) + yield return (service, type); + } + } + + private static IEnumerable BaseTypes(INamedTypeSymbol type) + { + for (var b = type.BaseType; b is not null; b = b.BaseType) + yield return b; + } + + // Every type in a namespace, and nested in those types, that the provider's assembly can name. + private static IEnumerable VisibleTypes(INamespaceOrTypeSymbol container, bool internals) => + (container is INamespaceSymbol ns ? ns.GetNamespaceMembers().SelectMany(n => VisibleTypes(n, internals)) : []) + .Concat(container.GetTypeMembers() + .Where(t => t.DeclaredAccessibility == Accessibility.Public + || internals && t.DeclaredAccessibility is Accessibility.Internal or Accessibility.ProtectedOrInternal) + .SelectMany(t => VisibleTypes(t, internals).Prepend(t))); +} diff --git a/src/Shared/Models/TypeModel.cs b/src/Shared/Models/TypeModel.cs index 095c8228..8969019f 100644 --- a/src/Shared/Models/TypeModel.cs +++ b/src/Shared/Models/TypeModel.cs @@ -1,4 +1,5 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; internal readonly record struct TypeModel( int Depth, @@ -120,19 +121,33 @@ public static string CreateKey(INamedTypeSymbol? type) return type.ToDisplayString(FullyQualifiedFormat); } - private static EquatableList GetFields(IEnumerable members) + // Types can have hundreds of members, so filter the symbols first and allocate the + // model array exactly once rather than growing a LINQ buffer. + private static EquatableList GetFields(ImmutableArray members) { - return new(members - .OfType() - .Where(Utilities.IsValidField) - .Select(MemberModel.Create)); + var fields = new List(); + foreach (var member in members) + { + if (member is IFieldSymbol field && Utilities.IsValidField(field)) + fields.Add(field); + } + var builder = ImmutableArray.CreateBuilder(fields.Count); + foreach (var field in fields) + builder.Add(MemberModel.Create(field)); + return new(builder.MoveToImmutable()); } - private static EquatableList GetProperties(IEnumerable members) + private static EquatableList GetProperties(ImmutableArray members) { - return new(members - .OfType() - .Where(Utilities.IsValidProperty) - .Select(MemberModel.Create)); + var properties = new List(); + foreach (var member in members) + { + if (member is IPropertySymbol property && Utilities.IsValidProperty(property)) + properties.Add(property); + } + var builder = ImmutableArray.CreateBuilder(properties.Count); + foreach (var property in properties) + builder.Add(MemberModel.Create(property)); + return new(builder.MoveToImmutable()); } } diff --git a/src/Shared/Models/Utilities.cs b/src/Shared/Models/Utilities.cs index 9724dce0..bac962af 100644 --- a/src/Shared/Models/Utilities.cs +++ b/src/Shared/Models/Utilities.cs @@ -1,25 +1,35 @@ -using Microsoft.CodeAnalysis; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; internal static class Utilities { + // These run once per member of every [AutoConstruct] type, so they loop directly over + // the ImmutableArrays: LINQ here boxed the array and allocated iterators per member. + public static string? GetServiceKey(ISymbol symbol) { - var keyedService = symbol.GetAttributes() - .Where(a => a.AttributeClass?.ToDisplayString() == AttributeNames.AutoKeyedService - || a.AttributeClass?.ToDisplayString() == "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute") - .FirstOrDefault(); - - if (keyedService != null) - return keyedService.ConstructorArguments[0].ToCSharpString(); + foreach (var attribute in symbol.GetAttributes()) + { + var name = attribute.AttributeClass?.ToDisplayString(); + if (name == AttributeNames.AutoKeyedService + || name == "Microsoft.Extensions.DependencyInjection.FromKeyedServicesAttribute") + return attribute.ConstructorArguments[0].ToCSharpString(); + } return null; } - public static bool HasIgnoreAttribute(IEnumerable attributes) => - attributes.Any(static a => a.AttributeClass?.ToDisplayString() - == AttributeNames.AutoConstructIgnore); + public static bool HasIgnoreAttribute(ImmutableArray attributes) + { + foreach (var attribute in attributes) + { + if (attribute.AttributeClass?.ToDisplayString() == AttributeNames.AutoConstructIgnore) + return true; + } + return false; + } public static bool IsValidField(IFieldSymbol field) { @@ -33,11 +43,11 @@ public static bool IsValidField(IFieldSymbol field) if (HasIgnoreAttribute(field.GetAttributes())) return false; - if (field.DeclaringSyntaxReferences - .Select(x => x.GetSyntax()) - .OfType() - .Any(x => x.Initializer != null)) - return false; + foreach (var reference in field.DeclaringSyntaxReferences) + { + if (reference.GetSyntax() is VariableDeclaratorSyntax { Initializer: not null }) + return false; + } return true; } @@ -51,14 +61,18 @@ public static bool IsValidProperty(IPropertySymbol property) return false; // Property has a backing field generated by the compiler - if (!property.ContainingType.GetMembers().OfType() - .Any(field => SymbolEqualityComparer.Default.Equals(field.AssociatedSymbol, property))) + if (!HasBackingField(property)) return false; - var propertySyntax = property.DeclaringSyntaxReferences - .Select(x => x.GetSyntax()) - .OfType() - .FirstOrDefault(); + PropertyDeclarationSyntax? propertySyntax = null; + foreach (var reference in property.DeclaringSyntaxReferences) + { + if (reference.GetSyntax() is PropertyDeclarationSyntax syntax) + { + propertySyntax = syntax; + break; + } + } if (propertySyntax is null) return false; @@ -90,6 +104,19 @@ public static bool IsValidProperty(IPropertySymbol property) return true; } + // ponytail: scans every member per property, so O(members x properties); precompute the + // set of backing fields per type if property-heavy types ever show up in a profile. + private static bool HasBackingField(IPropertySymbol property) + { + foreach (var member in property.ContainingType.GetMembers()) + { + if (member is IFieldSymbol field + && SymbolEqualityComparer.Default.Equals(field.AssociatedSymbol, property)) + return true; + } + return false; + } + public static bool HasEligibleMember(INamedTypeSymbol type) { foreach (var member in type.GetMembers()) @@ -115,11 +142,13 @@ public static IEnumerable GetEligibleMembers(INamedTypeSymbol type) } } + public static bool HasAttribute(ISymbol? symbol, string attributeName) { return symbol != null && symbol.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == attributeName); } + public static bool IsAutoConstructInCompilation(Compilation compilation) { return compilation.GetTypeByMetadataName(AttributeNames.AutoConstruct) is not null; diff --git a/src/Tests/DuplicateMemberNames.cs b/src/Tests/DuplicateMemberNames.cs new file mode 100644 index 00000000..26bedef4 --- /dev/null +++ b/src/Tests/DuplicateMemberNames.cs @@ -0,0 +1,48 @@ +using AutoCtor; +using static ExampleTestsHelper; + +internal sealed class DuplicateMemberNames +{ + // Code mid-edit can declare the same member name twice (CS0102). The generator has to + // keep producing output instead of throwing, which drops every generated constructor. + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task GeneratorDoesNotThrow(CompilationBuilderFactory builderFactory) + { + const string code = @" +public interface IService { } +public interface IOther { } +public interface IAnother { } + +[AutoCtor.AutoConstruct] +public partial class DuplicateMembers +{ + private readonly IService _service; + private readonly IService _service; + private readonly IOther _other; + private readonly IAnother _other; +} +"; + var builder = builderFactory.Builder; + var compilation = builder + .AddCodes(code) + .Build(nameof(DuplicateMemberNames)); + + var result = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(builder.ParseOptions) + .RunGenerators(compilation, TestHelper.CancellationToken) + .GetRunResult() + .Results.Single(); + + await Assert.That(result.Exception).IsNull() + .ConfigureAwait(false); + await Assert.That(result.GeneratedSources).IsNotEmpty() + .ConfigureAwait(false); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", "CA1812:Avoid uninstantiated internal classes", + Justification = "Instantiated in generated code.")] + internal sealed class CompilationBuilderFactory : CompilationBuilderFactory; +} diff --git a/src/Tests/ExampleTests.cs b/src/Tests/ExampleTests.cs index b1893760..2eee5ff2 100644 --- a/src/Tests/ExampleTests.cs +++ b/src/Tests/ExampleTests.cs @@ -1,6 +1,8 @@ using AutoCtor; using Microsoft.CodeAnalysis; using static ExampleTestsHelper; +using Microsoft.CodeAnalysis.Testing; + #if ROSLYN_4_4 using Microsoft.CodeAnalysis.CSharp; @@ -10,9 +12,49 @@ internal sealed class ExampleTests { [Test] [CombinedDataSources] - public async Task ExamplesGeneratedCode( + public Task ExamplesGeneratedCode( + [MethodDataSource(nameof(GetExamples))] CodeFileTheoryData theoryData, + [ClassDataSource(Shared = SharedType.PerTestSession)] CompilationBuilderFactory builderFactory) + => VerifyGeneratedCode(theoryData, builderFactory); + + [Test] + [CombinedDataSources] + public Task CodeCompilesProperly( [MethodDataSource(nameof(GetExamples))] CodeFileTheoryData theoryData, [ClassDataSource(Shared = SharedType.PerTestSession)] CompilationBuilderFactory builderFactory) + => AssertCompiles(theoryData, builderFactory); + + // Without Microsoft.Extensions.DependencyInjection.Abstractions. + [Test] + [CombinedDataSources] + public Task IoCGeneratedCode( + [MethodDataSource(nameof(GetIoCExamples))] CodeFileTheoryData theoryData, + [ClassDataSource(Shared = SharedType.PerTestSession)] BareCompilationBuilderFactory builderFactory) + => VerifyGeneratedCode(theoryData, builderFactory); + + [Test] + [CombinedDataSources] + public Task IoCCodeCompilesProperly( + [MethodDataSource(nameof(GetIoCExamples))] CodeFileTheoryData theoryData, + [ClassDataSource(Shared = SharedType.PerTestSession)] BareCompilationBuilderFactory builderFactory) + => AssertCompiles(theoryData, builderFactory); + + // IAsyncDisposable isn't in netstandard2.0. + [Test] + [CombinedDataSources] + public Task AsyncGeneratedCode( + [MethodDataSource(nameof(GetAsyncExamples))] CodeFileTheoryData theoryData, + [ClassDataSource(Shared = SharedType.PerTestSession)] AsyncCompilationBuilderFactory builderFactory) + => VerifyGeneratedCode(theoryData, builderFactory); + + [Test] + [CombinedDataSources] + public Task AsyncCodeCompilesProperly( + [MethodDataSource(nameof(GetAsyncExamples))] CodeFileTheoryData theoryData, + [ClassDataSource(Shared = SharedType.PerTestSession)] AsyncCompilationBuilderFactory builderFactory) + => AssertCompiles(theoryData, builderFactory); + + private static async Task VerifyGeneratedCode(CodeFileTheoryData theoryData, ExampleTestsHelper.CompilationBuilderFactory builderFactory) { var builder = builderFactory.Create(theoryData); var compilation = builder.Build(nameof(ExampleTests)); @@ -30,12 +72,11 @@ await Verify(driver) .ConfigureAwait(false); } - [Test] - [CombinedDataSources] - public async Task CodeCompilesProperly( - [MethodDataSource(nameof(GetExamples))] CodeFileTheoryData theoryData, - [ClassDataSource(Shared = SharedType.PerTestSession)] CompilationBuilderFactory builderFactory) + private static async Task AssertCompiles(CodeFileTheoryData theoryData, ExampleTestsHelper.CompilationBuilderFactory builderFactory) { + if (theoryData.SnapshotOnly) + return; + var builder = builderFactory.Create(theoryData); var compilation = builder.Build(nameof(ExampleTests)); new GeneratorDriverBuilder() @@ -98,6 +139,28 @@ internal sealed class CompilationBuilderFactory : CompilationBuilderFactory GetNuGetIds() => ["Microsoft.Extensions.DependencyInjection.Abstractions"]; } + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", "CA1812:Avoid uninstantiated internal classes", + Justification = "Instantiated in generated code.")] + internal sealed class BareCompilationBuilderFactory : CompilationBuilderFactory; + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Performance", "CA1812:Avoid uninstantiated internal classes", + Justification = "Instantiated in generated code.")] + internal sealed class AsyncCompilationBuilderFactory : CompilationBuilderFactory + { + protected override ReferenceAssemblies BaseReferenceAssemblies => ReferenceAssemblies.Net.Net100; + protected override IEnumerable GetNuGetIds() => ["Microsoft.Extensions.DependencyInjection.Abstractions"]; + } + + private static IEnumerable> GetIoCExamples(string examples, string diagnosticExamples) => + GetExamplesFiles(examples).Select>(example => () => new CodeFileTheoryData(example)) + .Concat(GetExamplesFiles(diagnosticExamples).Select>(example => () => new CodeFileTheoryData(example) { SnapshotOnly = true })); + + public static IEnumerable> GetIoCExamples() => GetIoCExamples("IoCExamples", "IoCDiagnosticExamples"); + + public static IEnumerable> GetAsyncExamples() => GetIoCExamples("IoCAsyncExamples", "IoCAsyncDiagnosticExamples"); + public static IEnumerable> GetExamples() { foreach (var example in GetExamplesFiles("Examples")) @@ -126,6 +189,9 @@ public static IEnumerable> GetExamples() }; } + foreach (var example in GetIoCExamples("IoCAbstractionsExamples", "IoCAbstractionsDiagnosticExamples")) + yield return example; + #if ROSLYN_4_4 foreach (var readmeExample in GetExamplesFiles("ReadmeExamples")) { diff --git a/src/Tests/Examples/AbstractClassTest.cs#AbstractClassTest.g.verified.cs b/src/Tests/Examples/AbstractClassTest.cs#AbstractClassTest.g.verified.cs index 9944df66..341241ff 100644 --- a/src/Tests/Examples/AbstractClassTest.cs#AbstractClassTest.g.verified.cs +++ b/src/Tests/Examples/AbstractClassTest.cs#AbstractClassTest.g.verified.cs @@ -7,10 +7,7 @@ partial class AbstractClassTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public AbstractClassTest(int item) { this._item = item; diff --git a/src/Tests/Examples/AllParametersSameTypeTest.cs#AllParametersAreSameTypeTest.g.verified.cs b/src/Tests/Examples/AllParametersSameTypeTest.cs#AllParametersAreSameTypeTest.g.verified.cs index 2dc6348f..ecd573e9 100644 --- a/src/Tests/Examples/AllParametersSameTypeTest.cs#AllParametersAreSameTypeTest.g.verified.cs +++ b/src/Tests/Examples/AllParametersSameTypeTest.cs#AllParametersAreSameTypeTest.g.verified.cs @@ -7,10 +7,7 @@ partial class AllParametersAreSameTypeTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public AllParametersAreSameTypeTest(global::IService baseService, global::IService service) : base(baseService) { this._service = service; diff --git a/src/Tests/Examples/AmbiguousMarkedMethods.cs#AmbiguousMarkedMethods.g.verified.cs b/src/Tests/Examples/AmbiguousMarkedMethods.cs#AmbiguousMarkedMethods.g.verified.cs index 99a76196..2347de7d 100644 --- a/src/Tests/Examples/AmbiguousMarkedMethods.cs#AmbiguousMarkedMethods.g.verified.cs +++ b/src/Tests/Examples/AmbiguousMarkedMethods.cs#AmbiguousMarkedMethods.g.verified.cs @@ -7,10 +7,7 @@ partial class AmbiguousMarkedMethods { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public AmbiguousMarkedMethods(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/BaseTest.cs#BaseBaseClass.g.verified.cs b/src/Tests/Examples/BaseTest.cs#BaseBaseClass.g.verified.cs index f5fb9ebe..c3727db4 100644 --- a/src/Tests/Examples/BaseTest.cs#BaseBaseClass.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#BaseBaseClass.g.verified.cs @@ -7,10 +7,7 @@ partial class BaseBaseClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BaseBaseClass(int number) { this._number = number; diff --git a/src/Tests/Examples/BaseTest.cs#BaseClass.g.verified.cs b/src/Tests/Examples/BaseTest.cs#BaseClass.g.verified.cs index 641fdd11..91d0ee1b 100644 --- a/src/Tests/Examples/BaseTest.cs#BaseClass.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#BaseClass.g.verified.cs @@ -7,10 +7,7 @@ partial class BaseClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BaseClass(int number, string text) : base(number) { this._text = text; diff --git a/src/Tests/Examples/BaseTest.cs#BaseTest.g.verified.cs b/src/Tests/Examples/BaseTest.cs#BaseTest.g.verified.cs index e3ce6851..521190d2 100644 --- a/src/Tests/Examples/BaseTest.cs#BaseTest.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#BaseTest.g.verified.cs @@ -7,10 +7,7 @@ partial class BaseTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BaseTest(string text, int value) : base(text) { this._value = value; diff --git a/src/Tests/Examples/BaseTest.cs#ComplexBase.g.verified.cs b/src/Tests/Examples/BaseTest.cs#ComplexBase.g.verified.cs index ecef38c7..b2e36750 100644 --- a/src/Tests/Examples/BaseTest.cs#ComplexBase.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#ComplexBase.g.verified.cs @@ -7,10 +7,7 @@ partial class ComplexBase { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ComplexBase(string baseValue) { this._baseValue = baseValue; diff --git a/src/Tests/Examples/BaseTest.cs#ComplexTest.g.verified.cs b/src/Tests/Examples/BaseTest.cs#ComplexTest.g.verified.cs index d9bffe23..a06233a8 100644 --- a/src/Tests/Examples/BaseTest.cs#ComplexTest.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#ComplexTest.g.verified.cs @@ -7,10 +7,7 @@ partial class ComplexTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ComplexTest(string baseValue, string value) : base(baseValue) { this._value = value; diff --git a/src/Tests/Examples/BaseTest.cs#TheClass.g.verified.cs b/src/Tests/Examples/BaseTest.cs#TheClass.g.verified.cs index 63453ee3..f040b4ac 100644 --- a/src/Tests/Examples/BaseTest.cs#TheClass.g.verified.cs +++ b/src/Tests/Examples/BaseTest.cs#TheClass.g.verified.cs @@ -7,10 +7,7 @@ partial class TheClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public TheClass(int number, string text, bool flag) : base(number, text) { this._flag = flag; diff --git a/src/Tests/Examples/Broken251Release.cs#Broken251Release.g.verified.cs b/src/Tests/Examples/Broken251Release.cs#Broken251Release.g.verified.cs index ce589c4b..dbf2929c 100644 --- a/src/Tests/Examples/Broken251Release.cs#Broken251Release.g.verified.cs +++ b/src/Tests/Examples/Broken251Release.cs#Broken251Release.g.verified.cs @@ -7,10 +7,7 @@ partial class Broken251Release { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Broken251Release(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/ConflicingNames.cs#AClass.g.verified.cs b/src/Tests/Examples/ConflicingNames.cs#AClass.g.verified.cs index 92b470ab..20eabdfb 100644 --- a/src/Tests/Examples/ConflicingNames.cs#AClass.g.verified.cs +++ b/src/Tests/Examples/ConflicingNames.cs#AClass.g.verified.cs @@ -7,10 +7,7 @@ partial class AClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public AClass(global::IServiceA service) { this._service = service; diff --git a/src/Tests/Examples/ConflicingNames.cs#BClass.g.verified.cs b/src/Tests/Examples/ConflicingNames.cs#BClass.g.verified.cs index 97d2601e..4e5c91b3 100644 --- a/src/Tests/Examples/ConflicingNames.cs#BClass.g.verified.cs +++ b/src/Tests/Examples/ConflicingNames.cs#BClass.g.verified.cs @@ -7,10 +7,7 @@ partial class BClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BClass(global::IServiceA service, global::IServiceB service0) : base(service) { this._service = service0; diff --git a/src/Tests/Examples/ConflicingNames.cs#CClass.g.verified.cs b/src/Tests/Examples/ConflicingNames.cs#CClass.g.verified.cs index ccc95b5c..ca284d1f 100644 --- a/src/Tests/Examples/ConflicingNames.cs#CClass.g.verified.cs +++ b/src/Tests/Examples/ConflicingNames.cs#CClass.g.verified.cs @@ -7,10 +7,7 @@ partial class CClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public CClass( global::IServiceA service, global::IServiceB service0, diff --git a/src/Tests/Examples/DerivedExample.cs#Derived.g.verified.cs b/src/Tests/Examples/DerivedExample.cs#Derived.g.verified.cs index 3f5eee73..08131c54 100644 --- a/src/Tests/Examples/DerivedExample.cs#Derived.g.verified.cs +++ b/src/Tests/Examples/DerivedExample.cs#Derived.g.verified.cs @@ -7,10 +7,7 @@ partial class Derived { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Derived(global::ILogger logger) : base(logger) { this.logger = logger; diff --git a/src/Tests/Examples/ExcludeStaticAndInitialisedFieldsTest.cs#ExcludeStaticAndInitialisedFieldsTest.g.verified.cs b/src/Tests/Examples/ExcludeStaticAndInitialisedFieldsTest.cs#ExcludeStaticAndInitialisedFieldsTest.g.verified.cs index 35b2e029..db7c3bab 100644 --- a/src/Tests/Examples/ExcludeStaticAndInitialisedFieldsTest.cs#ExcludeStaticAndInitialisedFieldsTest.g.verified.cs +++ b/src/Tests/Examples/ExcludeStaticAndInitialisedFieldsTest.cs#ExcludeStaticAndInitialisedFieldsTest.g.verified.cs @@ -7,10 +7,7 @@ partial class ExcludeStaticAndInitialisedFieldsTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ExcludeStaticAndInitialisedFieldsTest(int s) { this._s = s; diff --git a/src/Tests/Examples/FriendlyParameterNamesTest.cs#FriendlyParameterNamesTest.g.verified.cs b/src/Tests/Examples/FriendlyParameterNamesTest.cs#FriendlyParameterNamesTest.g.verified.cs index 2a167d3a..014c9f1a 100644 --- a/src/Tests/Examples/FriendlyParameterNamesTest.cs#FriendlyParameterNamesTest.g.verified.cs +++ b/src/Tests/Examples/FriendlyParameterNamesTest.cs#FriendlyParameterNamesTest.g.verified.cs @@ -7,10 +7,7 @@ partial class FriendlyParameterNamesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public FriendlyParameterNamesTest( int _, int _2, diff --git a/src/Tests/Examples/GenericClassTest.cs#GenericClassTest[T].g.verified.cs b/src/Tests/Examples/GenericClassTest.cs#GenericClassTest[T].g.verified.cs index 97477423..70249b20 100644 --- a/src/Tests/Examples/GenericClassTest.cs#GenericClassTest[T].g.verified.cs +++ b/src/Tests/Examples/GenericClassTest.cs#GenericClassTest[T].g.verified.cs @@ -7,10 +7,7 @@ partial class GenericClassTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public GenericClassTest(T item) { this._item = item; diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass.g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass.g.verified.cs index eb2d12ff..e9bcc4a6 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass.g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass.g.verified.cs @@ -7,10 +7,7 @@ partial class ConcreteClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ConcreteClass(global::IServiceA t) : base(t) { } diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass1[T].g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass1[T].g.verified.cs index 663dec51..cf00be3a 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass1[T].g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass1[T].g.verified.cs @@ -7,10 +7,7 @@ partial class ConcreteClass1 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ConcreteClass1(global::IServiceA t1, T t2) : base(t1, t2) { } diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass2.g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass2.g.verified.cs index aae819de..c5e62754 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass2.g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClass2.g.verified.cs @@ -7,10 +7,7 @@ partial class ConcreteClass2 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ConcreteClass2(global::IServiceA t1, global::IServiceB t2) : base(t1, t2) { } diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithAnotherField.g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithAnotherField.g.verified.cs index 124f5859..1e2f02e0 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithAnotherField.g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithAnotherField.g.verified.cs @@ -7,10 +7,7 @@ partial class ConcreteClassWithAnotherField { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ConcreteClassWithAnotherField(global::IServiceA t, global::IServiceB serviceb) : base(t) { this._serviceb = serviceb; diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithGenericArg[T2].g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithGenericArg[T2].g.verified.cs index f4bbe491..383c85eb 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithGenericArg[T2].g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#ConcreteClassWithGenericArg[T2].g.verified.cs @@ -7,10 +7,7 @@ partial class ConcreteClassWithGenericArg { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ConcreteClassWithGenericArg(T2 t) : base(t) { } diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase2[T1, T2].g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase2[T1, T2].g.verified.cs index d85f492c..f2ca8e9f 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase2[T1, T2].g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase2[T1, T2].g.verified.cs @@ -7,10 +7,7 @@ partial class GenericBase2 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public GenericBase2(T1 t1, T2 t2) { this._t1 = t1; diff --git a/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase[T].g.verified.cs b/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase[T].g.verified.cs index 1cbd3768..f5d9f00c 100644 --- a/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase[T].g.verified.cs +++ b/src/Tests/Examples/GenericInheritanceTest.cs#GenericBase[T].g.verified.cs @@ -7,10 +7,7 @@ partial class GenericBase { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public GenericBase(T t) { this._t = t; diff --git a/src/Tests/Examples/Guard.cs#Guard.g.verified.cs b/src/Tests/Examples/Guard.cs#Guard.g.verified.cs index 5cff4b8e..f6acb9cc 100644 --- a/src/Tests/Examples/Guard.cs#Guard.g.verified.cs +++ b/src/Tests/Examples/Guard.cs#Guard.g.verified.cs @@ -7,10 +7,7 @@ partial class Guard { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Guard(string value) { this._value = value ?? throw new global::System.ArgumentNullException("value"); diff --git a/src/Tests/Examples/InheritanceTest.cs#A.g.verified.cs b/src/Tests/Examples/InheritanceTest.cs#A.g.verified.cs index fc494af5..1fabfc23 100644 --- a/src/Tests/Examples/InheritanceTest.cs#A.g.verified.cs +++ b/src/Tests/Examples/InheritanceTest.cs#A.g.verified.cs @@ -7,10 +7,7 @@ partial class A { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public A( global::IServiceE serviceE, global::IServiceD serviceD, diff --git a/src/Tests/Examples/InheritanceTest.cs#B.g.verified.cs b/src/Tests/Examples/InheritanceTest.cs#B.g.verified.cs index 6c09260f..f8649f2c 100644 --- a/src/Tests/Examples/InheritanceTest.cs#B.g.verified.cs +++ b/src/Tests/Examples/InheritanceTest.cs#B.g.verified.cs @@ -7,10 +7,7 @@ partial class B { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public B( global::IServiceE serviceE, global::IServiceD serviceD, diff --git a/src/Tests/Examples/InheritanceTest.cs#C.g.verified.cs b/src/Tests/Examples/InheritanceTest.cs#C.g.verified.cs index 8fe96e7e..0d506bc2 100644 --- a/src/Tests/Examples/InheritanceTest.cs#C.g.verified.cs +++ b/src/Tests/Examples/InheritanceTest.cs#C.g.verified.cs @@ -7,10 +7,7 @@ partial class C { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public C( global::IServiceE serviceE, global::IServiceD serviceD, diff --git a/src/Tests/Examples/InheritanceTest.cs#D.g.verified.cs b/src/Tests/Examples/InheritanceTest.cs#D.g.verified.cs index 851b4512..b66eea11 100644 --- a/src/Tests/Examples/InheritanceTest.cs#D.g.verified.cs +++ b/src/Tests/Examples/InheritanceTest.cs#D.g.verified.cs @@ -7,10 +7,7 @@ partial class D { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public D(global::IServiceE serviceE, global::IServiceD serviceD) : base(serviceE) { this._serviceD = serviceD; diff --git a/src/Tests/Examples/InheritanceTest.cs#E.g.verified.cs b/src/Tests/Examples/InheritanceTest.cs#E.g.verified.cs index a6f669bb..5f661be8 100644 --- a/src/Tests/Examples/InheritanceTest.cs#E.g.verified.cs +++ b/src/Tests/Examples/InheritanceTest.cs#E.g.verified.cs @@ -7,10 +7,7 @@ partial class E { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public E(global::IServiceE serviceE) { this._serviceE = serviceE; diff --git a/src/Tests/Examples/Issue125.cs#OrderRequest.g.verified.cs b/src/Tests/Examples/Issue125.cs#OrderRequest.g.verified.cs index d446db00..5bb0d848 100644 --- a/src/Tests/Examples/Issue125.cs#OrderRequest.g.verified.cs +++ b/src/Tests/Examples/Issue125.cs#OrderRequest.g.verified.cs @@ -7,10 +7,7 @@ partial record OrderRequest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public OrderRequest(string Id) : base(Id) { } diff --git a/src/Tests/Examples/Issue206.cs#Base[T2].g.verified.cs b/src/Tests/Examples/Issue206.cs#Base[T2].g.verified.cs index 3df28b9b..c4c4cc28 100644 --- a/src/Tests/Examples/Issue206.cs#Base[T2].g.verified.cs +++ b/src/Tests/Examples/Issue206.cs#Base[T2].g.verified.cs @@ -7,10 +7,7 @@ partial class Base { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Base(global::ILogger> logger) { this.Logger = logger; diff --git a/src/Tests/Examples/Issue206.cs#Derived.g.verified.cs b/src/Tests/Examples/Issue206.cs#Derived.g.verified.cs index 833bf8b2..a79852d1 100644 --- a/src/Tests/Examples/Issue206.cs#Derived.g.verified.cs +++ b/src/Tests/Examples/Issue206.cs#Derived.g.verified.cs @@ -7,10 +7,7 @@ partial class Derived { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Derived(global::ILogger> logger) : base(logger) { } diff --git a/src/Tests/Examples/Issue299.cs#Issue299.g.verified.cs b/src/Tests/Examples/Issue299.cs#Issue299.g.verified.cs index 79281408..ce8d857f 100644 --- a/src/Tests/Examples/Issue299.cs#Issue299.g.verified.cs +++ b/src/Tests/Examples/Issue299.cs#Issue299.g.verified.cs @@ -7,10 +7,7 @@ partial class Issue299 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Issue299() { Inititialise(); diff --git a/src/Tests/Examples/Issue536.cs#IInterface.NestedClass.g.verified.cs b/src/Tests/Examples/Issue536.cs#IInterface.NestedClass.g.verified.cs index 385eba8c..4f9ffb5a 100644 --- a/src/Tests/Examples/Issue536.cs#IInterface.NestedClass.g.verified.cs +++ b/src/Tests/Examples/Issue536.cs#IInterface.NestedClass.g.verified.cs @@ -9,10 +9,7 @@ partial interface IInterface { partial class NestedClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NestedClass(global::IInterface item) { this.Item = item; diff --git a/src/Tests/Examples/KeyedServicesTest.cs#ChildKeyedServicesTest.g.verified.cs b/src/Tests/Examples/KeyedServicesTest.cs#ChildKeyedServicesTest.g.verified.cs index eb302609..62703baa 100644 --- a/src/Tests/Examples/KeyedServicesTest.cs#ChildKeyedServicesTest.g.verified.cs +++ b/src/Tests/Examples/KeyedServicesTest.cs#ChildKeyedServicesTest.g.verified.cs @@ -7,10 +7,7 @@ partial class ChildKeyedServicesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public ChildKeyedServicesTest( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null, [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string, diff --git a/src/Tests/Examples/KeyedServicesTest.cs#KeyedServicesTest[T].g.verified.cs b/src/Tests/Examples/KeyedServicesTest.cs#KeyedServicesTest[T].g.verified.cs index babf2309..0339e53b 100644 --- a/src/Tests/Examples/KeyedServicesTest.cs#KeyedServicesTest[T].g.verified.cs +++ b/src/Tests/Examples/KeyedServicesTest.cs#KeyedServicesTest[T].g.verified.cs @@ -7,10 +7,7 @@ partial class KeyedServicesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public KeyedServicesTest( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices(null)] global::IService @null, [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService @string, diff --git a/src/Tests/Examples/KeywordsTest.cs#KeywordsTest.g.verified.cs b/src/Tests/Examples/KeywordsTest.cs#KeywordsTest.g.verified.cs index 7e2a0b50..e6a74bc3 100644 --- a/src/Tests/Examples/KeywordsTest.cs#KeywordsTest.g.verified.cs +++ b/src/Tests/Examples/KeywordsTest.cs#KeywordsTest.g.verified.cs @@ -7,10 +7,7 @@ partial class KeywordsTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public KeywordsTest(int @base, int @int, int @class, int @private) { this.@base = @base; diff --git a/src/Tests/Examples/MixedNestedClassAndRecordTest.cs#OuterClass1.OuterRecord1.OuterClass2.MixedNestedClassAndRecordTest.g.verified.cs b/src/Tests/Examples/MixedNestedClassAndRecordTest.cs#OuterClass1.OuterRecord1.OuterClass2.MixedNestedClassAndRecordTest.g.verified.cs index d13beb28..cd9aada6 100644 --- a/src/Tests/Examples/MixedNestedClassAndRecordTest.cs#OuterClass1.OuterRecord1.OuterClass2.MixedNestedClassAndRecordTest.g.verified.cs +++ b/src/Tests/Examples/MixedNestedClassAndRecordTest.cs#OuterClass1.OuterRecord1.OuterClass2.MixedNestedClassAndRecordTest.g.verified.cs @@ -13,10 +13,7 @@ partial class OuterClass2 { partial record MixedNestedClassAndRecordTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public MixedNestedClassAndRecordTest(int item) { this._item = item; diff --git a/src/Tests/Examples/MultipleGenericInheritance.cs#Example1.g.verified.cs b/src/Tests/Examples/MultipleGenericInheritance.cs#Example1.g.verified.cs index b0416a0e..24d52f38 100644 --- a/src/Tests/Examples/MultipleGenericInheritance.cs#Example1.g.verified.cs +++ b/src/Tests/Examples/MultipleGenericInheritance.cs#Example1.g.verified.cs @@ -7,10 +7,7 @@ partial class Example1 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Example1(global::IServiceA a, global::IServiceB b, global::IServiceC c) : base(a, b) { this.c = c; diff --git a/src/Tests/Examples/MultipleGenericInheritance.cs#Example2.g.verified.cs b/src/Tests/Examples/MultipleGenericInheritance.cs#Example2.g.verified.cs index 3695b5c6..adaecba0 100644 --- a/src/Tests/Examples/MultipleGenericInheritance.cs#Example2.g.verified.cs +++ b/src/Tests/Examples/MultipleGenericInheritance.cs#Example2.g.verified.cs @@ -7,10 +7,7 @@ partial class Example2 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Example2(global::IServiceD a, global::IServiceE b, global::IServiceF f) : base(a, b) { this.f = f; diff --git a/src/Tests/Examples/MultipleGenericInheritance.cs#Generic[TA, TB].g.verified.cs b/src/Tests/Examples/MultipleGenericInheritance.cs#Generic[TA, TB].g.verified.cs index 27286950..869020f8 100644 --- a/src/Tests/Examples/MultipleGenericInheritance.cs#Generic[TA, TB].g.verified.cs +++ b/src/Tests/Examples/MultipleGenericInheritance.cs#Generic[TA, TB].g.verified.cs @@ -7,10 +7,7 @@ partial class Generic { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Generic(TA a, TB b) { this.a = a; diff --git a/src/Tests/Examples/NamespaceDoubleNestedClassTest.cs#TestNamespace.OuterClass1.OuterClass2.NamespaceDoubleNestedClassTest.g.verified.cs b/src/Tests/Examples/NamespaceDoubleNestedClassTest.cs#TestNamespace.OuterClass1.OuterClass2.NamespaceDoubleNestedClassTest.g.verified.cs index c9d4aeb7..0547c1b2 100644 --- a/src/Tests/Examples/NamespaceDoubleNestedClassTest.cs#TestNamespace.OuterClass1.OuterClass2.NamespaceDoubleNestedClassTest.g.verified.cs +++ b/src/Tests/Examples/NamespaceDoubleNestedClassTest.cs#TestNamespace.OuterClass1.OuterClass2.NamespaceDoubleNestedClassTest.g.verified.cs @@ -13,10 +13,7 @@ partial class OuterClass2 { partial class NamespaceDoubleNestedClassTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NamespaceDoubleNestedClassTest(int item) { this._item = item; diff --git a/src/Tests/Examples/NamespaceTest.cs#TestNamespace.NamespaceTest.g.verified.cs b/src/Tests/Examples/NamespaceTest.cs#TestNamespace.NamespaceTest.g.verified.cs index b461d510..383a8f40 100644 --- a/src/Tests/Examples/NamespaceTest.cs#TestNamespace.NamespaceTest.g.verified.cs +++ b/src/Tests/Examples/NamespaceTest.cs#TestNamespace.NamespaceTest.g.verified.cs @@ -9,10 +9,7 @@ namespace TestNamespace { partial class NamespaceTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NamespaceTest(int item) { this._item = item; diff --git a/src/Tests/Examples/NullableAnnotationTest.cs#NullableAnnotationTests.g.verified.cs b/src/Tests/Examples/NullableAnnotationTest.cs#NullableAnnotationTests.g.verified.cs index efa3f05c..56ad5617 100644 --- a/src/Tests/Examples/NullableAnnotationTest.cs#NullableAnnotationTests.g.verified.cs +++ b/src/Tests/Examples/NullableAnnotationTest.cs#NullableAnnotationTests.g.verified.cs @@ -7,10 +7,7 @@ partial class NullableAnnotationTests { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NullableAnnotationTests( global::IServiceA nullableService, global::IServiceB guardedService diff --git a/src/Tests/Examples/ObsoleteBaseCtor.cs#AutoClass.g.verified.cs b/src/Tests/Examples/ObsoleteBaseCtor.cs#AutoClass.g.verified.cs index f55000d6..f86e01db 100644 --- a/src/Tests/Examples/ObsoleteBaseCtor.cs#AutoClass.g.verified.cs +++ b/src/Tests/Examples/ObsoleteBaseCtor.cs#AutoClass.g.verified.cs @@ -7,10 +7,7 @@ partial class AutoClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public AutoClass(string firstName, string lastName) : base(firstName, lastName) { } diff --git a/src/Tests/Examples/OuterClass.cs#OuterClass.NestedClassTest.g.verified.cs b/src/Tests/Examples/OuterClass.cs#OuterClass.NestedClassTest.g.verified.cs index 91e0e6a2..083c3b06 100644 --- a/src/Tests/Examples/OuterClass.cs#OuterClass.NestedClassTest.g.verified.cs +++ b/src/Tests/Examples/OuterClass.cs#OuterClass.NestedClassTest.g.verified.cs @@ -9,10 +9,7 @@ partial class OuterClass { partial class NestedClassTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NestedClassTest(int item) { this._item = item; diff --git a/src/Tests/Examples/PostCtorAttributeTest.cs#PostCtorAttributeTest.g.verified.cs b/src/Tests/Examples/PostCtorAttributeTest.cs#PostCtorAttributeTest.g.verified.cs index 994732f0..9ddaaecc 100644 --- a/src/Tests/Examples/PostCtorAttributeTest.cs#PostCtorAttributeTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorAttributeTest.cs#PostCtorAttributeTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorAttributeTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorAttributeTest(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/PostCtorOptionalConst.cs#PostCtorOptionalConst.g.verified.cs b/src/Tests/Examples/PostCtorOptionalConst.cs#PostCtorOptionalConst.g.verified.cs index 776bf4a1..9c8a6461 100644 --- a/src/Tests/Examples/PostCtorOptionalConst.cs#PostCtorOptionalConst.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalConst.cs#PostCtorOptionalConst.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalConst { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalConst(int num = 123, string text = "123") { Initialize(num, text); diff --git a/src/Tests/Examples/PostCtorOptionalDefault.cs#PostCtorOptionalDefault.g.verified.cs b/src/Tests/Examples/PostCtorOptionalDefault.cs#PostCtorOptionalDefault.g.verified.cs index 797f73cb..b132d559 100644 --- a/src/Tests/Examples/PostCtorOptionalDefault.cs#PostCtorOptionalDefault.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalDefault.cs#PostCtorOptionalDefault.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalDefault { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalDefault(string value = default, global::Flag flag = (global::Flag)0) { Initialize(value, flag); diff --git a/src/Tests/Examples/PostCtorOptionalEnum.cs#PostCtorOptionalEnum.g.verified.cs b/src/Tests/Examples/PostCtorOptionalEnum.cs#PostCtorOptionalEnum.g.verified.cs index e7a13683..b4ba8bbd 100644 --- a/src/Tests/Examples/PostCtorOptionalEnum.cs#PostCtorOptionalEnum.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalEnum.cs#PostCtorOptionalEnum.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalEnum { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalEnum(global::ConstEnum value = (global::ConstEnum)2) { Initialize(value); diff --git a/src/Tests/Examples/PostCtorOptionalInt.cs#PostCtorOptionalInt.g.verified.cs b/src/Tests/Examples/PostCtorOptionalInt.cs#PostCtorOptionalInt.g.verified.cs index 637096d4..63c000e4 100644 --- a/src/Tests/Examples/PostCtorOptionalInt.cs#PostCtorOptionalInt.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalInt.cs#PostCtorOptionalInt.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalInt { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalInt(int value = 123, int df = 0) { Initialize(value, df); diff --git a/src/Tests/Examples/PostCtorOptionalString.cs#PostCtorOptionalString.g.verified.cs b/src/Tests/Examples/PostCtorOptionalString.cs#PostCtorOptionalString.g.verified.cs index a9832770..f31fefb9 100644 --- a/src/Tests/Examples/PostCtorOptionalString.cs#PostCtorOptionalString.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalString.cs#PostCtorOptionalString.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalString { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalString(string value = "dfault") { Initialize(value); diff --git a/src/Tests/Examples/PostCtorOptionalUniqueNameTest.cs#PostCtorOptionalUniqueNameTest.g.verified.cs b/src/Tests/Examples/PostCtorOptionalUniqueNameTest.cs#PostCtorOptionalUniqueNameTest.g.verified.cs index ba5a3272..5161fd2f 100644 --- a/src/Tests/Examples/PostCtorOptionalUniqueNameTest.cs#PostCtorOptionalUniqueNameTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalUniqueNameTest.cs#PostCtorOptionalUniqueNameTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalUniqueNameTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalUniqueNameTest(string value, string value0 = "") { this._value = value; diff --git a/src/Tests/Examples/PostCtorOptionalWithBase.cs#BaseTest.g.verified.cs b/src/Tests/Examples/PostCtorOptionalWithBase.cs#BaseTest.g.verified.cs index 4039fec5..dd4495b7 100644 --- a/src/Tests/Examples/PostCtorOptionalWithBase.cs#BaseTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalWithBase.cs#BaseTest.g.verified.cs @@ -7,10 +7,7 @@ partial class BaseTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BaseTest(string value) { this._value = value; diff --git a/src/Tests/Examples/PostCtorOptionalWithBase.cs#PostCtorOptionalWithBase.g.verified.cs b/src/Tests/Examples/PostCtorOptionalWithBase.cs#PostCtorOptionalWithBase.g.verified.cs index 459acbbf..baafd75b 100644 --- a/src/Tests/Examples/PostCtorOptionalWithBase.cs#PostCtorOptionalWithBase.g.verified.cs +++ b/src/Tests/Examples/PostCtorOptionalWithBase.cs#PostCtorOptionalWithBase.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorOptionalWithBase { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorOptionalWithBase(string value, string value0 = "dfault") : base(value) { Initialize(value0); diff --git a/src/Tests/Examples/PostCtorReturnsNonVoidTest.cs#PostCtorReturnsNonVoidTest.g.verified.cs b/src/Tests/Examples/PostCtorReturnsNonVoidTest.cs#PostCtorReturnsNonVoidTest.g.verified.cs index c0015046..ee7b5dd5 100644 --- a/src/Tests/Examples/PostCtorReturnsNonVoidTest.cs#PostCtorReturnsNonVoidTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorReturnsNonVoidTest.cs#PostCtorReturnsNonVoidTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorReturnsNonVoidTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorReturnsNonVoidTest(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/PostCtorWithArgumentsTest.cs#PostCtorWithArgumentsTest.g.verified.cs b/src/Tests/Examples/PostCtorWithArgumentsTest.cs#PostCtorWithArgumentsTest.g.verified.cs index c79c5f18..25e71fa1 100644 --- a/src/Tests/Examples/PostCtorWithArgumentsTest.cs#PostCtorWithArgumentsTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithArgumentsTest.cs#PostCtorWithArgumentsTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithArgumentsTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithArgumentsTest(global::IServiceA a, global::IServiceB b) { this.a = a; diff --git a/src/Tests/Examples/PostCtorWithGenericTest.cs#PostCtorWithGenericTest.g.verified.cs b/src/Tests/Examples/PostCtorWithGenericTest.cs#PostCtorWithGenericTest.g.verified.cs index 0d52ef1c..e17e5032 100644 --- a/src/Tests/Examples/PostCtorWithGenericTest.cs#PostCtorWithGenericTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithGenericTest.cs#PostCtorWithGenericTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithGenericTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithGenericTest(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/PostCtorWithInArgumentTest.cs#PostCtorWithInArgumentTest.g.verified.cs b/src/Tests/Examples/PostCtorWithInArgumentTest.cs#PostCtorWithInArgumentTest.g.verified.cs index 748a5f0c..0bfd1df4 100644 --- a/src/Tests/Examples/PostCtorWithInArgumentTest.cs#PostCtorWithInArgumentTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithInArgumentTest.cs#PostCtorWithInArgumentTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithInArgumentTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithInArgumentTest( global::IServiceA serviceA, global::IServiceB serviceB, diff --git a/src/Tests/Examples/PostCtorWithKeyedOutParameterTest.cs#PostCtorWithKeyedOutParameterTest.g.verified.cs b/src/Tests/Examples/PostCtorWithKeyedOutParameterTest.cs#PostCtorWithKeyedOutParameterTest.g.verified.cs index 46f2ad5c..496f7a68 100644 --- a/src/Tests/Examples/PostCtorWithKeyedOutParameterTest.cs#PostCtorWithKeyedOutParameterTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithKeyedOutParameterTest.cs#PostCtorWithKeyedOutParameterTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithKeyedOutParameterTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithKeyedOutParameterTest(global::IService service) { this._service = service; diff --git a/src/Tests/Examples/PostCtorWithKeyedServiceOutParameterTest.cs#PostCtorWithKeyedServiceOutParameterTest.g.verified.cs b/src/Tests/Examples/PostCtorWithKeyedServiceOutParameterTest.cs#PostCtorWithKeyedServiceOutParameterTest.g.verified.cs index e348c3cf..3cba7f57 100644 --- a/src/Tests/Examples/PostCtorWithKeyedServiceOutParameterTest.cs#PostCtorWithKeyedServiceOutParameterTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithKeyedServiceOutParameterTest.cs#PostCtorWithKeyedServiceOutParameterTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithKeyedServiceOutParameterTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithKeyedServiceOutParameterTest() { Initialize(out this._service); diff --git a/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#BaseClass.g.verified.cs b/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#BaseClass.g.verified.cs index 1229ac47..60d94b1b 100644 --- a/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#BaseClass.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#BaseClass.g.verified.cs @@ -7,10 +7,7 @@ partial class BaseClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public BaseClass( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("base")] global::IService service ) diff --git a/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#PostCtorWithKeyedServiceTest.g.verified.cs b/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#PostCtorWithKeyedServiceTest.g.verified.cs index 63e7bc7b..725741d3 100644 --- a/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#PostCtorWithKeyedServiceTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithKeyedServiceTest.cs#PostCtorWithKeyedServiceTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithKeyedServiceTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithKeyedServiceTest( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("base")] global::IService service, [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("field")] global::IService service0, diff --git a/src/Tests/Examples/PostCtorWithMSKeyedServiceTest.cs#PostCtorWithMSKeyedServiceTest.g.verified.cs b/src/Tests/Examples/PostCtorWithMSKeyedServiceTest.cs#PostCtorWithMSKeyedServiceTest.g.verified.cs index 65bd0479..1251a761 100644 --- a/src/Tests/Examples/PostCtorWithMSKeyedServiceTest.cs#PostCtorWithMSKeyedServiceTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithMSKeyedServiceTest.cs#PostCtorWithMSKeyedServiceTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithMSKeyedServiceTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithMSKeyedServiceTest( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("field")] global::IService service, [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("postconstruct")] global::IService postConstructService diff --git a/src/Tests/Examples/PostCtorWithOptionalArgumentsTest.cs#PostCtorWithOptionalArgumentsTest.g.verified.cs b/src/Tests/Examples/PostCtorWithOptionalArgumentsTest.cs#PostCtorWithOptionalArgumentsTest.g.verified.cs index 9f6a41f6..61cf55dc 100644 --- a/src/Tests/Examples/PostCtorWithOptionalArgumentsTest.cs#PostCtorWithOptionalArgumentsTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithOptionalArgumentsTest.cs#PostCtorWithOptionalArgumentsTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithOptionalArgumentsTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithOptionalArgumentsTest( global::IServiceA serviceA, global::IServiceB serviceB = default diff --git a/src/Tests/Examples/PostCtorWithOutArgumentTest.cs#PostCtorWithOutArgumentTest.g.verified.cs b/src/Tests/Examples/PostCtorWithOutArgumentTest.cs#PostCtorWithOutArgumentTest.g.verified.cs index f1a1a1cf..36732747 100644 --- a/src/Tests/Examples/PostCtorWithOutArgumentTest.cs#PostCtorWithOutArgumentTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithOutArgumentTest.cs#PostCtorWithOutArgumentTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithOutArgumentTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithOutArgumentTest( global::IServiceA serviceA, global::IServiceFactory serviceFactory diff --git a/src/Tests/Examples/PostCtorWithOutNoMatchProperty.cs#PostCtorWithOutNoMatchProperty.g.verified.cs b/src/Tests/Examples/PostCtorWithOutNoMatchProperty.cs#PostCtorWithOutNoMatchProperty.g.verified.cs index 11b3e001..c71ce36e 100644 --- a/src/Tests/Examples/PostCtorWithOutNoMatchProperty.cs#PostCtorWithOutNoMatchProperty.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithOutNoMatchProperty.cs#PostCtorWithOutNoMatchProperty.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithOutNoMatchProperty { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithOutNoMatchProperty(string value) { this.Value = value; diff --git a/src/Tests/Examples/PostCtorWithRefArgumentTest.cs#PostCtorWithRefArgumentTest.g.verified.cs b/src/Tests/Examples/PostCtorWithRefArgumentTest.cs#PostCtorWithRefArgumentTest.g.verified.cs index 85bdd351..a27cd618 100644 --- a/src/Tests/Examples/PostCtorWithRefArgumentTest.cs#PostCtorWithRefArgumentTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithRefArgumentTest.cs#PostCtorWithRefArgumentTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithRefArgumentTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithRefArgumentTest( global::IServiceA serviceA, global::IServiceFactory serviceFactory diff --git a/src/Tests/Examples/PostCtorWithRefReadonlyArgumentTest.cs#PostCtorWithRefReadonlyArgumentTest.g.verified.cs b/src/Tests/Examples/PostCtorWithRefReadonlyArgumentTest.cs#PostCtorWithRefReadonlyArgumentTest.g.verified.cs index b59f2ab4..86644fa5 100644 --- a/src/Tests/Examples/PostCtorWithRefReadonlyArgumentTest.cs#PostCtorWithRefReadonlyArgumentTest.g.verified.cs +++ b/src/Tests/Examples/PostCtorWithRefReadonlyArgumentTest.cs#PostCtorWithRefReadonlyArgumentTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PostCtorWithRefReadonlyArgumentTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostCtorWithRefReadonlyArgumentTest( global::IServiceA serviceA, global::IServiceB serviceB, diff --git a/src/Tests/Examples/RecordTest.cs#RecordStructTest.g.verified.cs b/src/Tests/Examples/RecordTest.cs#RecordStructTest.g.verified.cs index daa59fde..e5fce4ef 100644 --- a/src/Tests/Examples/RecordTest.cs#RecordStructTest.g.verified.cs +++ b/src/Tests/Examples/RecordTest.cs#RecordStructTest.g.verified.cs @@ -7,10 +7,7 @@ partial record struct RecordStructTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public RecordStructTest(int item) { this._item = item; diff --git a/src/Tests/Examples/RecordTest.cs#RecordTest.g.verified.cs b/src/Tests/Examples/RecordTest.cs#RecordTest.g.verified.cs index e882f767..f6c6dd2d 100644 --- a/src/Tests/Examples/RecordTest.cs#RecordTest.g.verified.cs +++ b/src/Tests/Examples/RecordTest.cs#RecordTest.g.verified.cs @@ -7,10 +7,7 @@ partial record RecordTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public RecordTest(int item) { this._item = item; diff --git a/src/Tests/Examples/SealedTest.cs#SealedTest.g.verified.cs b/src/Tests/Examples/SealedTest.cs#SealedTest.g.verified.cs index b867a565..93de5888 100644 --- a/src/Tests/Examples/SealedTest.cs#SealedTest.g.verified.cs +++ b/src/Tests/Examples/SealedTest.cs#SealedTest.g.verified.cs @@ -7,10 +7,7 @@ partial class SealedTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public SealedTest(int item) { this._item = item; diff --git a/src/Tests/Examples/StructTest.cs#StructTest.g.verified.cs b/src/Tests/Examples/StructTest.cs#StructTest.g.verified.cs index c3651637..453f4913 100644 --- a/src/Tests/Examples/StructTest.cs#StructTest.g.verified.cs +++ b/src/Tests/Examples/StructTest.cs#StructTest.g.verified.cs @@ -7,10 +7,7 @@ partial struct StructTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public StructTest(int item) { this._item = item; diff --git a/src/Tests/Examples/UniqueNameTest.cs#UniqueNameTest.g.verified.cs b/src/Tests/Examples/UniqueNameTest.cs#UniqueNameTest.g.verified.cs index 6d126a87..ca219e0e 100644 --- a/src/Tests/Examples/UniqueNameTest.cs#UniqueNameTest.g.verified.cs +++ b/src/Tests/Examples/UniqueNameTest.cs#UniqueNameTest.g.verified.cs @@ -7,10 +7,7 @@ partial class UniqueNameTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public UniqueNameTest( global::IServiceC serviceC, global::IServiceA serviceA, diff --git a/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTest2.g.verified.cs b/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTest2.g.verified.cs index 2b1c4bce..16a5729d 100644 --- a/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTest2.g.verified.cs +++ b/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTest2.g.verified.cs @@ -7,10 +7,7 @@ partial class UniqueNameTest2 { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public UniqueNameTest2(int value) : base(value) { this._value = value; diff --git a/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTestBase.g.verified.cs b/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTestBase.g.verified.cs index c07a4506..bc798bf0 100644 --- a/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTestBase.g.verified.cs +++ b/src/Tests/Examples/UniqueNameTest2.cs#UniqueNameTestBase.g.verified.cs @@ -7,10 +7,7 @@ partial class UniqueNameTestBase { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public UniqueNameTestBase(int value) { this._value = value; diff --git a/src/Tests/GeneratedAttributeTests.cs b/src/Tests/GeneratedAttributeTests.cs index 302411d1..5fee390d 100644 --- a/src/Tests/GeneratedAttributeTests.cs +++ b/src/Tests/GeneratedAttributeTests.cs @@ -46,6 +46,86 @@ await Assert.That(outputCompilationDiagnostics).IsEmpty() .ConfigureAwait(false); } + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task GenericAttributesCompileProperly(CompilationBuilderFactory builderFactory) + { + // The generic [Singleton<..>] attributes are behind their own symbol because generic + // attributes need C# 11. Without it they are never compiled by any other test. + var builder = builderFactory.Builder + .WithPreprocessorSymbols("AUTOCTOR_EMBED_ATTRIBUTES", "AUTOCTOR_EMBED_GENERIC_ATTRIBUTES"); + var compilation = builder.Build(nameof(GeneratedAttributeTests)); + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AttributeSourceGenerator()) + .Build(builder.ParseOptions) + .RunGeneratorsAndUpdateCompilation( + compilation, + out var outputCompilation, + out var diagnostics, + TestHelper.CancellationToken); + + var outputCompilationDiagnostics = outputCompilation + .GetDiagnostics(TestHelper.CancellationToken); + + await Assert.That(diagnostics).IsEmpty() + .ConfigureAwait(false); + await Assert.That(outputCompilationDiagnostics).IsEmpty() + .ConfigureAwait(false); + } + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task ServiceProviderUsagesCompileAgainstEmbeddedAttributes( + CompilationBuilderFactory builderFactory) + { + // Every named argument exists twice: on the package attribute, and on the copy this + // generator embeds. The other tests here compile the embedded attributes but never + // use them, so only a real registration catches a property that was added to one and + // not the other. + var builder = builderFactory.Builder + .AddCodes(""" + public interface IZone { } + public interface IClock { } + // Unregistered on purpose: without the fallback being read, this is ACTR012. + public class Clock(IZone zone) : IClock { public IZone Zone => zone; } + public interface IStamp { } + public class Stamp : IStamp { } + public interface IPlugin { } + public class Plugin : IPlugin { } + + [AutoCtor.ServiceProvider(Fallback = nameof(_host))] + [AutoCtor.Singleton(typeof(IClock), typeof(Clock), Key = "utc")] + [AutoCtor.Transient(typeof(IStamp), Factory = nameof(CreateStamp))] + [AutoCtor.ScanScoped(typeof(IPlugin), As = AutoCtor.ScanAs.Service | AutoCtor.ScanAs.Self, FromAssembliesOf = new[] { typeof(Container) })] + public sealed partial class Container + { + private readonly System.IServiceProvider _host; + public Container(System.IServiceProvider host) => _host = host; + private IStamp CreateStamp() => new Stamp(); + } + """) + .WithPreprocessorSymbols("AUTOCTOR_EMBED_ATTRIBUTES"); + var compilation = builder.Build(nameof(GeneratedAttributeTests)); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AttributeSourceGenerator()) + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(builder.ParseOptions) + .RunGeneratorsAndUpdateCompilation( + compilation, + out var outputCompilation, + out var diagnostics, + TestHelper.CancellationToken); + + var outputCompilationDiagnostics = outputCompilation + .GetDiagnostics(TestHelper.CancellationToken); + + await Assert.That(diagnostics).IsEmpty() + .ConfigureAwait(false); + await Assert.That(outputCompilationDiagnostics).IsEmpty() + .ConfigureAwait(false); + } + [Test] [ClassDataSource(Shared = SharedType.PerTestSession)] public async Task PreserveAttributesTest(CompilationBuilderFactory builderFactory) diff --git a/src/Tests/GeneratedAttributeTests.cs#AutoConstructAttribute.g.verified.cs b/src/Tests/GeneratedAttributeTests.cs#AutoConstructAttribute.g.verified.cs index 79f9627f..b210bff6 100644 --- a/src/Tests/GeneratedAttributeTests.cs#AutoConstructAttribute.g.verified.cs +++ b/src/Tests/GeneratedAttributeTests.cs#AutoConstructAttribute.g.verified.cs @@ -8,7 +8,6 @@ #if AUTOCTOR_EMBED_ATTRIBUTES namespace AutoCtor { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] internal enum GuardSetting { @@ -16,42 +15,119 @@ internal enum GuardSetting Disabled, Enabled } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Class | global::System.AttributeTargets.Struct, AllowMultiple = false, Inherited = false)] internal sealed class AutoConstructAttribute : global::System.Attribute { - public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) - { - } + public AutoConstructAttribute(GuardSetting guard = GuardSetting.Default) { } } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)] internal sealed class AutoPostConstructAttribute : global::System.Attribute { } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property, AllowMultiple = false, Inherited = false)] internal sealed class AutoConstructIgnoreAttribute : global::System.Attribute { } - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] [global::System.AttributeUsage(global::System.AttributeTargets.Field | global::System.AttributeTargets.Property | global::System.AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] internal sealed class AutoKeyedServiceAttribute : global::System.Attribute { public object Key { get; } public AutoKeyedServiceAttribute(object key) => Key = key; } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = false, Inherited = false)] + internal sealed class ServiceProviderAttribute : global::System.Attribute + { + public string Fallback { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + internal abstract class ServiceAttribute : global::System.Attribute + { + public object Key { get; set; } + public string Factory { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute + { + public SingletonAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute + { + public TransientAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute + { + public ScopedAttribute(global::System.Type service, global::System.Type implementation = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ImportAttribute : global::System.Attribute + { + public ImportAttribute(global::System.Type module) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.Flags] + internal enum ScanAs + { + Service = 1, + Self = 2, + ImplementedInterfaces = 4 + } + internal abstract class ScanAttribute : global::System.Attribute + { + public ScanAs As { get; set; } + public global::System.Type[] FromAssembliesOf { get; set; } + public string TypeNameFilter { get; set; } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanSingletonAttribute : ScanAttribute + { + public ScanSingletonAttribute(global::System.Type service = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanTransientAttribute : ScanAttribute + { + public ScanTransientAttribute(global::System.Type service = null) { } + } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScanScopedAttribute : ScanAttribute + { + public ScanScopedAttribute(global::System.Type service = null) { } + } + #if AUTOCTOR_EMBED_GENERIC_ATTRIBUTES + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class SingletonAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class TransientAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ScopedAttribute : ServiceAttribute { } + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + [global::System.AttributeUsage(global::System.AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + internal sealed class ImportAttribute : global::System.Attribute { } + #endif } #endif diff --git a/src/Tests/GuardExamples/DisabledGuard.cs#DisabledGuard.g.verified.cs b/src/Tests/GuardExamples/DisabledGuard.cs#DisabledGuard.g.verified.cs index 58d98351..78e32fe3 100644 --- a/src/Tests/GuardExamples/DisabledGuard.cs#DisabledGuard.g.verified.cs +++ b/src/Tests/GuardExamples/DisabledGuard.cs#DisabledGuard.g.verified.cs @@ -7,10 +7,7 @@ partial class DisabledGuard { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public DisabledGuard(string value) { this._value = value; diff --git a/src/Tests/GuardExamples/Guard.cs#Guard.g.verified.cs b/src/Tests/GuardExamples/Guard.cs#Guard.g.verified.cs index 5cff4b8e..f6acb9cc 100644 --- a/src/Tests/GuardExamples/Guard.cs#Guard.g.verified.cs +++ b/src/Tests/GuardExamples/Guard.cs#Guard.g.verified.cs @@ -7,10 +7,7 @@ partial class Guard { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Guard(string value) { this._value = value ?? throw new global::System.ArgumentNullException("value"); diff --git a/src/Tests/GuardExamples/NullableTypes.cs#NullableTypes.g.verified.cs b/src/Tests/GuardExamples/NullableTypes.cs#NullableTypes.g.verified.cs index 5242aeed..2e1bfbc4 100644 --- a/src/Tests/GuardExamples/NullableTypes.cs#NullableTypes.g.verified.cs +++ b/src/Tests/GuardExamples/NullableTypes.cs#NullableTypes.g.verified.cs @@ -7,10 +7,7 @@ partial class NullableTypes { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public NullableTypes(global::Service structService) { this._structService = structService; diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs new file mode 100644 index 00000000..de1194d5 --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs @@ -0,0 +1,17 @@ +using AutoCtor; + +// The service is registered, just not under the key being asked for. +[ServiceProvider] +[Singleton(Key = "primary")] +[Singleton] +public sealed partial class StoreProvider; + +public interface IStore; +public class PrimaryStore : IStore; + +public interface IStoreClient; + +public class StoreClient : IStoreClient +{ + public StoreClient([AutoKeyedService("secondary")] IStore store) { } +} diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs.verified.txt b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs.verified.txt new file mode 100644 index 00000000..f42fae99 --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR014_KeyedDependencyNotRegistered.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Singleton(Key = "primary")] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class StoreProvider; +*/ + : (5,1)-(5,37), + Message: No registration with key "secondary" was found for the dependency 'IStore' of 'StoreClient', + Severity: Error, + Descriptor: { + Id: ACTR014, + Title: Keyed service dependency is not registered, + MessageFormat: No registration with key {2} was found for the dependency '{0}' of '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs new file mode 100644 index 00000000..96bc04a4 --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs @@ -0,0 +1,32 @@ +using AutoCtor; + +// The provider outlives every scope it creates, so a singleton can never hold a scoped +// service. The second case reaches the scoped service through a transient, which is the +// same capture one step removed. +[ServiceProvider] +[Scoped] +[Singleton] +[Transient] +[Singleton] +public sealed partial class ScopedCaptureProvider; + +public interface ITenantContext; +public class TenantContext : ITenantContext; + +public interface ICacheDirect; +public class CacheDirect : ICacheDirect +{ + public CacheDirect(ITenantContext context) { } +} + +public interface IQuery; +public class Query : IQuery +{ + public Query(ITenantContext context) { } +} + +public interface ICacheIndirect; +public class CacheIndirect : ICacheIndirect +{ + public CacheIndirect(IQuery query) { } +} diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs.verified.txt b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs.verified.txt new file mode 100644 index 00000000..cbf891c4 --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingleton.cs.verified.txt @@ -0,0 +1,42 @@ +{ + Diagnostics: [ + { + Location: /* +[Scoped] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +[Transient] +*/ + : (7,1)-(7,37), + Message: The service 'TenantContext' requires a scope and cannot be injected into the singleton 'CacheDirect', + Severity: Error, + Descriptor: { + Id: ACTR019, + Title: Scoped service cannot be resolved outside a scope, + MessageFormat: The service '{0}' requires a scope and cannot be injected into the singleton '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + }, + { + Location: /* +[Transient] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class ScopedCaptureProvider; +*/ + : (9,1)-(9,41), + Message: The service 'Query' requires a scope and cannot be injected into the singleton 'CacheIndirect', + Severity: Error, + Descriptor: { + Id: ACTR019, + Title: Scoped service cannot be resolved outside a scope, + MessageFormat: The service '{0}' requires a scope and cannot be injected into the singleton '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs new file mode 100644 index 00000000..5f4dfb3f --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs @@ -0,0 +1,21 @@ +using AutoCtor; +using System.Collections.Generic; + +// The capture is through a collection: one element of IEnumerable is scoped, so a +// singleton taking the collection would hold it past the end of every scope. +[ServiceProvider] +[Singleton] +[Scoped] +[Singleton] +public sealed partial class HookRegistryProvider; + +public interface IHook; +public class StaticHook : IHook; +public class RequestHook : IHook; + +public interface IHookRegistry; + +public class HookRegistry : IHookRegistry +{ + public HookRegistry(IEnumerable hooks) { } +} diff --git a/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs.verified.txt b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs.verified.txt new file mode 100644 index 00000000..beb39e52 --- /dev/null +++ b/src/Tests/IoCAbstractionsDiagnosticExamples/ACTR019_ScopedInSingletonCollection.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Scoped] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class HookRegistryProvider; +*/ + : (8,1)-(8,39), + Message: The service 'RequestHook' requires a scope and cannot be injected into the singleton 'HookRegistry', + Severity: Error, + Descriptor: { + Id: ACTR019, + Title: Scoped service cannot be resolved outside a scope, + MessageFormat: The service '{0}' requires a scope and cannot be injected into the singleton '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs b/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs new file mode 100644 index 00000000..3d4b9331 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs @@ -0,0 +1,21 @@ +using AutoCtor; +using System.Collections.Generic; + +// One scoped element makes the whole collection scope bound, so it lives on the Scope and +// the provider does not expose it at all. +[ServiceProvider] +[Singleton] +[Scoped] +[Scoped] +public sealed partial class DispatcherProvider; + +public interface IListener; +public class GlobalListener : IListener; +public class RequestListener : IListener; + +public interface IDispatcher; + +public class Dispatcher : IDispatcher +{ + public Dispatcher(IEnumerable listeners) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs#DispatcherProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs#DispatcherProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..680b436e --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/EnumerableScoped.cs#DispatcherProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,282 @@ +//HintName: DispatcherProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DispatcherProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IListener? _S0; + private global::IListener S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::GlobalListener()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DispatcherProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DispatcherProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DispatcherProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver>, + DispatcherProvider.IResolver>, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver, + DispatcherProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DispatcherProvider _root; + public Scope(DispatcherProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IListener S0() => _root.S0(); + private volatile global::IListener? _S1; + private global::IListener S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::RequestListener()); + } + private volatile global::IDispatcher? _S2; + private global::IDispatcher S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Dispatcher(new global::IListener[] { S0(), S1() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IListener)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IDispatcher)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IListener[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDispatcher[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DispatcherProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DispatcherProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IListener DispatcherProvider.IResolver.Get() + => S1(); + global::IDispatcher DispatcherProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable DispatcherProvider.IResolver>.Get() + => new global::IListener[] { S0(), S1() }; + global::System.Collections.Generic.IEnumerable DispatcherProvider.IResolver>.Get() + => new global::IDispatcher[] { S2() }; + global::System.IServiceProvider DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DispatcherProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DispatcherProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs b/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs new file mode 100644 index 00000000..c26fb974 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs @@ -0,0 +1,25 @@ +using AutoCtor; + +// A factory parameter typed IServiceProvider is handed the container the service is being +// built in -- the provider for the singleton below, the scope for the scoped one. It is the +// only parameter that does not come from a registration. +[ServiceProvider] +[Singleton(Factory = nameof(CreateRegistry))] +[Scoped(Factory = nameof(CreateAudit))] +public sealed partial class AuditProvider +{ + private IRegistry CreateRegistry(System.IServiceProvider services) => new Registry(services); + + private IAudit CreateAudit(System.IServiceProvider services) => new Audit(services); +} + +public interface IRegistry; +public class Registry(System.IServiceProvider services) : IRegistry +{ + public System.IServiceProvider Services => services; +} +public interface IAudit; +public class Audit(System.IServiceProvider services) : IAudit +{ + public System.IServiceProvider Services => services; +} diff --git a/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs#AuditProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs#AuditProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..1064f9ae --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FactoryContainerParameter.cs#AuditProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,283 @@ +//HintName: AuditProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class AuditProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + AuditProvider.IResolver, + AuditProvider.IResolver>, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IRegistry? _S0; + private global::IRegistry S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(this.CreateRegistry(this))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IRegistry)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRegistry[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AuditProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AuditProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IRegistry AuditProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable AuditProvider.IResolver>.Get() + => new global::IRegistry[] { S0() }; + global::System.IServiceProvider AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService AuditProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver>, + AuditProvider.IResolver>, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver, + AuditProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly AuditProvider _root; + public Scope(AuditProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IRegistry S0() => _root.S0(); + private volatile global::IAudit? _S1; + private global::IAudit S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(_root.CreateAudit(this))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IRegistry)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IAudit)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRegistry[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAudit[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AuditProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AuditProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IRegistry AuditProvider.IResolver.Get() + => S0(); + global::IAudit AuditProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable AuditProvider.IResolver>.Get() + => new global::IRegistry[] { S0() }; + global::System.Collections.Generic.IEnumerable AuditProvider.IResolver>.Get() + => new global::IAudit[] { S1() }; + global::System.IServiceProvider AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider AuditProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService AuditProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs b/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs new file mode 100644 index 00000000..61be39fd --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs @@ -0,0 +1,20 @@ +using AutoCtor; + +// A scoped service is built inside the scope, where the factory member is not: it is +// declared on the provider, so the scope calls it through _root while the arguments still +// resolve against the scope. +[ServiceProvider] +[Scoped] +[Scoped(Factory = nameof(CreateUnitOfWork))] +public sealed partial class UnitOfWorkProvider +{ + private IUnitOfWork CreateUnitOfWork(ISession session) => new UnitOfWork(session); +} + +public interface ISession; +public class Session : ISession; +public interface IUnitOfWork; +public class UnitOfWork(ISession session) : IUnitOfWork +{ + public ISession Session => session; +} diff --git a/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs#UnitOfWorkProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs#UnitOfWorkProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..7ce33f9f --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FactoryScopedService.cs#UnitOfWorkProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,272 @@ +//HintName: UnitOfWorkProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class UnitOfWorkProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is UnitOfWorkProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is UnitOfWorkProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService UnitOfWorkProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver>, + UnitOfWorkProvider.IResolver>, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver, + UnitOfWorkProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly UnitOfWorkProvider _root; + public Scope(UnitOfWorkProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private volatile global::ISession? _S0; + private global::ISession S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Session()); + } + private volatile global::IUnitOfWork? _S1; + private global::IUnitOfWork S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(_root.CreateUnitOfWork(S0()))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISession)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IUnitOfWork)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISession[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IUnitOfWork[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is UnitOfWorkProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is UnitOfWorkProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ISession UnitOfWorkProvider.IResolver.Get() + => S0(); + global::IUnitOfWork UnitOfWorkProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable UnitOfWorkProvider.IResolver>.Get() + => new global::ISession[] { S0() }; + global::System.Collections.Generic.IEnumerable UnitOfWorkProvider.IResolver>.Get() + => new global::IUnitOfWork[] { S1() }; + global::System.IServiceProvider UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider UnitOfWorkProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService UnitOfWorkProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs b/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs new file mode 100644 index 00000000..ba93eb27 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs @@ -0,0 +1,30 @@ +using System; +using AutoCtor; +using Microsoft.Extensions.DependencyInjection; + +// Where the abstractions package is present the fallback answers the keyed questions too, +// and the IsService probes forward to it -- a host asks those before it resolves, so a +// fallback that answered GetService and not IsService would never be reached through one. +// The member is declared on the provider, so the scope reaches it through _root. +// +// A keyed constructor dependency the fallback answers is why UnitOfWork compiles: nothing +// here is registered under "audit", so the call goes to GetRequiredKeyedService, which is +// emitted for it even though every keyed chain on this provider is otherwise empty. +[ServiceProvider(Fallback = nameof(Host))] +[Singleton(Key = "primary")] +[Scoped] +public sealed partial class DispatchProvider +{ + public IServiceProvider Host { get; } + + public DispatchProvider(IServiceProvider host) => Host = host; +} + +public interface IHandler; +public class PrimaryHandler : IHandler; +public interface IAuditSink; +public interface IUnitOfWork; +public class UnitOfWork([FromKeyedServices("audit")] IAuditSink sink) : IUnitOfWork +{ + public IAuditSink Sink => sink; +} diff --git a/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs#DispatchProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs#DispatchProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..693a42fc --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/FallbackKeyedAndScoped.cs#DispatchProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,276 @@ +//HintName: DispatchProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DispatchProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IHandler? _S0; + private global::IHandler S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PrimaryHandler()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IHandler) && object.Equals(key, "primary")) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "primary")) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + return null; + } + + private global::System.IServiceProvider? Fallback => this.Host; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DispatchProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DispatchProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DispatchProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + DispatchProvider.IResolver, + DispatchProvider.IResolver>, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver, + DispatchProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DispatchProvider _root; + public Scope(DispatchProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IHandler S0() => _root.S0(); + private volatile global::IUnitOfWork? _S1; + private global::IUnitOfWork S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::UnitOfWork(Required(GetKeyedService(typeof(global::IAuditSink), "audit")))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IUnitOfWork)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IUnitOfWork[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IHandler) && object.Equals(key, "primary")) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "primary")) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + return null; + } + + private global::System.IServiceProvider? _fallback; + private global::System.IServiceProvider? Fallback { get { lock (_lock) return _fallback ??= _root.Host is global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory f ? Track(f.CreateScope()).ServiceProvider : _root.Host; } } + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DispatchProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DispatchProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IUnitOfWork DispatchProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable DispatchProvider.IResolver>.Get() + => new global::IUnitOfWork[] { S1() }; + global::System.IServiceProvider DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DispatchProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DispatchProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs b/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs new file mode 100644 index 00000000..e2c3278e --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs @@ -0,0 +1,19 @@ +using AutoCtor; + +// KeyedService.AnyKey is a key a caller passes to a resolve, never one a service is +// registered with. It asks for every keyed registration of a service type whatever the key, +// in registration order -- the unkeyed registration is not one of them. Only the collection +// answers it, so GetKeyedService(typeof(IHandler), AnyKey) finds nothing, as it does in +// Microsoft's container. +[ServiceProvider] +[Singleton] +[Singleton(Key = "email")] +[Singleton(Key = "sms")] +[Singleton(Key = "email")] +public sealed partial class HandlerProvider; + +public interface IHandler; +public class DefaultHandler : IHandler; +public class EmailHandler : IHandler; +public class SmsHandler : IHandler; +public class PushHandler : IHandler; diff --git a/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs#HandlerProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs#HandlerProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..7823222b --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedAnyKey.cs#HandlerProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,314 @@ +//HintName: HandlerProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class HandlerProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + HandlerProvider.IResolver, + HandlerProvider.IResolver>, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IHandler? _S0; + private global::IHandler S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::DefaultHandler()); + } + private volatile global::IHandler? _S1; + private global::IHandler S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::EmailHandler()); + } + private volatile global::IHandler? _S2; + private global::IHandler S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::SmsHandler()); + } + private volatile global::IHandler? _S3; + private global::IHandler S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::PushHandler()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IHandler) && object.Equals(key, "email")) + return probe ? this : (object)(S3()); + if (type == typeof(global::IHandler) && object.Equals(key, "sms")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "email")) + return probe ? this : (object)(new global::IHandler[] { S1(), S3() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sms")) + return probe ? this : (object)(new global::IHandler[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IHandler[] { S1(), S2(), S3() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IHandler HandlerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable HandlerProvider.IResolver>.Get() + => new global::IHandler[] { S0() }; + global::System.IServiceProvider HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HandlerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + HandlerProvider.IResolver, + HandlerProvider.IResolver>, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver, + HandlerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly HandlerProvider _root; + public Scope(HandlerProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IHandler S0() => _root.S0(); + private global::IHandler S1() => _root.S1(); + private global::IHandler S2() => _root.S2(); + private global::IHandler S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IHandler) && object.Equals(key, "email")) + return probe ? this : (object)(S3()); + if (type == typeof(global::IHandler) && object.Equals(key, "sms")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "email")) + return probe ? this : (object)(new global::IHandler[] { S1(), S3() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sms")) + return probe ? this : (object)(new global::IHandler[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IHandler[] { S1(), S2(), S3() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IHandler HandlerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable HandlerProvider.IResolver>.Get() + => new global::IHandler[] { S0() }; + global::System.IServiceProvider HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HandlerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HandlerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs b/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs new file mode 100644 index 00000000..a7e126d0 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs @@ -0,0 +1,14 @@ +using AutoCtor; + +// Keys make separate slots, so the same service can have an unkeyed registration and one +// per key. Each keyed winner gets its own getter named after the key. +[ServiceProvider] +[Singleton] +[Singleton(Key = "redis")] +[Singleton(Key = "memory")] +public sealed partial class CacheProvider; + +public interface ICache; +public class DefaultCache : ICache; +public class RedisCache : ICache; +public class MemoryCache : ICache; diff --git a/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs#CacheProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs#CacheProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..4d65e68c --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedBasic.cs#CacheProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,304 @@ +//HintName: CacheProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class CacheProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + CacheProvider.IResolver, + CacheProvider.IResolver>, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ICache? _S0; + private global::ICache S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::DefaultCache()); + } + private volatile global::ICache? _S1; + private global::ICache S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::RedisCache()); + } + private volatile global::ICache? _S2; + private global::ICache S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::MemoryCache()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICache)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICache[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::ICache) && object.Equals(key, "redis")) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICache) && object.Equals(key, "memory")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "redis")) + return probe ? this : (object)(new global::ICache[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "memory")) + return probe ? this : (object)(new global::ICache[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::ICache[] { S1(), S2() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ICache CacheProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::ICache[] { S0() }; + global::System.IServiceProvider CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService CacheProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + CacheProvider.IResolver, + CacheProvider.IResolver>, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly CacheProvider _root; + public Scope(CacheProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ICache S0() => _root.S0(); + private global::ICache S1() => _root.S1(); + private global::ICache S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICache)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICache[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::ICache) && object.Equals(key, "redis")) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICache) && object.Equals(key, "memory")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "redis")) + return probe ? this : (object)(new global::ICache[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "memory")) + return probe ? this : (object)(new global::ICache[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::ICache[] { S1(), S2() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ICache CacheProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::ICache[] { S0() }; + global::System.IServiceProvider CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider CacheProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService CacheProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs b/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs new file mode 100644 index 00000000..ad9e64c4 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs @@ -0,0 +1,23 @@ +using AutoCtor; +using System.Collections.Generic; + +// Keyed registrations are kept out of the unkeyed collection and grouped by key instead, +// so IEnumerable here has one element, not three. +[ServiceProvider] +[Singleton] +[Singleton(Key = "strict")] +[Singleton(Key = "strict")] +[Singleton] +public sealed partial class PolicyProvider; + +public interface IPolicy; +public class DefaultPolicy : IPolicy; +public class StrictPolicy : IPolicy; +public class LoosePolicy : IPolicy; + +public interface IPolicyHost; + +public class PolicyHost : IPolicyHost +{ + public PolicyHost(IEnumerable unkeyed) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs#PolicyProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs#PolicyProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..5d3ac892 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedEnumerable.cs#PolicyProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,326 @@ +//HintName: PolicyProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PolicyProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver>, + PolicyProvider.IResolver>, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPolicy? _S0; + private global::IPolicy S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::DefaultPolicy()); + } + private volatile global::IPolicy? _S1; + private global::IPolicy S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::StrictPolicy()); + } + private volatile global::IPolicy? _S2; + private global::IPolicy S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::LoosePolicy()); + } + private volatile global::IPolicyHost? _S3; + private global::IPolicyHost S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::PolicyHost(new global::IPolicy[] { S0() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPolicy)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPolicyHost)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPolicy[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPolicyHost[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IPolicy) && object.Equals(key, "strict")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "strict")) + return probe ? this : (object)(new global::IPolicy[] { S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IPolicy[] { S1(), S2() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PolicyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PolicyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPolicy PolicyProvider.IResolver.Get() + => S0(); + global::IPolicyHost PolicyProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable PolicyProvider.IResolver>.Get() + => new global::IPolicy[] { S0() }; + global::System.Collections.Generic.IEnumerable PolicyProvider.IResolver>.Get() + => new global::IPolicyHost[] { S3() }; + global::System.IServiceProvider PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PolicyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver>, + PolicyProvider.IResolver>, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver, + PolicyProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PolicyProvider _root; + public Scope(PolicyProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPolicy S0() => _root.S0(); + private global::IPolicy S1() => _root.S1(); + private global::IPolicy S2() => _root.S2(); + private global::IPolicyHost S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPolicy)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPolicyHost)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPolicy[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPolicyHost[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IPolicy) && object.Equals(key, "strict")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "strict")) + return probe ? this : (object)(new global::IPolicy[] { S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IPolicy[] { S1(), S2() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PolicyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PolicyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPolicy PolicyProvider.IResolver.Get() + => S0(); + global::IPolicyHost PolicyProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable PolicyProvider.IResolver>.Get() + => new global::IPolicy[] { S0() }; + global::System.Collections.Generic.IEnumerable PolicyProvider.IResolver>.Get() + => new global::IPolicyHost[] { S3() }; + global::System.IServiceProvider PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PolicyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PolicyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs b/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs new file mode 100644 index 00000000..38b49922 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs @@ -0,0 +1,23 @@ +using AutoCtor; + +// A keyed parameter resolves against registrations carrying the same key. Both AutoCtor's +// own attribute and Microsoft's FromKeyedServices are honoured. +[ServiceProvider] +[Singleton(Key = "primary")] +[Singleton(Key = "backup")] +[Singleton] +public sealed partial class QueueProvider; + +public interface IQueue; +public class PrimaryQueue : IQueue; +public class BackupQueue : IQueue; + +public interface IWorker; + +public class Worker : IWorker +{ + public Worker( + [AutoKeyedService("primary")] IQueue primary, + [AutoKeyedService("backup")] IQueue backup) + { } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs#QueueProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs#QueueProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..d46cae93 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedInjection.cs#QueueProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,304 @@ +//HintName: QueueProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class QueueProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + QueueProvider.IResolver, + QueueProvider.IResolver>, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IQueue? _S0; + private global::IQueue S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PrimaryQueue()); + } + private volatile global::IQueue? _S1; + private global::IQueue S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::BackupQueue()); + } + private volatile global::IWorker? _S2; + private global::IWorker S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Worker(S0(), S1())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IWorker)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IWorker[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IQueue) && object.Equals(key, "primary")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IQueue) && object.Equals(key, "backup")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "primary")) + return probe ? this : (object)(new global::IQueue[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "backup")) + return probe ? this : (object)(new global::IQueue[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IQueue[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is QueueProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is QueueProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IWorker QueueProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable QueueProvider.IResolver>.Get() + => new global::IWorker[] { S2() }; + global::System.IServiceProvider QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService QueueProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + QueueProvider.IResolver, + QueueProvider.IResolver>, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver, + QueueProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly QueueProvider _root; + public Scope(QueueProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IQueue S0() => _root.S0(); + private global::IQueue S1() => _root.S1(); + private global::IWorker S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IWorker)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IWorker[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IQueue) && object.Equals(key, "primary")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IQueue) && object.Equals(key, "backup")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "primary")) + return probe ? this : (object)(new global::IQueue[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "backup")) + return probe ? this : (object)(new global::IQueue[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IQueue[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is QueueProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is QueueProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IWorker QueueProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable QueueProvider.IResolver>.Get() + => new global::IWorker[] { S2() }; + global::System.IServiceProvider QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider QueueProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService QueueProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs b/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs new file mode 100644 index 00000000..b6b94509 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +// A key is orthogonal to lifetime -- each keyed slot keeps its own. +[ServiceProvider] +[Singleton(Key = "cached")] +[Transient(Key = "fresh")] +[Scoped(Key = "scoped")] +public sealed partial class ChannelProvider; + +public interface IChannel; +public class CachedChannel : IChannel; +public class FreshChannel : IChannel; +public class ScopedChannel : IChannel; diff --git a/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs#ChannelProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs#ChannelProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9b70f37c --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedLifetimes.cs#ChannelProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,277 @@ +//HintName: ChannelProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ChannelProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IChannel? _S0; + private global::IChannel S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::CachedChannel()); + } + private global::IChannel S1() => new global::FreshChannel(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IChannel) && object.Equals(key, "cached")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChannel) && object.Equals(key, "fresh")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "cached")) + return probe ? this : (object)(new global::IChannel[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "fresh")) + return probe ? this : (object)(new global::IChannel[] { S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ChannelProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ChannelProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ChannelProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver, + ChannelProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ChannelProvider _root; + public Scope(ChannelProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IChannel S0() => _root.S0(); + private global::IChannel S1() => new global::FreshChannel(); + private volatile global::IChannel? _S2; + private global::IChannel S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::ScopedChannel()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IChannel) && object.Equals(key, "cached")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChannel) && object.Equals(key, "fresh")) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChannel) && object.Equals(key, "scoped")) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "cached")) + return probe ? this : (object)(new global::IChannel[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "fresh")) + return probe ? this : (object)(new global::IChannel[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "scoped")) + return probe ? this : (object)(new global::IChannel[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IChannel[] { S0(), S1(), S2() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ChannelProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ChannelProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::System.IServiceProvider ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ChannelProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ChannelProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs b/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs new file mode 100644 index 00000000..ee232e48 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// A key that cannot be spelled as an identifier gets no strongly typed getter; it stays +// reachable through GetKeyedService. +[ServiceProvider] +[Singleton(Key = "api/v1")] +[Singleton(Key = 7)] +public sealed partial class EndpointProvider; + +public interface IEndpoint; +public class PublicEndpoint : IEndpoint; +public class AdminEndpoint : IEndpoint; diff --git a/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs#EndpointProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs#EndpointProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..2ad0db8d --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedNonIdentifierKey.cs#EndpointProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,274 @@ +//HintName: EndpointProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class EndpointProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IEndpoint? _S0; + private global::IEndpoint S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PublicEndpoint()); + } + private volatile global::IEndpoint? _S1; + private global::IEndpoint S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::AdminEndpoint()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IEndpoint) && object.Equals(key, "api/v1")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IEndpoint) && object.Equals(key, 7)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "api/v1")) + return probe ? this : (object)(new global::IEndpoint[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, 7)) + return probe ? this : (object)(new global::IEndpoint[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IEndpoint[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EndpointProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EndpointProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService EndpointProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver, + EndpointProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly EndpointProvider _root; + public Scope(EndpointProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IEndpoint S0() => _root.S0(); + private global::IEndpoint S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IEndpoint) && object.Equals(key, "api/v1")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IEndpoint) && object.Equals(key, 7)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "api/v1")) + return probe ? this : (object)(new global::IEndpoint[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, 7)) + return probe ? this : (object)(new global::IEndpoint[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IEndpoint[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EndpointProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EndpointProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::System.IServiceProvider EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider EndpointProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService EndpointProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs b/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs new file mode 100644 index 00000000..58c5e2e3 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton(typeof(IKeyedTypeofService), typeof(KeyedTypeofService), Key = "alpha")] +public sealed partial class KeyedTypeofProvider; + +public interface IKeyedTypeofService; +public class KeyedTypeofService : IKeyedTypeofService; diff --git a/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs#KeyedTypeofProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs#KeyedTypeofProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..b3c2efdd --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/KeyedTypeofAttribute.cs#KeyedTypeofProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,256 @@ +//HintName: KeyedTypeofProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class KeyedTypeofProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IKeyedTypeofService? _S0; + private global::IKeyedTypeofService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::KeyedTypeofService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IKeyedTypeofService) && object.Equals(key, "alpha")) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "alpha")) + return probe ? this : (object)(new global::IKeyedTypeofService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IKeyedTypeofService[] { S0() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is KeyedTypeofProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is KeyedTypeofProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService KeyedTypeofProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver, + KeyedTypeofProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly KeyedTypeofProvider _root; + public Scope(KeyedTypeofProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IKeyedTypeofService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IKeyedTypeofService) && object.Equals(key, "alpha")) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "alpha")) + return probe ? this : (object)(new global::IKeyedTypeofService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IKeyedTypeofService[] { S0() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is KeyedTypeofProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is KeyedTypeofProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::System.IServiceProvider KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider KeyedTypeofProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService KeyedTypeofProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs b/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs new file mode 100644 index 00000000..755bc4db --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs @@ -0,0 +1,25 @@ +using AutoCtor; +using Microsoft.Extensions.DependencyInjection; + +// Registering IServiceScopeFactory yourself, when the provider already answers it. The +// registration wins, and only one resolver is emitted for the type. +[ServiceProvider] +[Singleton] +[Singleton] +[Scoped] +public sealed partial class ScopeFactoryProvider; + +public interface ILogSink; +public class LogSink : ILogSink; + +public interface IRequestState; + +public class RequestState : IRequestState +{ + public RequestState(ILogSink sink) { } +} + +public class CustomScopeFactory : IServiceScopeFactory +{ + public IServiceScope CreateScope() => null!; +} diff --git a/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs#ScopeFactoryProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs#ScopeFactoryProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..e69b8565 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ResolverScopeFactory.cs#ScopeFactoryProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,307 @@ +//HintName: ScopeFactoryProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopeFactoryProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver>, + ScopeFactoryProvider.IResolver>, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ILogSink? _S0; + private global::ILogSink S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::LogSink()); + } + private volatile global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory? _S1; + private global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::CustomScopeFactory()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILogSink)) + return probe ? this : (object)(S0()); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILogSink[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopeFactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopeFactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ILogSink ScopeFactoryProvider.IResolver.Get() + => S0(); + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopeFactoryProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopeFactoryProvider.IResolver>.Get() + => new global::ILogSink[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopeFactoryProvider.IResolver>.Get() + => new global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory[] { S1() }; + global::System.IServiceProvider ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopeFactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver>, + ScopeFactoryProvider.IResolver>, + ScopeFactoryProvider.IResolver>, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver, + ScopeFactoryProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopeFactoryProvider _root; + public Scope(ScopeFactoryProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ILogSink S0() => _root.S0(); + private global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory S1() => _root.S1(); + private volatile global::IRequestState? _S2; + private global::IRequestState S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::RequestState(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILogSink)) + return probe ? this : (object)(S0()); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRequestState)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILogSink[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequestState[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopeFactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopeFactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ILogSink ScopeFactoryProvider.IResolver.Get() + => S0(); + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopeFactoryProvider.IResolver.Get() + => S1(); + global::IRequestState ScopeFactoryProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ScopeFactoryProvider.IResolver>.Get() + => new global::ILogSink[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopeFactoryProvider.IResolver>.Get() + => new global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory[] { S1() }; + global::System.Collections.Generic.IEnumerable ScopeFactoryProvider.IResolver>.Get() + => new global::IRequestState[] { S2() }; + global::System.IServiceProvider ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopeFactoryProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopeFactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs b/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs new file mode 100644 index 00000000..de07ec8f --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs @@ -0,0 +1,22 @@ +using AutoCtor; + +// No [Scoped] registration, and the provider is still an IServiceScopeFactory handing back +// a full IServiceScope. Anything that already understands Microsoft's container can take +// this one, whatever it happens to have registered. The scope owns nothing of its own here, +// so every getter reads through to the provider -- except the disposable transient, which +// whoever handed it out is the one to dispose. +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class IndexProvider; + +public interface IIndex; +public class SearchIndex : IIndex; + +public interface ILease; + +public class Lease : ILease, System.IDisposable +{ + public Lease(IIndex index) { } + public void Dispose() { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs#IndexProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs#IndexProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..ba15edab --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopeWithoutScopedServices.cs#IndexProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,286 @@ +//HintName: IndexProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class IndexProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver>, + IndexProvider.IResolver>, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IIndex? _S0; + private global::IIndex S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SearchIndex()); + } + private global::ILease S1() => Track(new global::Lease(S0())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IIndex)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILease)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IIndex[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILease[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is IndexProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is IndexProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IIndex IndexProvider.IResolver.Get() + => S0(); + global::ILease IndexProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable IndexProvider.IResolver>.Get() + => new global::IIndex[] { S0() }; + global::System.Collections.Generic.IEnumerable IndexProvider.IResolver>.Get() + => new global::ILease[] { S1() }; + global::System.IServiceProvider IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService IndexProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver>, + IndexProvider.IResolver>, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver, + IndexProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly IndexProvider _root; + public Scope(IndexProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IIndex S0() => _root.S0(); + private global::ILease S1() => Track(new global::Lease(S0())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IIndex)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILease)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IIndex[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILease[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is IndexProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is IndexProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IIndex IndexProvider.IResolver.Get() + => S0(); + global::ILease IndexProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable IndexProvider.IResolver>.Get() + => new global::IIndex[] { S0() }; + global::System.Collections.Generic.IEnumerable IndexProvider.IResolver>.Get() + => new global::ILease[] { S1() }; + global::System.IServiceProvider IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider IndexProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService IndexProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs b/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs new file mode 100644 index 00000000..6a414c56 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs @@ -0,0 +1,20 @@ +using AutoCtor; + +// A scoped service holds its transient dependency for as long as the scope lives. That is +// what constructor injection of a transient means, and the scope releases it, so this is +// deliberately not reported -- unlike the same capture by a singleton, which holds the +// instance for the life of the process and is ACTR018. +[ServiceProvider] +[Scoped] +[Transient] +public sealed partial class ScopedCaptureTransientProvider; + +public interface IScopedWorker; +public class ScopedWorker : IScopedWorker; + +public interface IScopedHolder; + +public class ScopedHolder : IScopedHolder +{ + public ScopedHolder(IScopedWorker worker) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs#ScopedCaptureTransientProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs#ScopedCaptureTransientProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..b45e4f09 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedCapturesTransient.cs#ScopedCaptureTransientProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,275 @@ +//HintName: ScopedCaptureTransientProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopedCaptureTransientProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver>, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IScopedWorker S1() => new global::ScopedWorker(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScopedWorker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedWorker[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedCaptureTransientProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedCaptureTransientProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IScopedWorker ScopedCaptureTransientProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopedCaptureTransientProvider.IResolver>.Get() + => new global::IScopedWorker[] { S1() }; + global::System.IServiceProvider ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedCaptureTransientProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver>, + ScopedCaptureTransientProvider.IResolver>, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver, + ScopedCaptureTransientProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopedCaptureTransientProvider _root; + public Scope(ScopedCaptureTransientProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private volatile global::IScopedHolder? _S0; + private global::IScopedHolder S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ScopedHolder(S1())); + } + private global::IScopedWorker S1() => new global::ScopedWorker(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScopedHolder)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IScopedWorker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedHolder[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedWorker[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedCaptureTransientProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedCaptureTransientProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IScopedHolder ScopedCaptureTransientProvider.IResolver.Get() + => S0(); + global::IScopedWorker ScopedCaptureTransientProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopedCaptureTransientProvider.IResolver>.Get() + => new global::IScopedHolder[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopedCaptureTransientProvider.IResolver>.Get() + => new global::IScopedWorker[] { S1() }; + global::System.IServiceProvider ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedCaptureTransientProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedCaptureTransientProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs b/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs new file mode 100644 index 00000000..4079ac10 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs @@ -0,0 +1,17 @@ +using AutoCtor; + +// No singletons at all, so the provider itself has nothing to construct and everything +// lives on the scope. +[ServiceProvider] +[Scoped] +[Scoped] +public sealed partial class ScopedOnlyProvider; + +public interface IScopedOnlyOne; +public interface IScopedOnlyTwo; +public class ScopedOnlyOne : IScopedOnlyOne; + +public class ScopedOnlyTwo : IScopedOnlyTwo +{ + public ScopedOnlyTwo(IScopedOnlyOne one) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs#ScopedOnlyProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs#ScopedOnlyProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..faef49ab --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedOnly.cs#ScopedOnlyProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,272 @@ +//HintName: ScopedOnlyProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopedOnlyProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedOnlyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedOnlyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedOnlyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver>, + ScopedOnlyProvider.IResolver>, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver, + ScopedOnlyProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopedOnlyProvider _root; + public Scope(ScopedOnlyProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private volatile global::IScopedOnlyOne? _S0; + private global::IScopedOnlyOne S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ScopedOnlyOne()); + } + private volatile global::IScopedOnlyTwo? _S1; + private global::IScopedOnlyTwo S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ScopedOnlyTwo(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScopedOnlyOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IScopedOnlyTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedOnlyOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedOnlyTwo[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedOnlyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedOnlyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IScopedOnlyOne ScopedOnlyProvider.IResolver.Get() + => S0(); + global::IScopedOnlyTwo ScopedOnlyProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopedOnlyProvider.IResolver>.Get() + => new global::IScopedOnlyOne[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopedOnlyProvider.IResolver>.Get() + => new global::IScopedOnlyTwo[] { S1() }; + global::System.IServiceProvider ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedOnlyProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedOnlyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs b/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs new file mode 100644 index 00000000..9530efc9 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs @@ -0,0 +1,18 @@ +using AutoCtor; + +// The provider gains CreateScope and a nested Scope type; the scoped service is built +// eagerly by the scope, not the provider. +[ServiceProvider] +[Singleton] +[Scoped] +public sealed partial class ScopedSimpleProvider; + +public interface IAppConfig; +public class AppConfig : IAppConfig; + +public interface IUnitOfWork; + +public class UnitOfWork : IUnitOfWork +{ + public UnitOfWork(IAppConfig config) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs#ScopedSimpleProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs#ScopedSimpleProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..475e497b --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedSimple.cs#ScopedSimpleProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,283 @@ +//HintName: ScopedSimpleProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopedSimpleProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver>, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IAppConfig? _S0; + private global::IAppConfig S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::AppConfig()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAppConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAppConfig[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedSimpleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedSimpleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IAppConfig ScopedSimpleProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable ScopedSimpleProvider.IResolver>.Get() + => new global::IAppConfig[] { S0() }; + global::System.IServiceProvider ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedSimpleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver>, + ScopedSimpleProvider.IResolver>, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver, + ScopedSimpleProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopedSimpleProvider _root; + public Scope(ScopedSimpleProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IAppConfig S0() => _root.S0(); + private volatile global::IUnitOfWork? _S1; + private global::IUnitOfWork S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::UnitOfWork(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAppConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IUnitOfWork)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAppConfig[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IUnitOfWork[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedSimpleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedSimpleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IAppConfig ScopedSimpleProvider.IResolver.Get() + => S0(); + global::IUnitOfWork ScopedSimpleProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopedSimpleProvider.IResolver>.Get() + => new global::IAppConfig[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopedSimpleProvider.IResolver>.Get() + => new global::IUnitOfWork[] { S1() }; + global::System.IServiceProvider ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedSimpleProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedSimpleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs b/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs new file mode 100644 index 00000000..33b80b3a --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs @@ -0,0 +1,29 @@ +using AutoCtor; + +// A transient that reaches a scoped service can only be built inside a scope, so its +// factory lives on the Scope. One that reaches only singletons stays on the provider and +// the scope forwards to it. +[ServiceProvider] +[Singleton] +[Scoped] +[Transient] +[Transient] +public sealed partial class PlacementProvider; + +public interface IPlacementConfig; +public class PlacementConfig : IPlacementConfig; + +public interface IPlacementContext; +public class PlacementContext : IPlacementContext; + +public interface IRootSafeCommand; +public class RootSafeCommand : IRootSafeCommand +{ + public RootSafeCommand(IPlacementConfig config) { } +} + +public interface IScopeBoundCommand; +public class ScopeBoundCommand : IScopeBoundCommand +{ + public ScopeBoundCommand(IPlacementContext context, IRootSafeCommand command) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs#PlacementProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs#PlacementProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..24726378 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedTransientPlacement.cs#PlacementProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,316 @@ +//HintName: PlacementProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PlacementProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver>, + PlacementProvider.IResolver>, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPlacementConfig? _S0; + private global::IPlacementConfig S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PlacementConfig()); + } + private global::IRootSafeCommand S2() => new global::RootSafeCommand(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlacementConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IRootSafeCommand)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlacementConfig[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRootSafeCommand[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PlacementProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PlacementProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPlacementConfig PlacementProvider.IResolver.Get() + => S0(); + global::IRootSafeCommand PlacementProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IPlacementConfig[] { S0() }; + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IRootSafeCommand[] { S2() }; + global::System.IServiceProvider PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PlacementProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver>, + PlacementProvider.IResolver>, + PlacementProvider.IResolver>, + PlacementProvider.IResolver>, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver, + PlacementProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PlacementProvider _root; + public Scope(PlacementProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPlacementConfig S0() => _root.S0(); + private volatile global::IPlacementContext? _S1; + private global::IPlacementContext S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::PlacementContext()); + } + private global::IRootSafeCommand S2() => new global::RootSafeCommand(S0()); + private global::IScopeBoundCommand S3() => new global::ScopeBoundCommand(S1(), S2()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlacementConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPlacementContext)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRootSafeCommand)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IScopeBoundCommand)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlacementConfig[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlacementContext[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRootSafeCommand[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopeBoundCommand[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PlacementProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PlacementProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPlacementConfig PlacementProvider.IResolver.Get() + => S0(); + global::IPlacementContext PlacementProvider.IResolver.Get() + => S1(); + global::IRootSafeCommand PlacementProvider.IResolver.Get() + => S2(); + global::IScopeBoundCommand PlacementProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IPlacementConfig[] { S0() }; + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IPlacementContext[] { S1() }; + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IRootSafeCommand[] { S2() }; + global::System.Collections.Generic.IEnumerable PlacementProvider.IResolver>.Get() + => new global::IScopeBoundCommand[] { S3() }; + global::System.IServiceProvider PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PlacementProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PlacementProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs b/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs new file mode 100644 index 00000000..538fbf7d --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Scoped(typeof(IScopedTypeofService), typeof(ScopedTypeofService))] +public sealed partial class ScopedTypeofAttributeProvider; + +public interface IScopedTypeofService; +public class ScopedTypeofService : IScopedTypeofService; diff --git a/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs#ScopedTypeofAttributeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs#ScopedTypeofAttributeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..d312fa2f --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedTypeofAttribute.cs#ScopedTypeofAttributeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,253 @@ +//HintName: ScopedTypeofAttributeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopedTypeofAttributeProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedTypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedTypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedTypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver>, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver, + ScopedTypeofAttributeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopedTypeofAttributeProvider _root; + public Scope(ScopedTypeofAttributeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private volatile global::IScopedTypeofService? _S0; + private global::IScopedTypeofService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ScopedTypeofService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScopedTypeofService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScopedTypeofService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedTypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedTypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IScopedTypeofService ScopedTypeofAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable ScopedTypeofAttributeProvider.IResolver>.Get() + => new global::IScopedTypeofService[] { S0() }; + global::System.IServiceProvider ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedTypeofAttributeProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedTypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs new file mode 100644 index 00000000..f08c4b5c --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs @@ -0,0 +1,18 @@ +using AutoCtor; + +// The scoped implementation's constructor does not exist yet when the IoC stage runs. +[ServiceProvider] +[Singleton] +[Scoped] +public sealed partial class ScopedAutoConstructProvider; + +public interface IScAcDependency; +public class ScAcDependency : IScAcDependency; + +public interface IScAcService; + +[AutoConstruct] +public partial class ScAcService : IScAcService +{ + private readonly IScAcDependency _dependency; +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScAcService.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScAcService.g.verified.cs new file mode 100644 index 00000000..bbabbb50 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScAcService.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: ScAcService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class ScAcService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public ScAcService(global::IScAcDependency dependency) + { + this._dependency = dependency; + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScopedAutoConstructProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScopedAutoConstructProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..770dec5d --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedWithAutoConstructImpl.cs#ScopedAutoConstructProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,283 @@ +//HintName: ScopedAutoConstructProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopedAutoConstructProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver>, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IScAcDependency? _S0; + private global::IScAcDependency S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ScAcDependency()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScAcDependency[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedAutoConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedAutoConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IScAcDependency ScopedAutoConstructProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable ScopedAutoConstructProvider.IResolver>.Get() + => new global::IScAcDependency[] { S0() }; + global::System.IServiceProvider ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedAutoConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver>, + ScopedAutoConstructProvider.IResolver>, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver, + ScopedAutoConstructProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopedAutoConstructProvider _root; + public Scope(ScopedAutoConstructProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IScAcDependency S0() => _root.S0(); + private volatile global::IScAcService? _S1; + private global::IScAcService S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ScAcService(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IScAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IScAcService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScAcDependency[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IScAcService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopedAutoConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopedAutoConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IScAcDependency ScopedAutoConstructProvider.IResolver.Get() + => S0(); + global::IScAcService ScopedAutoConstructProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopedAutoConstructProvider.IResolver>.Get() + => new global::IScAcDependency[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopedAutoConstructProvider.IResolver>.Get() + => new global::IScAcService[] { S1() }; + global::System.IServiceProvider ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ScopedAutoConstructProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ScopedAutoConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs b/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs new file mode 100644 index 00000000..ef94443b --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs @@ -0,0 +1,31 @@ +using AutoCtor; +using System; + +// Which cached implementations are disposable is known at compile time, so disposal is a +// direct call list in reverse construction order, with no runtime tracking. Nothing here is +// a disposable transient, so no provider carries a stack -- see TransientDisposableInScope. +[ServiceProvider] +[Singleton] +[Scoped] +[Scoped] +public sealed partial class DisposableProvider; + +public interface IPoolOwner; +public class PoolOwner : IPoolOwner, IDisposable +{ + public void Dispose() { } +} + +public interface ISession; +public class Session : ISession, IDisposable +{ + public Session(IPoolOwner owner) { } + public void Dispose() { } +} + +// Not disposable, so it is skipped entirely. +public interface ITracker; +public class Tracker : ITracker +{ + public Tracker(ISession session) { } +} diff --git a/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs#DisposableProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs#DisposableProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..cea4b5f1 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/ScopedWithDisposables.cs#DisposableProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,302 @@ +//HintName: DisposableProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DisposableProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + DisposableProvider.IResolver, + DisposableProvider.IResolver>, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPoolOwner? _S0; + private global::IPoolOwner S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(new global::PoolOwner())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPoolOwner)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPoolOwner[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DisposableProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DisposableProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPoolOwner DisposableProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable DisposableProvider.IResolver>.Get() + => new global::IPoolOwner[] { S0() }; + global::System.IServiceProvider DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DisposableProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver>, + DisposableProvider.IResolver>, + DisposableProvider.IResolver>, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver, + DisposableProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DisposableProvider _root; + public Scope(DisposableProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPoolOwner S0() => _root.S0(); + private volatile global::ISession? _S1; + private global::ISession S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(new global::Session(S0()))); + } + private volatile global::ITracker? _S2; + private global::ITracker S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Tracker(S1())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPoolOwner)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISession)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ITracker)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPoolOwner[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISession[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITracker[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DisposableProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DisposableProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPoolOwner DisposableProvider.IResolver.Get() + => S0(); + global::ISession DisposableProvider.IResolver.Get() + => S1(); + global::ITracker DisposableProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable DisposableProvider.IResolver>.Get() + => new global::IPoolOwner[] { S0() }; + global::System.Collections.Generic.IEnumerable DisposableProvider.IResolver>.Get() + => new global::ISession[] { S1() }; + global::System.Collections.Generic.IEnumerable DisposableProvider.IResolver>.Get() + => new global::ITracker[] { S2() }; + global::System.IServiceProvider DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider DisposableProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService DisposableProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs b/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs new file mode 100644 index 00000000..7ad38bf7 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs @@ -0,0 +1,39 @@ +using AutoCtor; +using System; + +// RequestLog needs nothing scoped, so a transient that was not disposable would live on the +// provider with the scope forwarding to it. Because it is disposable the scope builds its own +// instead: forwarding would leave every scope's instances owned by the root, and alive until +// the provider is. Audit depends on a scoped service, so it only ever exists on the scope. +[ServiceProvider] +[Singleton] +[Scoped] +[Transient] +[Transient] +public sealed partial class RequestProvider; + +public interface IClock; +public class Clock : IClock; + +// Disposable and scoped, so the scope disposes it by the compile-time list before it drains +// the transients it handed out. +public interface IRequest; +public class Request : IRequest, IDisposable +{ + public Request(IClock clock) { } + public void Dispose() { } +} + +public interface IRequestLog; +public class RequestLog : IRequestLog, IDisposable +{ + public RequestLog(IClock clock) { } + public void Dispose() { } +} + +public interface IAudit; +public class Audit : IAudit, IDisposable +{ + public Audit(IRequest request) { } + public void Dispose() { } +} diff --git a/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs#RequestProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs#RequestProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..938b55c7 --- /dev/null +++ b/src/Tests/IoCAbstractionsExamples/TransientDisposableInScope.cs#RequestProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,316 @@ +//HintName: RequestProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class RequestProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver>, + RequestProvider.IResolver>, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + private global::IRequestLog S2() => Track(new global::RequestLog(S0())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IRequestLog)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequestLog[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RequestProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RequestProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock RequestProvider.IResolver.Get() + => S0(); + global::IRequestLog RequestProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IRequestLog[] { S2() }; + global::System.IServiceProvider RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService RequestProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver>, + RequestProvider.IResolver>, + RequestProvider.IResolver>, + RequestProvider.IResolver>, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver, + RequestProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly RequestProvider _root; + public Scope(RequestProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private volatile global::IRequest? _S1; + private global::IRequest S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(new global::Request(S0()))); + } + private global::IRequestLog S2() => Track(new global::RequestLog(S0())); + private global::IAudit S3() => Track(new global::Audit(S1())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IRequest)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRequestLog)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IAudit)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequest[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequestLog[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAudit[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RequestProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RequestProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock RequestProvider.IResolver.Get() + => S0(); + global::IRequest RequestProvider.IResolver.Get() + => S1(); + global::IRequestLog RequestProvider.IResolver.Get() + => S2(); + global::IAudit RequestProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IRequest[] { S1() }; + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IRequestLog[] { S2() }; + global::System.Collections.Generic.IEnumerable RequestProvider.IResolver>.Get() + => new global::IAudit[] { S3() }; + global::System.IServiceProvider RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider RequestProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService RequestProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposable.cs b/src/Tests/IoCAsyncExamples/AsyncDisposable.cs new file mode 100644 index 00000000..2a1e80d5 --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposable.cs @@ -0,0 +1,35 @@ +using AutoCtor; +using System; +using System.Threading.Tasks; + +// Three shapes at once. Cache implements both interfaces, so Dispose uses the synchronous one +// and DisposeAsync the asynchronous one. Pump implements only the asynchronous one, so Dispose +// cannot release it and says so rather than passing over it. Channel is a transient that is +// only asynchronously disposable, which is what makes the tracking list hold objects rather +// than IDisposable -- the two interfaces have to be told apart when it is walked. +[ServiceProvider] +[Singleton] +[Singleton] +[Transient] +public sealed partial class MediaProvider; + +public interface ICache; +public class Cache : ICache, IDisposable, IAsyncDisposable +{ + public void Dispose() { } + public ValueTask DisposeAsync() => default; +} + +public interface IPump; +public class Pump : IPump, IAsyncDisposable +{ + public Pump(ICache cache) { } + public ValueTask DisposeAsync() => default; +} + +public interface IChannel; +public class Channel : IChannel, IAsyncDisposable +{ + public Channel(ICache cache) { } + public ValueTask DisposeAsync() => default; +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposable.cs#MediaProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAsyncExamples/AsyncDisposable.cs#MediaProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..59f46906 --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposable.cs#MediaProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,340 @@ +//HintName: MediaProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class MediaProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver>, + MediaProvider.IResolver>, + MediaProvider.IResolver>, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ICache? _S0; + private global::ICache S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(new global::Cache())); + } + private volatile global::IPump? _S1; + private global::IPump S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(new global::Pump(S0()))); + } + private global::IChannel S2() => Track(new global::Channel(S0())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICache)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPump)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChannel)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICache[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPump[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChannel[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MediaProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MediaProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ICache MediaProvider.IResolver.Get() + => S0(); + global::IPump MediaProvider.IResolver.Get() + => S1(); + global::IChannel MediaProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::ICache[] { S0() }; + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::IPump[] { S1() }; + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::IChannel[] { S2() }; + global::System.IServiceProvider MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService MediaProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver>, + MediaProvider.IResolver>, + MediaProvider.IResolver>, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver, + MediaProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly MediaProvider _root; + public Scope(MediaProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ICache S0() => _root.S0(); + private global::IPump S1() => _root.S1(); + private global::IChannel S2() => Track(new global::Channel(S0())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICache)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPump)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChannel)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICache[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPump[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChannel[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MediaProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MediaProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ICache MediaProvider.IResolver.Get() + => S0(); + global::IPump MediaProvider.IResolver.Get() + => S1(); + global::IChannel MediaProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::ICache[] { S0() }; + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::IPump[] { S1() }; + global::System.Collections.Generic.IEnumerable MediaProvider.IResolver>.Get() + => new global::IChannel[] { S2() }; + global::System.IServiceProvider MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider MediaProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService MediaProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + } +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs b/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs new file mode 100644 index 00000000..71b7a273 --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs @@ -0,0 +1,30 @@ +using AutoCtor; +using System; +using System.Threading.Tasks; + +// A scope always implements IServiceScope, and so always has a Dispose. Session can only be +// released asynchronously, so that Dispose names DisposeAsync instead of skipping it, and the +// scope picks up IAsyncDisposable. Only transients are tracked in the list, so a scoped async +// service does not stop it being a list of IDisposable. +[ServiceProvider] +[Singleton] +[Scoped] +[Transient] +public sealed partial class SessionProvider; + +public interface IClock; +public class Clock : IClock; + +public interface ISession; +public class Session : ISession, IAsyncDisposable +{ + public Session(IClock clock) { } + public ValueTask DisposeAsync() => default; +} + +public interface ILease; +public class Lease : ILease, IDisposable +{ + public Lease(ISession session) { } + public void Dispose() { } +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs#SessionProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs#SessionProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..a67d156f --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposableInScope.cs#SessionProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,318 @@ +//HintName: SessionProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class SessionProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + SessionProvider.IResolver, + SessionProvider.IResolver>, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SessionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SessionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock SessionProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable SessionProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.IServiceProvider SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService SessionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver>, + SessionProvider.IResolver>, + SessionProvider.IResolver>, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver, + SessionProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly SessionProvider _root; + public Scope(SessionProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private volatile global::ISession? _S1; + private global::ISession S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(new global::Session(S0()))); + } + private global::ILease S2() => Track(new global::Lease(S1())); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISession)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ILease)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISession[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILease[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SessionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SessionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock SessionProvider.IResolver.Get() + => S0(); + global::ISession SessionProvider.IResolver.Get() + => S1(); + global::ILease SessionProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable SessionProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable SessionProvider.IResolver>.Get() + => new global::ISession[] { S1() }; + global::System.Collections.Generic.IEnumerable SessionProvider.IResolver>.Get() + => new global::ILease[] { S2() }; + global::System.IServiceProvider SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider SessionProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService SessionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + } +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs b/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs new file mode 100644 index 00000000..00ad1dfa --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs @@ -0,0 +1,16 @@ +using AutoCtor; +using System; +using System.Threading.Tasks; + +// Nothing here can be disposed synchronously, so the provider does not implement IDisposable +// at all. Reaching for `using` is then a compile error naming the missing interface, rather +// than a Dispose that exists only to throw. +[ServiceProvider] +[Singleton] +public sealed partial class StreamProvider; + +public interface IStream; +public class Stream : IStream, IAsyncDisposable +{ + public ValueTask DisposeAsync() => default; +} diff --git a/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs#StreamProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs#StreamProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..8c5bb660 --- /dev/null +++ b/src/Tests/IoCAsyncExamples/AsyncDisposableOnly.cs#StreamProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,288 @@ +//HintName: StreamProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class StreamProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + StreamProvider.IResolver, + StreamProvider.IResolver>, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IStream? _S0; + private global::IStream S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(new global::Stream())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStream)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStream[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StreamProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StreamProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IStream StreamProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable StreamProvider.IResolver>.Get() + => new global::IStream[] { S0() }; + global::System.IServiceProvider StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService StreamProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + StreamProvider.IResolver, + StreamProvider.IResolver>, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver, + StreamProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly StreamProvider _root; + public Scope(StreamProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IStream S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStream)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStream[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StreamProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StreamProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IStream StreamProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable StreamProvider.IResolver>.Get() + => new global::IStream[] { S0() }; + global::System.IServiceProvider StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider StreamProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService StreamProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + } +} diff --git a/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs b/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs new file mode 100644 index 00000000..86857fd2 --- /dev/null +++ b/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs @@ -0,0 +1,17 @@ +using AutoCtor; +using System; + +// Everything owned is only synchronously disposable, on a target framework that has +// IAsyncDisposable. DisposeAsync is still part of the surface, because Microsoft's provider +// always has one, but it has nothing to await -- so it is written without the state machine +// an unused async would cost. +[ServiceProvider] +[Singleton] +public sealed partial class LedgerProvider; + +public interface ILedger; + +public class Ledger : ILedger, IDisposable +{ + public void Dispose() { } +} diff --git a/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs#LedgerProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs#LedgerProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..3e5d13ff --- /dev/null +++ b/src/Tests/IoCAsyncExamples/SyncDisposableOnly.cs#LedgerProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,288 @@ +//HintName: LedgerProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class LedgerProvider : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + LedgerProvider.IResolver, + LedgerProvider.IResolver>, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ILedger? _S0; + private global::ILedger S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(new global::Ledger())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is LedgerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is LedgerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ILedger LedgerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable LedgerProvider.IResolver>.Get() + => new global::ILedger[] { S0() }; + global::System.IServiceProvider LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService LedgerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::System.IAsyncDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + LedgerProvider.IResolver, + LedgerProvider.IResolver>, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver, + LedgerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly LedgerProvider _root; + public Scope(LedgerProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ILedger S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is LedgerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is LedgerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ILedger LedgerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable LedgerProvider.IResolver>.Get() + => new global::ILedger[] { S0() }; + global::System.IServiceProvider LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider LedgerProvider.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService LedgerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable || service is global::System.IAsyncDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + public async global::System.Threading.Tasks.ValueTask DisposeAsync() + { + foreach (var item in Drain()) + { + if (item is global::System.IAsyncDisposable d) + await d.DisposeAsync().ConfigureAwait(false); + else + ((global::System.IDisposable)item).Dispose(); + } + } + } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs b/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs new file mode 100644 index 00000000..f1c055f1 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +public sealed partial class MultiCtorProvider; + +public interface IMultiCtorService; + +public class MultiCtorService : IMultiCtorService +{ + public MultiCtorService() { } + public MultiCtorService(int value) { } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs.verified.txt new file mode 100644 index 00000000..1eca8b02 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR010_MultiplePublicConstructors.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class MultiCtorProvider; +*/ + : (3,1)-(3,47), + Message: The implementation type 'MultiCtorService' must have exactly one public constructor to be used as a service, + Severity: Error, + Descriptor: { + Id: ACTR010, + Title: Service implementation must have a single public constructor, + MessageFormat: The implementation type '{0}' must have exactly one public constructor to be used as a service, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs b/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs new file mode 100644 index 00000000..9dad92fe --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton(typeof(IAbstractService), typeof(AbstractService))] +public sealed partial class AbstractImplProvider; + +public interface IAbstractService; +public abstract class AbstractService : IAbstractService; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs.verified.txt new file mode 100644 index 00000000..38224cd8 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR011_AbstractImplementation.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IAbstractService), typeof(AbstractService))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class AbstractImplProvider; +*/ + : (3,1)-(3,61), + Message: The implementation type 'AbstractService' cannot be instantiated; it must be a non-abstract, non-generic class, + Severity: Error, + Descriptor: { + Id: ACTR011, + Title: Service implementation cannot be instantiated, + MessageFormat: The implementation type '{0}' cannot be instantiated; it must be a non-abstract, non-generic class, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs b/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs new file mode 100644 index 00000000..f735bb44 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +public sealed partial class UnregisteredDependencyProvider; + +public interface IMissingService; +public interface INeedsMissing; + +public class NeedsMissing : INeedsMissing +{ + public NeedsMissing(IMissingService missing) { } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs.verified.txt new file mode 100644 index 00000000..3760354f --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR012_UnregisteredDependency.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class UnregisteredDependencyProvider; +*/ + : (3,1)-(3,39), + Message: No registration was found for the dependency 'IMissingService' of 'NeedsMissing', + Severity: Error, + Descriptor: { + Id: ACTR012, + Title: Service dependency is not registered, + MessageFormat: No registration was found for the dependency '{0}' of '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs b/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs new file mode 100644 index 00000000..1dda6c6a --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs @@ -0,0 +1,19 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +[Singleton] +public sealed partial class CircularDependencyProvider; + +public interface ICycleA; +public interface ICycleB; + +public class CycleA : ICycleA +{ + public CycleA(ICycleB b) { } +} + +public class CycleB : ICycleB +{ + public CycleB(ICycleA a) { } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs.verified.txt new file mode 100644 index 00000000..6192b811 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR013_CircularDependency.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^ +[Singleton] +*/ + : (3,1)-(3,27), + Message: A circular dependency was detected involving 'CycleA', + Severity: Error, + Descriptor: { + Id: ACTR013, + Title: Circular dependency between registered services, + MessageFormat: A circular dependency was detected involving '{0}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs b/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs new file mode 100644 index 00000000..0246eb8d --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton(typeof(IUnrelatedService), typeof(UnrelatedImplementation))] +public sealed partial class NotAssignableProvider; + +public interface IUnrelatedService; +public class UnrelatedImplementation; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs.verified.txt new file mode 100644 index 00000000..c5fd51d9 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR015_NotAssignable.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IUnrelatedService), typeof(UnrelatedImplementation))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class NotAssignableProvider; +*/ + : (3,1)-(3,70), + Message: The implementation type 'UnrelatedImplementation' is not assignable to the service type 'IUnrelatedService', + Severity: Error, + Descriptor: { + Id: ACTR015, + Title: Implementation type is not assignable to the service type, + MessageFormat: The implementation type '{0}' is not assignable to the service type '{1}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs b/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs new file mode 100644 index 00000000..eec5a6d0 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs @@ -0,0 +1,10 @@ +using AutoCtor; + +// The rule cannot work: closing IPair<,> yields two type arguments, and PairImpl<> only +// takes one. +[ServiceProvider] +[Singleton(typeof(IPair<,>), typeof(PairImpl<>))] +public sealed partial class PairProvider; + +public interface IPair; +public class PairImpl : IPair; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs.verified.txt new file mode 100644 index 00000000..07b7fc1b --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR016_OpenGenericArityMismatch.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IPair<,>), typeof(PairImpl<>))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class PairProvider; +*/ + : (5,1)-(5,48), + Message: The open generic service 'IPair' cannot be registered with 'PairImpl'; the implementation must be an open generic type with the same number of type parameters, + Severity: Error, + Descriptor: { + Id: ACTR016, + Title: Open generic registration is not valid, + MessageFormat: The open generic service '{0}' cannot be registered with '{1}'; the implementation must be an open generic type with the same number of type parameters, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs b/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs new file mode 100644 index 00000000..b1917e4d --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs @@ -0,0 +1,10 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +public sealed partial class GenericProvider +{ +} + +public interface IInvalidService; +public class InvalidService : IInvalidService; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs.verified.txt new file mode 100644 index 00000000..ad141c18 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR017_InvalidProvider.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Singleton] +public sealed partial class GenericProvider + ^^^^^^^^^^^^^^^ +{ +*/ + : (4,28)-(4,43), + Message: The type 'GenericProvider' cannot be a service provider; it must not be generic, and must not be marked with [AutoConstruct], + Severity: Error, + Descriptor: { + Id: ACTR017, + Title: Service provider type is not valid, + MessageFormat: The type '{0}' cannot be a service provider; it must not be generic, and must not be marked with [AutoConstruct], + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs new file mode 100644 index 00000000..ca5168eb --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs @@ -0,0 +1,30 @@ +using AutoCtor; + +// The singleton holds the transient for the lifetime of the provider, so ICaptiveWorker +// is only ever constructed once. Still valid code, so the provider is emitted. +// +// The snapshot also covers construction ordering through a transient: the eager assignment +// of ICaptiveHolder calls GetICaptiveWorker(), which reads the ICaptiveSettings field, so +// that field has to be assigned first. +[ServiceProvider] +[Singleton] +[Transient] +[Singleton] +public sealed partial class CaptiveTransientProvider; + +public interface ICaptiveSettings; +public class CaptiveSettings : ICaptiveSettings; + +public interface ICaptiveWorker; + +public class CaptiveWorker : ICaptiveWorker +{ + public CaptiveWorker(ICaptiveSettings settings) { } +} + +public interface ICaptiveHolder; + +public class CaptiveHolder : ICaptiveHolder +{ + public CaptiveHolder(ICaptiveWorker worker) { } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs#CaptiveTransientProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs#CaptiveTransientProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..acebf928 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs#CaptiveTransientProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,265 @@ +//HintName: CaptiveTransientProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class CaptiveTransientProvider : + global::System.IServiceProvider, + global::System.IDisposable, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ICaptiveHolder? _S0; + private global::ICaptiveHolder S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::CaptiveHolder(S1())); + } + private global::ICaptiveWorker S1() => new global::CaptiveWorker(S2()); + private volatile global::ICaptiveSettings? _S2; + private global::ICaptiveSettings S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::CaptiveSettings()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICaptiveHolder)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICaptiveWorker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICaptiveSettings)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveHolder[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveWorker[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveSettings[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CaptiveTransientProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CaptiveTransientProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ICaptiveHolder CaptiveTransientProvider.IResolver.Get() + => S0(); + global::ICaptiveWorker CaptiveTransientProvider.IResolver.Get() + => S1(); + global::ICaptiveSettings CaptiveTransientProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveHolder[] { S0() }; + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveWorker[] { S1() }; + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveSettings[] { S2() }; + global::System.IServiceProvider CaptiveTransientProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver>, + CaptiveTransientProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly CaptiveTransientProvider _root; + public Scope(CaptiveTransientProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ICaptiveHolder S0() => _root.S0(); + private global::ICaptiveWorker S1() => new global::CaptiveWorker(S2()); + private global::ICaptiveSettings S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICaptiveHolder)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICaptiveWorker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICaptiveSettings)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveHolder[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveWorker[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICaptiveSettings[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CaptiveTransientProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CaptiveTransientProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ICaptiveHolder CaptiveTransientProvider.IResolver.Get() + => S0(); + global::ICaptiveWorker CaptiveTransientProvider.IResolver.Get() + => S1(); + global::ICaptiveSettings CaptiveTransientProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveHolder[] { S0() }; + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveWorker[] { S1() }; + global::System.Collections.Generic.IEnumerable CaptiveTransientProvider.IResolver>.Get() + => new global::ICaptiveSettings[] { S2() }; + global::System.IServiceProvider CaptiveTransientProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs.verified.txt new file mode 100644 index 00000000..3358c906 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR018_CaptiveTransient.cs.verified.txt @@ -0,0 +1,24 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +[Transient] +*/ + : (9,1)-(9,41), + Message: The transient service 'CaptiveWorker' is captured by the singleton 'CaptiveHolder' and will not be recreated for each resolve, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: ACTR018, + Title: Transient service is captured by a singleton, + MessageFormat: The transient service '{0}' is captured by the singleton '{1}' and will not be recreated for each resolve, + Category: AutoCtor, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs b/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs new file mode 100644 index 00000000..53bd61c1 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs @@ -0,0 +1,18 @@ +using AutoCtor; + +// IRef is asked for, but closing RefImpl<> over int breaks its class constraint. The +// generated code would not compile, so it is caught here instead. +[ServiceProvider] +[Singleton(typeof(IRef<>), typeof(RefImpl<>))] +[Singleton] +public sealed partial class RefProvider; + +public interface IRef; +public class RefImpl : IRef where T : class; + +public interface IRefConsumer; + +public class RefConsumer : IRefConsumer +{ + public RefConsumer(IRef numbers) { } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs.verified.txt new file mode 100644 index 00000000..03211aae --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR020_OpenGenericConstraint.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IRef<>), typeof(RefImpl<>))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +[Singleton] +*/ + : (5,1)-(5,45), + Message: 'IRef' is needed by 'numbers', but its type arguments do not satisfy the constraints of 'RefImpl', + Severity: Error, + Descriptor: { + Id: ACTR020, + Title: Type arguments do not satisfy the open generic implementation, + MessageFormat: '{0}' is needed by '{1}', but its type arguments do not satisfy the constraints of '{2}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs b/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs new file mode 100644 index 00000000..680e3ce1 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs @@ -0,0 +1,10 @@ +using AutoCtor; + +// Nothing on the provider is called BuildThing. A name that does exist would be written +// with nameof; a literal is the only way to name one that does not. +[ServiceProvider] +[Singleton(typeof(IThing), typeof(Thing), Factory = "BuildThing")] +public sealed partial class MissingFactoryProvider; + +public interface IThing; +public class Thing : IThing; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs.verified.txt new file mode 100644 index 00000000..c2f294cb --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR021_MissingFactoryMember.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IThing), typeof(Thing), Factory = "BuildThing")] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class MissingFactoryProvider; +*/ + : (5,1)-(5,65), + Message: The factory 'BuildThing' cannot be used for the service 'IThing'; it must name an accessible, non-generic method, property or field declared on the service provider that returns a value, + Severity: Error, + Descriptor: { + Id: ACTR021, + Title: Service factory member is not valid, + MessageFormat: The factory '{0}' cannot be used for the service '{1}'; it must name an accessible, non-generic method, property or field declared on the service provider that returns a value, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs b/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs new file mode 100644 index 00000000..d8f5cdf0 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// The member exists and can be called, but an int is not an IThing. +[ServiceProvider] +[Singleton(typeof(IThing), typeof(Thing), Factory = nameof(CreateNumber))] +public sealed partial class WrongReturnProvider +{ + private int CreateNumber() => 42; +} + +public interface IThing; +public class Thing : IThing; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs.verified.txt new file mode 100644 index 00000000..0c2ce50f --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR022_FactoryReturnType.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IThing), typeof(Thing), Factory = nameof(CreateNumber))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class WrongReturnProvider +*/ + : (4,1)-(4,73), + Message: The factory 'CreateNumber' returns 'int', which is not assignable to the service type 'IThing', + Severity: Error, + Descriptor: { + Id: ACTR022, + Title: Service factory does not return the service type, + MessageFormat: The factory '{0}' returns '{1}', which is not assignable to the service type '{2}', + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs b/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs new file mode 100644 index 00000000..6e07b1cd --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// An open registration is closed on demand, so there is no one type for a factory to build. +[ServiceProvider] +[Singleton(typeof(IRepository<>), typeof(Repository<>), Factory = nameof(CreateRepository))] +public sealed partial class OpenGenericFactoryProvider +{ + private Repository? CreateRepository() => null; +} + +public interface IRepository; +public class Repository : IRepository; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs.verified.txt new file mode 100644 index 00000000..d276d6b5 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR023_OpenGenericFactory.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Singleton(typeof(IRepository<>), typeof(Repository<>), Factory = nameof(CreateRepository))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class OpenGenericFactoryProvider +*/ + : (4,1)-(4,91), + Message: The open generic service 'IRepository' cannot use the factory 'CreateRepository'; a factory builds one type, and an open registration is closed on demand, + Severity: Error, + Descriptor: { + Id: ACTR023, + Title: Open generic registration cannot use a factory, + MessageFormat: The open generic service '{0}' cannot use the factory '{1}'; a factory builds one type, and an open registration is closed on demand, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs b/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs new file mode 100644 index 00000000..26a03ede --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs @@ -0,0 +1,13 @@ +using System; +using AutoCtor; + +// A fallback is read on the miss path, where there is nothing to resolve a parameter from. +[ServiceProvider(Fallback = nameof(GetHost))] +[Singleton] +public sealed partial class ParameterisedFallbackProvider +{ + private IServiceProvider GetHost(int tenant) => throw new NotSupportedException(); +} + +public interface IService; +public class Service : IService; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs.verified.txt new file mode 100644 index 00000000..9cc2bced --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR024_InvalidFallback.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Singleton] +public sealed partial class ParameterisedFallbackProvider + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +{ +*/ + : (6,28)-(6,57), + Message: The fallback 'GetHost' cannot be used by 'ParameterisedFallbackProvider'; it must name an accessible, non-generic field, property or parameterless method declared on the service provider that returns a value, + Severity: Error, + Descriptor: { + Id: ACTR024, + Title: Service provider fallback member is not valid, + MessageFormat: The fallback '{0}' cannot be used by '{1}'; it must name an accessible, non-generic field, property or parameterless method declared on the service provider that returns a value, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs b/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs new file mode 100644 index 00000000..fb55f8a0 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs @@ -0,0 +1,11 @@ +using AutoCtor; + +[ServiceProvider(Fallback = nameof(_host))] +[Singleton] +public sealed partial class WrongFallbackProvider +{ + private readonly string _host = ""; +} + +public interface IService; +public class Service : IService; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs.verified.txt new file mode 100644 index 00000000..cff78356 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR025_FallbackNotAServiceProvider.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Singleton] +public sealed partial class WrongFallbackProvider + ^^^^^^^^^^^^^^^^^^^^^ +{ +*/ + : (4,28)-(4,49), + Message: The fallback '_host' returns 'string', which is not assignable to System.IServiceProvider, + Severity: Error, + Descriptor: { + Id: ACTR025, + Title: Service provider fallback is not a service provider, + MessageFormat: The fallback '{0}' returns '{1}', which is not assignable to System.IServiceProvider, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs b/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs new file mode 100644 index 00000000..daf30eb1 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[ScanSingleton(typeof(IMissing))] +public sealed partial class EmptyScanProvider; + +public interface IMissing; +public abstract class MissingBase : IMissing; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs.verified.txt new file mode 100644 index 00000000..60e065d1 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR026_ScanFoundNoTypes.cs.verified.txt @@ -0,0 +1,24 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[ScanSingleton(typeof(IMissing))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class EmptyScanProvider; +*/ + : (3,1)-(3,32), + Message: The scan for 'IMissing' found no accessible, concrete, non-generic class to register, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: ACTR026, + Title: Service scan found no types, + MessageFormat: The scan for '{0}' found no accessible, concrete, non-generic class to register, + Category: AutoCtor, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs new file mode 100644 index 00000000..9763d38c --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +// EmptyModule carries no registrations -- which is also how a module from an assembly built +// without AUTOCTOR_USAGES looks. +[ServiceProvider] +[Import] +[Singleton] +public sealed partial class EmptyModuleProvider; + +public class EmptyModule; + +public interface IThing; +public class Thing : IThing; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs#EmptyModuleProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs#EmptyModuleProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9bde7a5c --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs#EmptyModuleProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: EmptyModuleProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class EmptyModuleProvider : + global::System.IServiceProvider, + global::System.IDisposable, + EmptyModuleProvider.IResolver, + EmptyModuleProvider.IResolver>, + EmptyModuleProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IThing? _S0; + private global::IThing S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Thing()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IThing)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IThing[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EmptyModuleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EmptyModuleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IThing EmptyModuleProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable EmptyModuleProvider.IResolver>.Get() + => new global::IThing[] { S0() }; + global::System.IServiceProvider EmptyModuleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + EmptyModuleProvider.IResolver, + EmptyModuleProvider.IResolver>, + EmptyModuleProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly EmptyModuleProvider _root; + public Scope(EmptyModuleProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IThing S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IThing)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IThing[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EmptyModuleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EmptyModuleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IThing EmptyModuleProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable EmptyModuleProvider.IResolver>.Get() + => new global::IThing[] { S0() }; + global::System.IServiceProvider EmptyModuleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs.verified.txt new file mode 100644 index 00000000..65dae7d3 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR027_EmptyModule.cs.verified.txt @@ -0,0 +1,24 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[Import] + ^^^^^^^^^^^^^^^^^^^ +[Singleton] +*/ + : (5,1)-(5,20), + Message: The module 'EmptyModule' has no registrations; a module in another assembly needs that assembly to define AUTOCTOR_USAGES, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: ACTR027, + Title: Imported module has no registrations, + MessageFormat: The module '{0}' has no registrations; a module in another assembly needs that assembly to define AUTOCTOR_USAGES, + Category: AutoCtor, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs b/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs new file mode 100644 index 00000000..055940ff --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs @@ -0,0 +1,15 @@ +using AutoCtor; + +// Build is an instance member, and nothing ever creates an InstanceFactoryModule to call it on. +[ServiceProvider] +[Import] +public sealed partial class InstanceFactoryProvider; + +[Singleton(Factory = nameof(Build))] +public class InstanceFactoryModule +{ + public IThing Build() => new Thing(); +} + +public interface IThing; +public class Thing : IThing; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs.verified.txt new file mode 100644 index 00000000..82c4bae8 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR028_ModuleInstanceFactory.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* + +[Singleton(Factory = nameof(Build))] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public class InstanceFactoryModule +*/ + : (7,1)-(7,43), + Message: The factory 'Build' on the module 'InstanceFactoryModule' must be static; a module is never instantiated, + Severity: Error, + Descriptor: { + Id: ACTR028, + Title: Module factory must be static, + MessageFormat: The factory '{0}' on the module '{1}' must be static; a module is never instantiated, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs b/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs new file mode 100644 index 00000000..17ccedb8 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs @@ -0,0 +1,10 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +public partial class UnsealedProvider +{ +} + +public interface IUnsealedService; +public class UnsealedService : IUnsealedService; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs.verified.txt new file mode 100644 index 00000000..c1a76317 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR029_UnsealedProvider.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[Singleton] +public partial class UnsealedProvider + ^^^^^^^^^^^^^^^^ +{ +*/ + : (4,21)-(4,37), + Message: The type 'UnsealedProvider' must be sealed to be a service provider, + Severity: Error, + Descriptor: { + Id: ACTR029, + Title: Service provider type must be sealed, + MessageFormat: The type '{0}' must be sealed to be a service provider, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs b/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs new file mode 100644 index 00000000..fd0171cc --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs @@ -0,0 +1,7 @@ +using AutoCtor; + +[ServiceProvider] +[ScanSingleton] +public sealed partial class UnfilteredScanProvider; + +public class Anything; diff --git a/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs.verified.txt b/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs.verified.txt new file mode 100644 index 00000000..f2fc3f89 --- /dev/null +++ b/src/Tests/IoCDiagnosticExamples/ACTR030_ScanHasNoFilter.cs.verified.txt @@ -0,0 +1,23 @@ +{ + Diagnostics: [ + { + Location: /* +[ServiceProvider] +[ScanSingleton] + ^^^^^^^^^^^^^ +public sealed partial class UnfilteredScanProvider; +*/ + : (3,1)-(3,14), + Message: 'ScanSingleton' needs a service or a TypeNameFilter, + Severity: Error, + Descriptor: { + Id: ACTR030, + Title: Service scan has no filter, + MessageFormat: '{0}' needs a service or a TypeNameFilter, + Category: AutoCtor, + DefaultSeverity: Error, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCExamples/EnumerableEmpty.cs b/src/Tests/IoCExamples/EnumerableEmpty.cs new file mode 100644 index 00000000..63ac63b2 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableEmpty.cs @@ -0,0 +1,17 @@ +using AutoCtor; +using System.Collections.Generic; + +// Nothing is registered for IPlugin, so the collection is empty rather than an error -- +// the same as Microsoft's container. +[ServiceProvider] +[Singleton] +public sealed partial class EmptyCollectionProvider; + +public interface IPlugin; + +public interface IPluginHost; + +public class PluginHost : IPluginHost +{ + public PluginHost(IEnumerable plugins) { } +} diff --git a/src/Tests/IoCExamples/EnumerableEmpty.cs#EmptyCollectionProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/EnumerableEmpty.cs#EmptyCollectionProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..8b42b15c --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableEmpty.cs#EmptyCollectionProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: EmptyCollectionProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class EmptyCollectionProvider : + global::System.IServiceProvider, + global::System.IDisposable, + EmptyCollectionProvider.IResolver, + EmptyCollectionProvider.IResolver>, + EmptyCollectionProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPluginHost? _S0; + private global::IPluginHost S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PluginHost(new global::IPlugin[] { })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EmptyCollectionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EmptyCollectionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPluginHost EmptyCollectionProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable EmptyCollectionProvider.IResolver>.Get() + => new global::IPluginHost[] { S0() }; + global::System.IServiceProvider EmptyCollectionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + EmptyCollectionProvider.IResolver, + EmptyCollectionProvider.IResolver>, + EmptyCollectionProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly EmptyCollectionProvider _root; + public Scope(EmptyCollectionProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPluginHost S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is EmptyCollectionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is EmptyCollectionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPluginHost EmptyCollectionProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable EmptyCollectionProvider.IResolver>.Get() + => new global::IPluginHost[] { S0() }; + global::System.IServiceProvider EmptyCollectionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs b/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs new file mode 100644 index 00000000..2c3c3afd --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// Each element keeps its own lifetime, so the array is rebuilt per resolve: the singleton +// element is read from its field, the transient one is constructed again. +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class MixedLifetimeProvider; + +public interface IStep; +public class CachedStep : IStep; +public class FreshStep : IStep; diff --git a/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs#MixedLifetimeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs#MixedLifetimeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..fe644884 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableMixedLifetimes.cs#MixedLifetimeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,215 @@ +//HintName: MixedLifetimeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class MixedLifetimeProvider : + global::System.IServiceProvider, + global::System.IDisposable, + MixedLifetimeProvider.IResolver, + MixedLifetimeProvider.IResolver>, + MixedLifetimeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IStep? _S0; + private global::IStep S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::CachedStep()); + } + private global::IStep S1() => new global::FreshStep(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStep)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStep[] { S0(), S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MixedLifetimeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MixedLifetimeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IStep MixedLifetimeProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable MixedLifetimeProvider.IResolver>.Get() + => new global::IStep[] { S0(), S1() }; + global::System.IServiceProvider MixedLifetimeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + MixedLifetimeProvider.IResolver, + MixedLifetimeProvider.IResolver>, + MixedLifetimeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly MixedLifetimeProvider _root; + public Scope(MixedLifetimeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IStep S0() => _root.S0(); + private global::IStep S1() => new global::FreshStep(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStep)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStep[] { S0(), S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MixedLifetimeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MixedLifetimeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IStep MixedLifetimeProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable MixedLifetimeProvider.IResolver>.Get() + => new global::IStep[] { S0(), S1() }; + global::System.IServiceProvider MixedLifetimeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs b/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs new file mode 100644 index 00000000..3989aa02 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs @@ -0,0 +1,23 @@ +using AutoCtor; +using System.Collections.Generic; + +// Registering a service more than once keeps every registration. The collection sees them +// in registration order; asking for IValidator on its own gets the last. +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class ValidationProvider; + +public interface IValidator; +public class NameValidator : IValidator; +public class EmailValidator : IValidator; +public class AgeValidator : IValidator; + +public interface IValidationRunner; + +public class ValidationRunner : IValidationRunner +{ + public ValidationRunner(IEnumerable validators) { } +} diff --git a/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs#ValidationProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs#ValidationProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..a514a824 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableMultipleImplementations.cs#ValidationProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,263 @@ +//HintName: ValidationProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ValidationProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ValidationProvider.IResolver, + ValidationProvider.IResolver, + ValidationProvider.IResolver>, + ValidationProvider.IResolver>, + ValidationProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IValidator? _S0; + private global::IValidator S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::NameValidator()); + } + private volatile global::IValidator? _S1; + private global::IValidator S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::EmailValidator()); + } + private volatile global::IValidator? _S2; + private global::IValidator S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::AgeValidator()); + } + private volatile global::IValidationRunner? _S3; + private global::IValidationRunner S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::ValidationRunner(new global::IValidator[] { S0(), S1(), S2() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IValidationRunner)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IValidator[] { S0(), S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IValidationRunner[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ValidationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ValidationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IValidator ValidationProvider.IResolver.Get() + => S2(); + global::IValidationRunner ValidationProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable ValidationProvider.IResolver>.Get() + => new global::IValidator[] { S0(), S1(), S2() }; + global::System.Collections.Generic.IEnumerable ValidationProvider.IResolver>.Get() + => new global::IValidationRunner[] { S3() }; + global::System.IServiceProvider ValidationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ValidationProvider.IResolver, + ValidationProvider.IResolver, + ValidationProvider.IResolver>, + ValidationProvider.IResolver>, + ValidationProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ValidationProvider _root; + public Scope(ValidationProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IValidator S0() => _root.S0(); + private global::IValidator S1() => _root.S1(); + private global::IValidator S2() => _root.S2(); + private global::IValidationRunner S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IValidationRunner)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IValidator[] { S0(), S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IValidationRunner[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ValidationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ValidationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IValidator ValidationProvider.IResolver.Get() + => S2(); + global::IValidationRunner ValidationProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable ValidationProvider.IResolver>.Get() + => new global::IValidator[] { S0(), S1(), S2() }; + global::System.Collections.Generic.IEnumerable ValidationProvider.IResolver>.Get() + => new global::IValidationRunner[] { S3() }; + global::System.IServiceProvider ValidationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/EnumerableSingleRegistration.cs b/src/Tests/IoCExamples/EnumerableSingleRegistration.cs new file mode 100644 index 00000000..f93a8870 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableSingleRegistration.cs @@ -0,0 +1,18 @@ +using AutoCtor; +using System.Collections.Generic; + +// A service registered once is still resolvable as a one element collection. +[ServiceProvider] +[Singleton] +[Singleton] +public sealed partial class SingleRegistrationProvider; + +public interface ISoleHandler; +public class SoleHandler : ISoleHandler; + +public interface ISoleConsumer; + +public class SoleConsumer : ISoleConsumer +{ + public SoleConsumer(IEnumerable handlers) { } +} diff --git a/src/Tests/IoCExamples/EnumerableSingleRegistration.cs#SingleRegistrationProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/EnumerableSingleRegistration.cs#SingleRegistrationProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..73772583 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableSingleRegistration.cs#SingleRegistrationProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: SingleRegistrationProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class SingleRegistrationProvider : + global::System.IServiceProvider, + global::System.IDisposable, + SingleRegistrationProvider.IResolver, + SingleRegistrationProvider.IResolver, + SingleRegistrationProvider.IResolver>, + SingleRegistrationProvider.IResolver>, + SingleRegistrationProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ISoleHandler? _S0; + private global::ISoleHandler S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SoleHandler()); + } + private volatile global::ISoleConsumer? _S1; + private global::ISoleConsumer S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SoleConsumer(new global::ISoleHandler[] { S0() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISoleHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISoleConsumer)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISoleHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISoleConsumer[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SingleRegistrationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SingleRegistrationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ISoleHandler SingleRegistrationProvider.IResolver.Get() + => S0(); + global::ISoleConsumer SingleRegistrationProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable SingleRegistrationProvider.IResolver>.Get() + => new global::ISoleHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable SingleRegistrationProvider.IResolver>.Get() + => new global::ISoleConsumer[] { S1() }; + global::System.IServiceProvider SingleRegistrationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + SingleRegistrationProvider.IResolver, + SingleRegistrationProvider.IResolver, + SingleRegistrationProvider.IResolver>, + SingleRegistrationProvider.IResolver>, + SingleRegistrationProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly SingleRegistrationProvider _root; + public Scope(SingleRegistrationProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ISoleHandler S0() => _root.S0(); + private global::ISoleConsumer S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISoleHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISoleConsumer)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISoleHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISoleConsumer[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SingleRegistrationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SingleRegistrationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ISoleHandler SingleRegistrationProvider.IResolver.Get() + => S0(); + global::ISoleConsumer SingleRegistrationProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable SingleRegistrationProvider.IResolver>.Get() + => new global::ISoleHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable SingleRegistrationProvider.IResolver>.Get() + => new global::ISoleConsumer[] { S1() }; + global::System.IServiceProvider SingleRegistrationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/EnumerableTransientElements.cs b/src/Tests/IoCExamples/EnumerableTransientElements.cs new file mode 100644 index 00000000..0a9f3db9 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableTransientElements.cs @@ -0,0 +1,20 @@ +using AutoCtor; +using System.Collections.Generic; + +// A transient consumer of a transient collection rebuilds every element on each resolve. +[ServiceProvider] +[Transient] +[Transient] +[Transient] +public sealed partial class RuleSetProvider; + +public interface IRule; +public class RuleOne : IRule; +public class RuleTwo : IRule; + +public interface IRuleSet; + +public class RuleSet : IRuleSet +{ + public RuleSet(IEnumerable rules) { } +} diff --git a/src/Tests/IoCExamples/EnumerableTransientElements.cs#RuleSetProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/EnumerableTransientElements.cs#RuleSetProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..efea20d7 --- /dev/null +++ b/src/Tests/IoCExamples/EnumerableTransientElements.cs#RuleSetProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,229 @@ +//HintName: RuleSetProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class RuleSetProvider : + global::System.IServiceProvider, + global::System.IDisposable, + RuleSetProvider.IResolver, + RuleSetProvider.IResolver, + RuleSetProvider.IResolver>, + RuleSetProvider.IResolver>, + RuleSetProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IRule S0() => new global::RuleOne(); + private global::IRule S1() => new global::RuleTwo(); + private global::IRuleSet S2() => new global::RuleSet(new global::IRule[] { S0(), S1() }); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IRule)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRuleSet)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRule[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRuleSet[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RuleSetProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RuleSetProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IRule RuleSetProvider.IResolver.Get() + => S1(); + global::IRuleSet RuleSetProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable RuleSetProvider.IResolver>.Get() + => new global::IRule[] { S0(), S1() }; + global::System.Collections.Generic.IEnumerable RuleSetProvider.IResolver>.Get() + => new global::IRuleSet[] { S2() }; + global::System.IServiceProvider RuleSetProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + RuleSetProvider.IResolver, + RuleSetProvider.IResolver, + RuleSetProvider.IResolver>, + RuleSetProvider.IResolver>, + RuleSetProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly RuleSetProvider _root; + public Scope(RuleSetProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IRule S0() => new global::RuleOne(); + private global::IRule S1() => new global::RuleTwo(); + private global::IRuleSet S2() => new global::RuleSet(new global::IRule[] { S0(), S1() }); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IRule)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRuleSet)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRule[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRuleSet[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RuleSetProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RuleSetProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IRule RuleSetProvider.IResolver.Get() + => S1(); + global::IRuleSet RuleSetProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable RuleSetProvider.IResolver>.Get() + => new global::IRule[] { S0(), S1() }; + global::System.Collections.Generic.IEnumerable RuleSetProvider.IResolver>.Get() + => new global::IRuleSet[] { S2() }; + global::System.IServiceProvider RuleSetProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FactoryConstructorInstance.cs b/src/Tests/IoCExamples/FactoryConstructorInstance.cs new file mode 100644 index 00000000..8eb2dc05 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryConstructorInstance.cs @@ -0,0 +1,21 @@ +using AutoCtor; + +// The provider may declare a constructor of its own: nothing is generated on it but field +// initializers, which run first whatever constructor you write. That is how an instance +// built elsewhere gets in, rather than having to be built in a field initializer. +[ServiceProvider] +[Singleton(Factory = nameof(_clock))] +[Singleton] +public sealed partial class ReportProvider +{ + private readonly IClock _clock; + + public ReportProvider(IClock clock) => _clock = clock; +} + +public interface IClock; +public interface IReport; +public class Report(IClock clock) : IReport +{ + public IClock Clock => clock; +} diff --git a/src/Tests/IoCExamples/FactoryConstructorInstance.cs#ReportProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FactoryConstructorInstance.cs#ReportProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..6ddb32c0 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryConstructorInstance.cs#ReportProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: ReportProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ReportProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ReportProvider.IResolver, + ReportProvider.IResolver, + ReportProvider.IResolver>, + ReportProvider.IResolver>, + ReportProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(this._clock)); + } + private volatile global::IReport? _S1; + private global::IReport S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Report(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IReport[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ReportProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ReportProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock ReportProvider.IResolver.Get() + => S0(); + global::IReport ReportProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ReportProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ReportProvider.IResolver>.Get() + => new global::IReport[] { S1() }; + global::System.IServiceProvider ReportProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ReportProvider.IResolver, + ReportProvider.IResolver, + ReportProvider.IResolver>, + ReportProvider.IResolver>, + ReportProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ReportProvider _root; + public Scope(ReportProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IReport S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IReport[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ReportProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ReportProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock ReportProvider.IResolver.Get() + => S0(); + global::IReport ReportProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ReportProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ReportProvider.IResolver>.Get() + => new global::IReport[] { S1() }; + global::System.IServiceProvider ReportProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FactoryDisposableTransient.cs b/src/Tests/IoCExamples/FactoryDisposableTransient.cs new file mode 100644 index 00000000..96f1aa75 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryDisposableTransient.cs @@ -0,0 +1,16 @@ +using AutoCtor; + +// Disposal is decided on the implementation type, which with a factory is the caller's word +// for what comes back. The provider still owns what it hands out. +[ServiceProvider] +[Transient(Factory = nameof(CreateWorkItem))] +public sealed partial class WorkItemProvider +{ + private WorkItem CreateWorkItem() => new WorkItem(); +} + +public interface IWorkItem; +public sealed class WorkItem : IWorkItem, System.IDisposable +{ + public void Dispose() { } +} diff --git a/src/Tests/IoCExamples/FactoryDisposableTransient.cs#WorkItemProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FactoryDisposableTransient.cs#WorkItemProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..19ddab3d --- /dev/null +++ b/src/Tests/IoCExamples/FactoryDisposableTransient.cs#WorkItemProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,205 @@ +//HintName: WorkItemProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class WorkItemProvider : + global::System.IServiceProvider, + global::System.IDisposable, + WorkItemProvider.IResolver, + WorkItemProvider.IResolver>, + WorkItemProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IWorkItem S0() => Track(this.CreateWorkItem()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IWorkItem)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IWorkItem[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is WorkItemProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is WorkItemProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IWorkItem WorkItemProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable WorkItemProvider.IResolver>.Get() + => new global::IWorkItem[] { S0() }; + global::System.IServiceProvider WorkItemProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + WorkItemProvider.IResolver, + WorkItemProvider.IResolver>, + WorkItemProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly WorkItemProvider _root; + public Scope(WorkItemProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IWorkItem S0() => Track(_root.CreateWorkItem()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IWorkItem)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IWorkItem[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is WorkItemProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is WorkItemProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IWorkItem WorkItemProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable WorkItemProvider.IResolver>.Get() + => new global::IWorkItem[] { S0() }; + global::System.IServiceProvider WorkItemProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FactoryExistingInstance.cs b/src/Tests/IoCExamples/FactoryExistingInstance.cs new file mode 100644 index 00000000..fb638614 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryExistingInstance.cs @@ -0,0 +1,17 @@ +using AutoCtor; + +// An existing instance is the degenerate factory: a member that takes no arguments and +// hands back something built by hand. A field initializer is one way to fill it; a +// constructor of your own is the other, which FactoryConstructorInstance shows. +[ServiceProvider] +[Singleton(Factory = nameof(_banner))] +public sealed partial class BannerProvider +{ + private readonly IBanner _banner = new Banner("AutoCtor"); +} + +public interface IBanner; +public class Banner(string text) : IBanner +{ + public string Text => text; +} diff --git a/src/Tests/IoCExamples/FactoryExistingInstance.cs#BannerProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FactoryExistingInstance.cs#BannerProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..eeb36ae8 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryExistingInstance.cs#BannerProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: BannerProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class BannerProvider : + global::System.IServiceProvider, + global::System.IDisposable, + BannerProvider.IResolver, + BannerProvider.IResolver>, + BannerProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IBanner? _S0; + private global::IBanner S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(this._banner)); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is BannerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is BannerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IBanner BannerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable BannerProvider.IResolver>.Get() + => new global::IBanner[] { S0() }; + global::System.IServiceProvider BannerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + BannerProvider.IResolver, + BannerProvider.IResolver>, + BannerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly BannerProvider _root; + public Scope(BannerProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IBanner S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is BannerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is BannerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IBanner BannerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable BannerProvider.IResolver>.Get() + => new global::IBanner[] { S0() }; + global::System.IServiceProvider BannerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FactoryMethod.cs b/src/Tests/IoCExamples/FactoryMethod.cs new file mode 100644 index 00000000..945e27aa --- /dev/null +++ b/src/Tests/IoCExamples/FactoryMethod.cs @@ -0,0 +1,21 @@ +using AutoCtor; + +// The factory replaces the constructor call and nothing else: IClock is still a singleton +// built once on first use, and the factory's parameters are resolved like a constructor's. +[ServiceProvider] +[Singleton] +[Singleton(Factory = nameof(CreateClock))] +public sealed partial class ClockProvider +{ + private IClock CreateClock(Settings settings) => new UtcClock(settings.Offset); +} + +public interface IClock; +public class UtcClock(int offset) : IClock +{ + public int Offset => offset; +} +public class Settings +{ + public int Offset => 0; +} diff --git a/src/Tests/IoCExamples/FactoryMethod.cs#ClockProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FactoryMethod.cs#ClockProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..fb4b7e96 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryMethod.cs#ClockProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: ClockProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ClockProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ClockProvider.IResolver, + ClockProvider.IResolver, + ClockProvider.IResolver>, + ClockProvider.IResolver>, + ClockProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::Settings? _S0; + private global::Settings S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Settings()); + } + private volatile global::IClock? _S1; + private global::IClock S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(this.CreateClock(S0()))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Settings)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Settings[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ClockProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ClockProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::Settings ClockProvider.IResolver.Get() + => S0(); + global::IClock ClockProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ClockProvider.IResolver>.Get() + => new global::Settings[] { S0() }; + global::System.Collections.Generic.IEnumerable ClockProvider.IResolver>.Get() + => new global::IClock[] { S1() }; + global::System.IServiceProvider ClockProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ClockProvider.IResolver, + ClockProvider.IResolver, + ClockProvider.IResolver>, + ClockProvider.IResolver>, + ClockProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ClockProvider _root; + public Scope(ClockProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::Settings S0() => _root.S0(); + private global::IClock S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Settings)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Settings[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ClockProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ClockProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::Settings ClockProvider.IResolver.Get() + => S0(); + global::IClock ClockProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ClockProvider.IResolver>.Get() + => new global::Settings[] { S0() }; + global::System.Collections.Generic.IEnumerable ClockProvider.IResolver>.Get() + => new global::IClock[] { S1() }; + global::System.IServiceProvider ClockProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FactoryStaticMethod.cs b/src/Tests/IoCExamples/FactoryStaticMethod.cs new file mode 100644 index 00000000..6caaef65 --- /dev/null +++ b/src/Tests/IoCExamples/FactoryStaticMethod.cs @@ -0,0 +1,15 @@ +using AutoCtor; + +// A static factory has no receiver to choose, so it is called through the provider type. +[ServiceProvider] +[Transient(Factory = nameof(Create))] +public sealed partial class GreetingProvider +{ + private static IGreeting Create() => new Greeting("hello"); +} + +public interface IGreeting; +public class Greeting(string text) : IGreeting +{ + public string Text => text; +} diff --git a/src/Tests/IoCExamples/FactoryStaticMethod.cs#GreetingProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FactoryStaticMethod.cs#GreetingProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..cc4c33ea --- /dev/null +++ b/src/Tests/IoCExamples/FactoryStaticMethod.cs#GreetingProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,205 @@ +//HintName: GreetingProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class GreetingProvider : + global::System.IServiceProvider, + global::System.IDisposable, + GreetingProvider.IResolver, + GreetingProvider.IResolver>, + GreetingProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IGreeting S0() => Track(global::GreetingProvider.Create()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeting)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeting[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is GreetingProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is GreetingProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IGreeting GreetingProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable GreetingProvider.IResolver>.Get() + => new global::IGreeting[] { S0() }; + global::System.IServiceProvider GreetingProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + GreetingProvider.IResolver, + GreetingProvider.IResolver>, + GreetingProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly GreetingProvider _root; + public Scope(GreetingProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IGreeting S0() => Track(global::GreetingProvider.Create()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeting)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeting[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is GreetingProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is GreetingProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IGreeting GreetingProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable GreetingProvider.IResolver>.Get() + => new global::IGreeting[] { S0() }; + global::System.IServiceProvider GreetingProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/FallbackProvider.cs b/src/Tests/IoCExamples/FallbackProvider.cs new file mode 100644 index 00000000..a31393a2 --- /dev/null +++ b/src/Tests/IoCExamples/FallbackProvider.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using AutoCtor; + +// Anything the provider has no registration for is asked of the fallback rather than coming +// back null. The member is read on the miss path only, so it can be assigned from a +// constructor of your own, and the provider never disposes what it does not own. +// +// Declaring a fallback also says that constructor dependencies may come from outside, so +// IReporter is wired to it instead of being reported by ACTR012. Nothing is known about +// what comes back, so it takes no part in the graph: no ordering, no lifetime checks. +// +// A collection is both containers' registrations, the fallback's first. IEnumerable +// is the case that shows why: nothing here is registered for it, and an empty array would +// have been a wrong answer rather than a missing one. +[ServiceProvider(Fallback = nameof(_host))] +[Singleton] +[Singleton] +public sealed partial class HostedProvider +{ + private readonly IServiceProvider _host; + + public HostedProvider(IServiceProvider host) => _host = host; +} + +public interface IClock; +public interface IReporter; +public interface IAudit; +public interface ILedger; +public class Clock(IReporter reporter) : IClock +{ + public IReporter Reporter => reporter; +} +public class Ledger(IEnumerable audits) : ILedger +{ + public IEnumerable Audits => audits; +} diff --git a/src/Tests/IoCExamples/FallbackProvider.cs#HostedProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/FallbackProvider.cs#HostedProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..40027879 --- /dev/null +++ b/src/Tests/IoCExamples/FallbackProvider.cs#HostedProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: HostedProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class HostedProvider : + global::System.IServiceProvider, + global::System.IDisposable, + HostedProvider.IResolver, + HostedProvider.IResolver, + HostedProvider.IResolver>, + HostedProvider.IResolver>, + HostedProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock(Required(GetKeyedService(typeof(global::IReporter), null)))); + } + private volatile global::ILedger? _S1; + private global::ILedger S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Ledger(new global::IAudit[] { })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => this._host; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostedProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostedProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock HostedProvider.IResolver.Get() + => S0(); + global::ILedger HostedProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable HostedProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostedProvider.IResolver>.Get() + => new global::ILedger[] { S1() }; + global::System.IServiceProvider HostedProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + HostedProvider.IResolver, + HostedProvider.IResolver, + HostedProvider.IResolver>, + HostedProvider.IResolver>, + HostedProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly HostedProvider _root; + public Scope(HostedProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::ILedger S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => _root._host; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostedProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostedProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock HostedProvider.IResolver.Get() + => S0(); + global::ILedger HostedProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable HostedProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostedProvider.IResolver>.Get() + => new global::ILedger[] { S1() }; + global::System.IServiceProvider HostedProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericClosedRegistration.cs b/src/Tests/IoCExamples/GenericClosedRegistration.cs new file mode 100644 index 00000000..966e85d8 --- /dev/null +++ b/src/Tests/IoCExamples/GenericClosedRegistration.cs @@ -0,0 +1,10 @@ +using AutoCtor; + +// A fully closed generic is an ordinary registration -- no open generic machinery involved. +[ServiceProvider] +[Singleton, Box>] +public sealed partial class ClosedGenericProvider; + +public class Widget; +public interface IBox; +public class Box : IBox; diff --git a/src/Tests/IoCExamples/GenericClosedRegistration.cs#ClosedGenericProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericClosedRegistration.cs#ClosedGenericProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..be04c416 --- /dev/null +++ b/src/Tests/IoCExamples/GenericClosedRegistration.cs#ClosedGenericProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: ClosedGenericProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ClosedGenericProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ClosedGenericProvider.IResolver>, + ClosedGenericProvider.IResolver>>, + ClosedGenericProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IBox? _S0; + private global::IBox S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Box()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBox)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IBox[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ClosedGenericProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ClosedGenericProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IBox ClosedGenericProvider.IResolver>.Get() + => S0(); + global::System.Collections.Generic.IEnumerable> ClosedGenericProvider.IResolver>>.Get() + => new global::IBox[] { S0() }; + global::System.IServiceProvider ClosedGenericProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ClosedGenericProvider.IResolver>, + ClosedGenericProvider.IResolver>>, + ClosedGenericProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ClosedGenericProvider _root; + public Scope(ClosedGenericProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IBox S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBox)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IBox[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ClosedGenericProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ClosedGenericProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IBox ClosedGenericProvider.IResolver>.Get() + => S0(); + global::System.Collections.Generic.IEnumerable> ClosedGenericProvider.IResolver>>.Get() + => new global::IBox[] { S0() }; + global::System.IServiceProvider ClosedGenericProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs new file mode 100644 index 00000000..c2d9144f --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs @@ -0,0 +1,29 @@ +using AutoCtor; + +// The open implementation is [AutoConstruct], so its constructor does not exist yet AND +// its parameters have to be substituted for the closed construction. +[ServiceProvider] +[Singleton] +[Singleton(typeof(ICache<>), typeof(Cache<>))] +[Singleton] +public sealed partial class CacheProvider; + +public class Report; + +public interface IClock; +public class SystemClock : IClock; + +public interface ICache; + +[AutoConstruct] +public partial class Cache : ICache +{ + private readonly IClock _clock; +} + +public interface IReportService; + +public class ReportService : IReportService +{ + public ReportService(ICache cache) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#CacheProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#CacheProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f06f5121 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#CacheProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: CacheProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class CacheProvider : + global::System.IServiceProvider, + global::System.IDisposable, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver>, + CacheProvider.IResolver>, + CacheProvider.IResolver>, + CacheProvider.IResolver>>, + CacheProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SystemClock()); + } + private volatile global::IReportService? _S1; + private global::IReportService S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ReportService(S2())); + } + private volatile global::ICache? _S2; + private global::ICache S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Cache(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IReportService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICache)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IReportService[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ICache[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock CacheProvider.IResolver.Get() + => S0(); + global::IReportService CacheProvider.IResolver.Get() + => S1(); + global::ICache CacheProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::IReportService[] { S1() }; + global::System.Collections.Generic.IEnumerable> CacheProvider.IResolver>>.Get() + => new global::ICache[] { S2() }; + global::System.IServiceProvider CacheProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + CacheProvider.IResolver, + CacheProvider.IResolver, + CacheProvider.IResolver>, + CacheProvider.IResolver>, + CacheProvider.IResolver>, + CacheProvider.IResolver>>, + CacheProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly CacheProvider _root; + public Scope(CacheProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IReportService S1() => _root.S1(); + private global::ICache S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IReportService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ICache)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IReportService[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ICache[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CacheProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock CacheProvider.IResolver.Get() + => S0(); + global::IReportService CacheProvider.IResolver.Get() + => S1(); + global::ICache CacheProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable CacheProvider.IResolver>.Get() + => new global::IReportService[] { S1() }; + global::System.Collections.Generic.IEnumerable> CacheProvider.IResolver>>.Get() + => new global::ICache[] { S2() }; + global::System.IServiceProvider CacheProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#Cache[T].g.verified.cs b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#Cache[T].g.verified.cs new file mode 100644 index 00000000..fac26b5c --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenAutoConstruct.cs#Cache[T].g.verified.cs @@ -0,0 +1,15 @@ +//HintName: Cache[T].g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class Cache +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public Cache(global::IClock clock) + { + this._clock = clock; + } +} diff --git a/src/Tests/IoCExamples/GenericOpenChained.cs b/src/Tests/IoCExamples/GenericOpenChained.cs new file mode 100644 index 00000000..f291b03a --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenChained.cs @@ -0,0 +1,28 @@ +using AutoCtor; + +// A construction synthesised from one rule pulls in another, so the closure has to keep +// going rather than stopping after a single pass. +[ServiceProvider] +[Singleton(typeof(IValidator<>), typeof(Validator<>))] +[Singleton(typeof(IHandler<>), typeof(Handler<>))] +[Singleton] +public sealed partial class PipelineProvider; + +public class Command; + +public interface IValidator; +public class Validator : IValidator; + +public interface IHandler; + +public class Handler : IHandler +{ + public Handler(IValidator validator) { } +} + +public interface IPipeline; + +public class Pipeline : IPipeline +{ + public Pipeline(IHandler handler) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenChained.cs#PipelineProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenChained.cs#PipelineProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..1b931239 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenChained.cs#PipelineProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: PipelineProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PipelineProvider : + global::System.IServiceProvider, + global::System.IDisposable, + PipelineProvider.IResolver, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>>, + PipelineProvider.IResolver>>, + PipelineProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPipeline? _S0; + private global::IPipeline S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Pipeline(S1())); + } + private volatile global::IHandler? _S1; + private global::IHandler S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Handler(S2())); + } + private volatile global::IValidator? _S2; + private global::IValidator S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Validator()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPipeline)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPipeline[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IValidator[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PipelineProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PipelineProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPipeline PipelineProvider.IResolver.Get() + => S0(); + global::IHandler PipelineProvider.IResolver>.Get() + => S1(); + global::IValidator PipelineProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PipelineProvider.IResolver>.Get() + => new global::IPipeline[] { S0() }; + global::System.Collections.Generic.IEnumerable> PipelineProvider.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable> PipelineProvider.IResolver>>.Get() + => new global::IValidator[] { S2() }; + global::System.IServiceProvider PipelineProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + PipelineProvider.IResolver, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>, + PipelineProvider.IResolver>>, + PipelineProvider.IResolver>>, + PipelineProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PipelineProvider _root; + public Scope(PipelineProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPipeline S0() => _root.S0(); + private global::IHandler S1() => _root.S1(); + private global::IValidator S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPipeline)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPipeline[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IValidator[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PipelineProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PipelineProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPipeline PipelineProvider.IResolver.Get() + => S0(); + global::IHandler PipelineProvider.IResolver>.Get() + => S1(); + global::IValidator PipelineProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PipelineProvider.IResolver>.Get() + => new global::IPipeline[] { S0() }; + global::System.Collections.Generic.IEnumerable> PipelineProvider.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable> PipelineProvider.IResolver>>.Get() + => new global::IValidator[] { S2() }; + global::System.IServiceProvider PipelineProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenExplicitWins.cs b/src/Tests/IoCExamples/GenericOpenExplicitWins.cs new file mode 100644 index 00000000..352322c5 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenExplicitWins.cs @@ -0,0 +1,23 @@ +using AutoCtor; + +// An explicit closed registration takes precedence over the rule for that construction, +// so IStore uses SpecialStore rather than a synthesised Store. +[ServiceProvider] +[Singleton(typeof(IStore<>), typeof(Store<>))] +[Singleton, SpecialStore>] +[Singleton] +public sealed partial class StoreProvider; + +public class Special; +public class Ordinary; + +public interface IStore; +public class Store : IStore; +public class SpecialStore : IStore; + +public interface IStoreHost; + +public class StoreHost : IStoreHost +{ + public StoreHost(IStore special, IStore ordinary) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenExplicitWins.cs#StoreProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenExplicitWins.cs#StoreProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..ff9b476f --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenExplicitWins.cs#StoreProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,283 @@ +//HintName: StoreProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class StoreProvider : + global::System.IServiceProvider, + global::System.IDisposable, + StoreProvider.IResolver>, + StoreProvider.IResolver, + StoreProvider.IResolver>, + StoreProvider.IResolver>>, + StoreProvider.IResolver>, + StoreProvider.IResolver>>, + StoreProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IStore? _S0; + private global::IStore S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SpecialStore()); + } + private volatile global::IStoreHost? _S1; + private global::IStoreHost S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::StoreHost(S0(), S3())); + } + private volatile global::IStore? _S2; + private global::IStore S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Store()); + } + private volatile global::IStore? _S3; + private global::IStore S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::Store()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStore)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IStoreHost)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IStore)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IStore[] { S2(), S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStoreHost[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IStore[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IStore StoreProvider.IResolver>.Get() + => S0(); + global::IStoreHost StoreProvider.IResolver.Get() + => S1(); + global::IStore StoreProvider.IResolver>.Get() + => S3(); + global::System.Collections.Generic.IEnumerable> StoreProvider.IResolver>>.Get() + => new global::IStore[] { S2(), S0() }; + global::System.Collections.Generic.IEnumerable StoreProvider.IResolver>.Get() + => new global::IStoreHost[] { S1() }; + global::System.Collections.Generic.IEnumerable> StoreProvider.IResolver>>.Get() + => new global::IStore[] { S3() }; + global::System.IServiceProvider StoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + StoreProvider.IResolver>, + StoreProvider.IResolver, + StoreProvider.IResolver>, + StoreProvider.IResolver>>, + StoreProvider.IResolver>, + StoreProvider.IResolver>>, + StoreProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly StoreProvider _root; + public Scope(StoreProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IStore S0() => _root.S0(); + private global::IStoreHost S1() => _root.S1(); + private global::IStore S2() => _root.S2(); + private global::IStore S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStore)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IStoreHost)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IStore)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IStore[] { S2(), S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStoreHost[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IStore[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IStore StoreProvider.IResolver>.Get() + => S0(); + global::IStoreHost StoreProvider.IResolver.Get() + => S1(); + global::IStore StoreProvider.IResolver>.Get() + => S3(); + global::System.Collections.Generic.IEnumerable> StoreProvider.IResolver>>.Get() + => new global::IStore[] { S2(), S0() }; + global::System.Collections.Generic.IEnumerable StoreProvider.IResolver>.Get() + => new global::IStoreHost[] { S1() }; + global::System.Collections.Generic.IEnumerable> StoreProvider.IResolver>>.Get() + => new global::IStore[] { S3() }; + global::System.IServiceProvider StoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenLifetimes.cs b/src/Tests/IoCExamples/GenericOpenLifetimes.cs new file mode 100644 index 00000000..4e39bd62 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenLifetimes.cs @@ -0,0 +1,19 @@ +using AutoCtor; + +// The rule's lifetime carries to every construction made from it. +[ServiceProvider] +[Transient(typeof(IFactory<>), typeof(Factory<>))] +[Singleton] +public sealed partial class FactoryProvider; + +public class Part; + +public interface IFactory; +public class Factory : IFactory; + +public interface IBuilder; + +public class Builder : IBuilder +{ + public Builder(IFactory factory) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenLifetimes.cs#FactoryProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenLifetimes.cs#FactoryProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..14943dd6 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenLifetimes.cs#FactoryProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,235 @@ +//HintName: FactoryProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class FactoryProvider : + global::System.IServiceProvider, + global::System.IDisposable, + FactoryProvider.IResolver, + FactoryProvider.IResolver>, + FactoryProvider.IResolver>, + FactoryProvider.IResolver>>, + FactoryProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IBuilder? _S0; + private global::IBuilder S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Builder(S1())); + } + private global::IFactory S1() => new global::Factory(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBuilder)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IFactory)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBuilder[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IFactory[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IBuilder FactoryProvider.IResolver.Get() + => S0(); + global::IFactory FactoryProvider.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable FactoryProvider.IResolver>.Get() + => new global::IBuilder[] { S0() }; + global::System.Collections.Generic.IEnumerable> FactoryProvider.IResolver>>.Get() + => new global::IFactory[] { S1() }; + global::System.IServiceProvider FactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + FactoryProvider.IResolver, + FactoryProvider.IResolver>, + FactoryProvider.IResolver>, + FactoryProvider.IResolver>>, + FactoryProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly FactoryProvider _root; + public Scope(FactoryProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IBuilder S0() => _root.S0(); + private global::IFactory S1() => new global::Factory(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IBuilder)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IFactory)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBuilder[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IFactory[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IBuilder FactoryProvider.IResolver.Get() + => S0(); + global::IFactory FactoryProvider.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable FactoryProvider.IResolver>.Get() + => new global::IBuilder[] { S0() }; + global::System.Collections.Generic.IEnumerable> FactoryProvider.IResolver>>.Get() + => new global::IFactory[] { S1() }; + global::System.IServiceProvider FactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenLifetimes.cs.verified.txt b/src/Tests/IoCExamples/GenericOpenLifetimes.cs.verified.txt new file mode 100644 index 00000000..2446cae6 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenLifetimes.cs.verified.txt @@ -0,0 +1,24 @@ +{ + Diagnostics: [ + { + Location: /* +[Transient(typeof(IFactory<>), typeof(Factory<>))] +[Singleton] + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +public sealed partial class FactoryProvider; +*/ + : (5,1)-(5,29), + Message: The transient service 'Factory' is captured by the singleton 'Builder' and will not be recreated for each resolve, + Severity: Warning, + WarningLevel: 1, + Descriptor: { + Id: ACTR018, + Title: Transient service is captured by a singleton, + MessageFormat: The transient service '{0}' is captured by the singleton '{1}' and will not be recreated for each resolve, + Category: AutoCtor, + DefaultSeverity: Warning, + IsEnabledByDefault: true + } + } + ] +} \ No newline at end of file diff --git a/src/Tests/IoCExamples/GenericOpenRegistration.cs b/src/Tests/IoCExamples/GenericOpenRegistration.cs new file mode 100644 index 00000000..6819f4eb --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenRegistration.cs @@ -0,0 +1,29 @@ +using AutoCtor; + +// The open registration is a rule. Only IRepository is injected anywhere, so that is +// the single construction emitted -- IRepository is never asked for. +[ServiceProvider] +[Singleton] +[Singleton(typeof(IRepository<>), typeof(Repository<>))] +[Singleton] +public sealed partial class RepositoryProvider; + +public class User; +public class Order; + +public interface IDbSession; +public class DbSession : IDbSession; + +public interface IRepository; + +public class Repository : IRepository +{ + public Repository(IDbSession session) { } +} + +public interface IUserReport; + +public class UserReport : IUserReport +{ + public UserReport(IRepository users) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenRegistration.cs#RepositoryProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenRegistration.cs#RepositoryProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9814553e --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenRegistration.cs#RepositoryProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: RepositoryProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class RepositoryProvider : + global::System.IServiceProvider, + global::System.IDisposable, + RepositoryProvider.IResolver, + RepositoryProvider.IResolver, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>>, + RepositoryProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IDbSession? _S0; + private global::IDbSession S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::DbSession()); + } + private volatile global::IUserReport? _S1; + private global::IUserReport S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::UserReport(S2())); + } + private volatile global::IRepository? _S2; + private global::IRepository S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Repository(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IDbSession)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IUserReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRepository)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDbSession[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IUserReport[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IRepository[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RepositoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RepositoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IDbSession RepositoryProvider.IResolver.Get() + => S0(); + global::IUserReport RepositoryProvider.IResolver.Get() + => S1(); + global::IRepository RepositoryProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable RepositoryProvider.IResolver>.Get() + => new global::IDbSession[] { S0() }; + global::System.Collections.Generic.IEnumerable RepositoryProvider.IResolver>.Get() + => new global::IUserReport[] { S1() }; + global::System.Collections.Generic.IEnumerable> RepositoryProvider.IResolver>>.Get() + => new global::IRepository[] { S2() }; + global::System.IServiceProvider RepositoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + RepositoryProvider.IResolver, + RepositoryProvider.IResolver, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>, + RepositoryProvider.IResolver>>, + RepositoryProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly RepositoryProvider _root; + public Scope(RepositoryProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IDbSession S0() => _root.S0(); + private global::IUserReport S1() => _root.S1(); + private global::IRepository S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IDbSession)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IUserReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRepository)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDbSession[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IUserReport[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IRepository[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RepositoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RepositoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IDbSession RepositoryProvider.IResolver.Get() + => S0(); + global::IUserReport RepositoryProvider.IResolver.Get() + => S1(); + global::IRepository RepositoryProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable RepositoryProvider.IResolver>.Get() + => new global::IDbSession[] { S0() }; + global::System.Collections.Generic.IEnumerable RepositoryProvider.IResolver>.Get() + => new global::IUserReport[] { S1() }; + global::System.Collections.Generic.IEnumerable> RepositoryProvider.IResolver>>.Get() + => new global::IRepository[] { S2() }; + global::System.IServiceProvider RepositoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/GenericOpenSubstitution.cs b/src/Tests/IoCExamples/GenericOpenSubstitution.cs new file mode 100644 index 00000000..98f8a546 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenSubstitution.cs @@ -0,0 +1,29 @@ +using AutoCtor; + +// Handler is [AutoConstruct], so its constructor does not exist yet, and its dependency +// mentions T -- the predicted parameter list has to be substituted before it can be used. +[ServiceProvider] +[Singleton(typeof(ISubValidator<>), typeof(SubValidator<>))] +[Singleton(typeof(ISubHandler<>), typeof(SubHandler<>))] +[Singleton] +public sealed partial class SubstitutionProvider; + +public class Invoice; + +public interface ISubValidator; +public class SubValidator : ISubValidator; + +public interface ISubHandler; + +[AutoConstruct] +public partial class SubHandler : ISubHandler +{ + private readonly ISubValidator _validator; +} + +public interface ISubPipeline; + +public class SubPipeline : ISubPipeline +{ + public SubPipeline(ISubHandler handler) { } +} diff --git a/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubHandler[T].g.verified.cs b/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubHandler[T].g.verified.cs new file mode 100644 index 00000000..7e962767 --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubHandler[T].g.verified.cs @@ -0,0 +1,15 @@ +//HintName: SubHandler[T].g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class SubHandler +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public SubHandler(global::ISubValidator validator) + { + this._validator = validator; + } +} diff --git a/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubstitutionProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubstitutionProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..c065a2ab --- /dev/null +++ b/src/Tests/IoCExamples/GenericOpenSubstitution.cs#SubstitutionProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: SubstitutionProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class SubstitutionProvider : + global::System.IServiceProvider, + global::System.IDisposable, + SubstitutionProvider.IResolver, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>>, + SubstitutionProvider.IResolver>>, + SubstitutionProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ISubPipeline? _S0; + private global::ISubPipeline S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SubPipeline(S1())); + } + private volatile global::ISubHandler? _S1; + private global::ISubHandler S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SubHandler(S2())); + } + private volatile global::ISubValidator? _S2; + private global::ISubValidator S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::SubValidator()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISubPipeline)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISubHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ISubValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISubPipeline[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ISubHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ISubValidator[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SubstitutionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SubstitutionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ISubPipeline SubstitutionProvider.IResolver.Get() + => S0(); + global::ISubHandler SubstitutionProvider.IResolver>.Get() + => S1(); + global::ISubValidator SubstitutionProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable SubstitutionProvider.IResolver>.Get() + => new global::ISubPipeline[] { S0() }; + global::System.Collections.Generic.IEnumerable> SubstitutionProvider.IResolver>>.Get() + => new global::ISubHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable> SubstitutionProvider.IResolver>>.Get() + => new global::ISubValidator[] { S2() }; + global::System.IServiceProvider SubstitutionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + SubstitutionProvider.IResolver, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>, + SubstitutionProvider.IResolver>>, + SubstitutionProvider.IResolver>>, + SubstitutionProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly SubstitutionProvider _root; + public Scope(SubstitutionProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ISubPipeline S0() => _root.S0(); + private global::ISubHandler S1() => _root.S1(); + private global::ISubValidator S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISubPipeline)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ISubHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::ISubValidator)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISubPipeline[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ISubHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::ISubValidator[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SubstitutionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SubstitutionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ISubPipeline SubstitutionProvider.IResolver.Get() + => S0(); + global::ISubHandler SubstitutionProvider.IResolver>.Get() + => S1(); + global::ISubValidator SubstitutionProvider.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable SubstitutionProvider.IResolver>.Get() + => new global::ISubPipeline[] { S0() }; + global::System.Collections.Generic.IEnumerable> SubstitutionProvider.IResolver>>.Get() + => new global::ISubHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable> SubstitutionProvider.IResolver>>.Get() + => new global::ISubValidator[] { S2() }; + global::System.IServiceProvider SubstitutionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs b/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs new file mode 100644 index 00000000..65de7d90 --- /dev/null +++ b/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs @@ -0,0 +1,14 @@ +using AutoCtor; + +// Keyed registrations without the Microsoft package. GetKeyedService and +// GetRequiredKeyedService are ordinary public methods, so they stand on their own; +// only IKeyedServiceProvider is skipped, along with KeyedService.AnyKey, which is a +// value that lives in the package rather than anything registered here. +[ServiceProvider] +[Singleton] +[Singleton(Key = "remote")] +public sealed partial class StoreProvider; + +public interface IStore; +public class LocalStore : IStore; +public class RemoteStore : IStore; diff --git a/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs#StoreProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs#StoreProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..ad3b27d9 --- /dev/null +++ b/src/Tests/IoCExamples/KeyedWithoutAbstractions.cs#StoreProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,231 @@ +//HintName: StoreProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class StoreProvider : + global::System.IServiceProvider, + global::System.IDisposable, + StoreProvider.IResolver, + StoreProvider.IResolver>, + StoreProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IStore? _S0; + private global::IStore S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::LocalStore()); + } + private volatile global::IStore? _S1; + private global::IStore S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::RemoteStore()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStore)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStore[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IStore) && object.Equals(key, "remote")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "remote")) + return probe ? this : (object)(new global::IStore[] { S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IStore StoreProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable StoreProvider.IResolver>.Get() + => new global::IStore[] { S0() }; + global::System.IServiceProvider StoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + StoreProvider.IResolver, + StoreProvider.IResolver>, + StoreProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly StoreProvider _root; + public Scope(StoreProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IStore S0() => _root.S0(); + private global::IStore S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStore)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStore[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IStore) && object.Equals(key, "remote")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "remote")) + return probe ? this : (object)(new global::IStore[] { S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IStore StoreProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable StoreProvider.IResolver>.Get() + => new global::IStore[] { S0() }; + global::System.IServiceProvider StoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ModuleImport.cs b/src/Tests/IoCExamples/ModuleImport.cs new file mode 100644 index 00000000..839d1648 --- /dev/null +++ b/src/Tests/IoCExamples/ModuleImport.cs @@ -0,0 +1,25 @@ +using AutoCtor; + +// Imports expand where they are written, so the provider's own IClock, written after them, +// wins single resolution over the module's. +[ServiceProvider] +[Import] +[Import(typeof(GreeterModule))] +[Singleton] +public sealed partial class ModuleImportProvider; + +[Singleton] +public class ClockModule; + +[Transient] +public class GreeterModule; + +public interface IClock; +public class SystemClock : IClock; +public class TestClock : IClock; + +public interface IGreeter; +public class Greeter(IClock clock) : IGreeter +{ + public IClock Clock => clock; +} diff --git a/src/Tests/IoCExamples/ModuleImport.cs#ModuleImportProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ModuleImport.cs#ModuleImportProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..051bdfeb --- /dev/null +++ b/src/Tests/IoCExamples/ModuleImport.cs#ModuleImportProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,245 @@ +//HintName: ModuleImportProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ModuleImportProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ModuleImportProvider.IResolver, + ModuleImportProvider.IResolver, + ModuleImportProvider.IResolver>, + ModuleImportProvider.IResolver>, + ModuleImportProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SystemClock()); + } + private global::IGreeter S1() => new global::Greeter(S2()); + private volatile global::IClock? _S2; + private global::IClock S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::TestClock()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ModuleImportProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ModuleImportProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock ModuleImportProvider.IResolver.Get() + => S2(); + global::IGreeter ModuleImportProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ModuleImportProvider.IResolver>.Get() + => new global::IClock[] { S0(), S2() }; + global::System.Collections.Generic.IEnumerable ModuleImportProvider.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider ModuleImportProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ModuleImportProvider.IResolver, + ModuleImportProvider.IResolver, + ModuleImportProvider.IResolver>, + ModuleImportProvider.IResolver>, + ModuleImportProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ModuleImportProvider _root; + public Scope(ModuleImportProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IGreeter S1() => new global::Greeter(S2()); + private global::IClock S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ModuleImportProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ModuleImportProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock ModuleImportProvider.IResolver.Get() + => S2(); + global::IGreeter ModuleImportProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ModuleImportProvider.IResolver>.Get() + => new global::IClock[] { S0(), S2() }; + global::System.Collections.Generic.IEnumerable ModuleImportProvider.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider ModuleImportProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ModuleNested.cs b/src/Tests/IoCExamples/ModuleNested.cs new file mode 100644 index 00000000..c9080b60 --- /dev/null +++ b/src/Tests/IoCExamples/ModuleNested.cs @@ -0,0 +1,25 @@ +using AutoCtor; + +// The two modules import each other. Each is expanded once, and the scan in the inner one +// looks in the module's assembly. +[ServiceProvider] +[Import] +public sealed partial class NestedModuleProvider; + +[Singleton] +[Import] +public class OuterModule; + +[ScanSingleton(typeof(IPlugin))] +[Import] +public class InnerModule; + +public interface IPlugin; +public class FirstPlugin : IPlugin; +public class SecondPlugin : IPlugin; + +public interface IPluginHost; +public class PluginHost(System.Collections.Generic.IEnumerable plugins) : IPluginHost +{ + public System.Collections.Generic.IEnumerable Plugins => plugins; +} diff --git a/src/Tests/IoCExamples/ModuleNested.cs#NestedModuleProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ModuleNested.cs#NestedModuleProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..4ff886c9 --- /dev/null +++ b/src/Tests/IoCExamples/ModuleNested.cs#NestedModuleProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,253 @@ +//HintName: NestedModuleProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class NestedModuleProvider : + global::System.IServiceProvider, + global::System.IDisposable, + NestedModuleProvider.IResolver, + NestedModuleProvider.IResolver, + NestedModuleProvider.IResolver>, + NestedModuleProvider.IResolver>, + NestedModuleProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPluginHost? _S0; + private global::IPluginHost S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PluginHost(new global::IPlugin[] { S1(), S2() })); + } + private volatile global::IPlugin? _S1; + private global::IPlugin S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::FirstPlugin()); + } + private volatile global::IPlugin? _S2; + private global::IPlugin S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::SecondPlugin()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S1(), S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is NestedModuleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is NestedModuleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPluginHost NestedModuleProvider.IResolver.Get() + => S0(); + global::IPlugin NestedModuleProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable NestedModuleProvider.IResolver>.Get() + => new global::IPluginHost[] { S0() }; + global::System.Collections.Generic.IEnumerable NestedModuleProvider.IResolver>.Get() + => new global::IPlugin[] { S1(), S2() }; + global::System.IServiceProvider NestedModuleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + NestedModuleProvider.IResolver, + NestedModuleProvider.IResolver, + NestedModuleProvider.IResolver>, + NestedModuleProvider.IResolver>, + NestedModuleProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly NestedModuleProvider _root; + public Scope(NestedModuleProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPluginHost S0() => _root.S0(); + private global::IPlugin S1() => _root.S1(); + private global::IPlugin S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S1(), S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is NestedModuleProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is NestedModuleProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPluginHost NestedModuleProvider.IResolver.Get() + => S0(); + global::IPlugin NestedModuleProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable NestedModuleProvider.IResolver>.Get() + => new global::IPluginHost[] { S0() }; + global::System.Collections.Generic.IEnumerable NestedModuleProvider.IResolver>.Get() + => new global::IPlugin[] { S1(), S2() }; + global::System.IServiceProvider NestedModuleProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ModuleStaticFactory.cs b/src/Tests/IoCExamples/ModuleStaticFactory.cs new file mode 100644 index 00000000..f0b69538 --- /dev/null +++ b/src/Tests/IoCExamples/ModuleStaticFactory.cs @@ -0,0 +1,23 @@ +using AutoCtor; + +// A module is never instantiated, so its factory is static and called through the module. +// Its parameters still resolve against the provider. +[ServiceProvider] +[Import] +[Singleton] +public sealed partial class ModuleFactoryProvider; + +[Transient(Factory = nameof(Create))] +public class GreetingModule +{ + internal static IGreeting Create(IGreetingClock clock) => new Greeting(clock); +} + +public interface IGreetingClock; +public class GreetingClock : IGreetingClock; + +public interface IGreeting; +public class Greeting(IGreetingClock clock) : IGreeting +{ + public IGreetingClock Clock => clock; +} diff --git a/src/Tests/IoCExamples/ModuleStaticFactory.cs#ModuleFactoryProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ModuleStaticFactory.cs#ModuleFactoryProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..d2587db6 --- /dev/null +++ b/src/Tests/IoCExamples/ModuleStaticFactory.cs#ModuleFactoryProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,235 @@ +//HintName: ModuleFactoryProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ModuleFactoryProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ModuleFactoryProvider.IResolver, + ModuleFactoryProvider.IResolver, + ModuleFactoryProvider.IResolver>, + ModuleFactoryProvider.IResolver>, + ModuleFactoryProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IGreeting S0() => Track(global::GreetingModule.Create(S1())); + private volatile global::IGreetingClock? _S1; + private global::IGreetingClock S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::GreetingClock()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeting)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreetingClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeting[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreetingClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ModuleFactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ModuleFactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IGreeting ModuleFactoryProvider.IResolver.Get() + => S0(); + global::IGreetingClock ModuleFactoryProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ModuleFactoryProvider.IResolver>.Get() + => new global::IGreeting[] { S0() }; + global::System.Collections.Generic.IEnumerable ModuleFactoryProvider.IResolver>.Get() + => new global::IGreetingClock[] { S1() }; + global::System.IServiceProvider ModuleFactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ModuleFactoryProvider.IResolver, + ModuleFactoryProvider.IResolver, + ModuleFactoryProvider.IResolver>, + ModuleFactoryProvider.IResolver>, + ModuleFactoryProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ModuleFactoryProvider _root; + public Scope(ModuleFactoryProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IGreeting S0() => Track(global::GreetingModule.Create(S1())); + private global::IGreetingClock S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeting)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreetingClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeting[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreetingClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ModuleFactoryProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ModuleFactoryProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IGreeting ModuleFactoryProvider.IResolver.Get() + => S0(); + global::IGreetingClock ModuleFactoryProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ModuleFactoryProvider.IResolver>.Get() + => new global::IGreeting[] { S0() }; + global::System.Collections.Generic.IEnumerable ModuleFactoryProvider.IResolver>.Get() + => new global::IGreetingClock[] { S1() }; + global::System.IServiceProvider ModuleFactoryProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ResolverDuplicateTypes.cs b/src/Tests/IoCExamples/ResolverDuplicateTypes.cs new file mode 100644 index 00000000..27a1ca23 --- /dev/null +++ b/src/Tests/IoCExamples/ResolverDuplicateTypes.cs @@ -0,0 +1,29 @@ +using AutoCtor; +using System; +using System.Collections.Generic; + +// Two registrations that collide with something the provider resolves on its own. Both are +// legal and both win, the way a later registration wins anywhere else: IEnumerable +// would otherwise be the collection built from the IPlugin registrations, and IServiceProvider +// would otherwise be the provider itself. +// +// A chain of ifs never noticed -- the first branch returned and the rest were unreachable. A +// resolver interface cannot be implemented twice for the same type, so this is what proves +// the built-in resolvers are skipped rather than merely ordered after the registrations. +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton, PluginList>] +[Singleton] +public sealed partial class DuplicateResolverProvider; + +public interface IPlugin; +public class FirstPlugin : IPlugin; +public class SecondPlugin : IPlugin; + +public class PluginList : List; + +public class AmbientProvider : IServiceProvider +{ + public object? GetService(Type serviceType) => null; +} diff --git a/src/Tests/IoCExamples/ResolverDuplicateTypes.cs#DuplicateResolverProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ResolverDuplicateTypes.cs#DuplicateResolverProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9962d392 --- /dev/null +++ b/src/Tests/IoCExamples/ResolverDuplicateTypes.cs#DuplicateResolverProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,271 @@ +//HintName: DuplicateResolverProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DuplicateResolverProvider : + global::System.IServiceProvider, + global::System.IDisposable, + DuplicateResolverProvider.IResolver, + DuplicateResolverProvider.IResolver>, + DuplicateResolverProvider.IResolver, + DuplicateResolverProvider.IResolver>>, + DuplicateResolverProvider.IResolver> + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPlugin? _S0; + private global::IPlugin S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::FirstPlugin()); + } + private volatile global::IPlugin? _S1; + private global::IPlugin S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SecondPlugin()); + } + private volatile global::System.Collections.Generic.IEnumerable? _S2; + private global::System.Collections.Generic.IEnumerable S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::PluginList()); + } + private volatile global::System.IServiceProvider? _S3; + private global::System.IServiceProvider S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::AmbientProvider()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::System.Collections.Generic.IEnumerable[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::System.IServiceProvider[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DuplicateResolverProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DuplicateResolverProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPlugin DuplicateResolverProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable DuplicateResolverProvider.IResolver>.Get() + => S2(); + global::System.IServiceProvider DuplicateResolverProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable> DuplicateResolverProvider.IResolver>>.Get() + => new global::System.Collections.Generic.IEnumerable[] { S2() }; + global::System.Collections.Generic.IEnumerable DuplicateResolverProvider.IResolver>.Get() + => new global::System.IServiceProvider[] { S3() }; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + DuplicateResolverProvider.IResolver, + DuplicateResolverProvider.IResolver>, + DuplicateResolverProvider.IResolver, + DuplicateResolverProvider.IResolver>>, + DuplicateResolverProvider.IResolver> + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DuplicateResolverProvider _root; + public Scope(DuplicateResolverProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPlugin S0() => _root.S0(); + private global::IPlugin S1() => _root.S1(); + private global::System.Collections.Generic.IEnumerable S2() => _root.S2(); + private global::System.IServiceProvider S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::System.Collections.Generic.IEnumerable[] { S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::System.IServiceProvider[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DuplicateResolverProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DuplicateResolverProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPlugin DuplicateResolverProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable DuplicateResolverProvider.IResolver>.Get() + => S2(); + global::System.IServiceProvider DuplicateResolverProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable> DuplicateResolverProvider.IResolver>>.Get() + => new global::System.Collections.Generic.IEnumerable[] { S2() }; + global::System.Collections.Generic.IEnumerable DuplicateResolverProvider.IResolver>.Get() + => new global::System.IServiceProvider[] { S3() }; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScanAs.cs b/src/Tests/IoCExamples/ScanAs.cs new file mode 100644 index 00000000..6e2f5cd5 --- /dev/null +++ b/src/Tests/IoCExamples/ScanAs.cs @@ -0,0 +1,16 @@ +using AutoCtor; + +// As chooses what each match is registered under: here each notifier as itself and as every +// interface it has. The registration after the scan wins IAuditable, as it would after one +// written out by hand, and the collection still has both. +[ServiceProvider] +[ScanSingleton(typeof(INotifier), As = ScanAs.Self | ScanAs.ImplementedInterfaces)] +[Singleton] +public sealed partial class NotifierProvider; + +public interface INotifier; +public interface IAuditable; + +public class EmailNotifier : INotifier, IAuditable; +public class SmsNotifier : INotifier; +public class AuditLog : IAuditable; diff --git a/src/Tests/IoCExamples/ScanAs.cs#NotifierProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScanAs.cs#NotifierProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f1197289 --- /dev/null +++ b/src/Tests/IoCExamples/ScanAs.cs#NotifierProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,323 @@ +//HintName: NotifierProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class NotifierProvider : + global::System.IServiceProvider, + global::System.IDisposable, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::EmailNotifier? _S0; + private global::EmailNotifier S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::EmailNotifier()); + } + private volatile global::INotifier? _S1; + private global::INotifier S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::EmailNotifier()); + } + private volatile global::IAuditable? _S2; + private global::IAuditable S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::EmailNotifier()); + } + private volatile global::SmsNotifier? _S3; + private global::SmsNotifier S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::SmsNotifier()); + } + private volatile global::INotifier? _S4; + private global::INotifier S4() + { + var service = _S4; + if (service is not null) + return service; + lock (_lock) + return (_S4 ??= new global::SmsNotifier()); + } + private volatile global::IAuditable? _S5; + private global::IAuditable S5() + { + var service = _S5; + if (service is not null) + return service; + lock (_lock) + return (_S5 ??= new global::AuditLog()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::EmailNotifier)) + return probe ? this : (object)(S0()); + if (type == typeof(global::INotifier)) + return probe ? this : (object)(S4()); + if (type == typeof(global::IAuditable)) + return probe ? this : (object)(S5()); + if (type == typeof(global::SmsNotifier)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::EmailNotifier[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::INotifier[] { S1(), S4() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAuditable[] { S2(), S5() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SmsNotifier[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is NotifierProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is NotifierProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::EmailNotifier NotifierProvider.IResolver.Get() + => S0(); + global::INotifier NotifierProvider.IResolver.Get() + => S4(); + global::IAuditable NotifierProvider.IResolver.Get() + => S5(); + global::SmsNotifier NotifierProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::EmailNotifier[] { S0() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::INotifier[] { S1(), S4() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::IAuditable[] { S2(), S5() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::SmsNotifier[] { S3() }; + global::System.IServiceProvider NotifierProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver>, + NotifierProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly NotifierProvider _root; + public Scope(NotifierProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::EmailNotifier S0() => _root.S0(); + private global::INotifier S1() => _root.S1(); + private global::IAuditable S2() => _root.S2(); + private global::SmsNotifier S3() => _root.S3(); + private global::INotifier S4() => _root.S4(); + private global::IAuditable S5() => _root.S5(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::EmailNotifier)) + return probe ? this : (object)(S0()); + if (type == typeof(global::INotifier)) + return probe ? this : (object)(S4()); + if (type == typeof(global::IAuditable)) + return probe ? this : (object)(S5()); + if (type == typeof(global::SmsNotifier)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::EmailNotifier[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::INotifier[] { S1(), S4() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAuditable[] { S2(), S5() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SmsNotifier[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is NotifierProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is NotifierProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::EmailNotifier NotifierProvider.IResolver.Get() + => S0(); + global::INotifier NotifierProvider.IResolver.Get() + => S4(); + global::IAuditable NotifierProvider.IResolver.Get() + => S5(); + global::SmsNotifier NotifierProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::EmailNotifier[] { S0() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::INotifier[] { S1(), S4() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::IAuditable[] { S2(), S5() }; + global::System.Collections.Generic.IEnumerable NotifierProvider.IResolver>.Get() + => new global::SmsNotifier[] { S3() }; + global::System.IServiceProvider NotifierProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScanFilter.cs b/src/Tests/IoCExamples/ScanFilter.cs new file mode 100644 index 00000000..c6826f06 --- /dev/null +++ b/src/Tests/IoCExamples/ScanFilter.cs @@ -0,0 +1,28 @@ +using AutoCtor; +using System.Collections.Generic; + +// A scan registers every concrete class it can see that is assignable to the service, in +// name order. Abstract classes, interfaces, generic classes and types the provider cannot +// name are left out. +[ServiceProvider] +[ScanSingleton(typeof(IPlugin))] +[Singleton] +public sealed partial class PluginProvider +{ + private class HiddenPlugin : IPlugin; +} + +public interface IPlugin; +public interface IExtraPlugin : IPlugin; +public class AudioPlugin : IPlugin; +public class VideoPlugin : IPlugin; +public class ImagePlugin : IExtraPlugin; +public abstract class PluginBase : IPlugin; +public class GenericPlugin : IPlugin; + +public interface IPluginHost; + +public class PluginHost : IPluginHost +{ + public PluginHost(IEnumerable plugins) { } +} diff --git a/src/Tests/IoCExamples/ScanFilter.cs#PluginProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScanFilter.cs#PluginProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..44e1b3d6 --- /dev/null +++ b/src/Tests/IoCExamples/ScanFilter.cs#PluginProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,263 @@ +//HintName: PluginProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PluginProvider : + global::System.IServiceProvider, + global::System.IDisposable, + PluginProvider.IResolver, + PluginProvider.IResolver, + PluginProvider.IResolver>, + PluginProvider.IResolver>, + PluginProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPlugin? _S0; + private global::IPlugin S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::AudioPlugin()); + } + private volatile global::IPlugin? _S1; + private global::IPlugin S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ImagePlugin()); + } + private volatile global::IPlugin? _S2; + private global::IPlugin S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::VideoPlugin()); + } + private volatile global::IPluginHost? _S3; + private global::IPluginHost S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::PluginHost(new global::IPlugin[] { S0(), S1(), S2() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PluginProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PluginProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPlugin PluginProvider.IResolver.Get() + => S2(); + global::IPluginHost PluginProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable PluginProvider.IResolver>.Get() + => new global::IPlugin[] { S0(), S1(), S2() }; + global::System.Collections.Generic.IEnumerable PluginProvider.IResolver>.Get() + => new global::IPluginHost[] { S3() }; + global::System.IServiceProvider PluginProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + PluginProvider.IResolver, + PluginProvider.IResolver, + PluginProvider.IResolver>, + PluginProvider.IResolver>, + PluginProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PluginProvider _root; + public Scope(PluginProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPlugin S0() => _root.S0(); + private global::IPlugin S1() => _root.S1(); + private global::IPlugin S2() => _root.S2(); + private global::IPluginHost S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S2()); + if (type == typeof(global::IPluginHost)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPluginHost[] { S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PluginProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PluginProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPlugin PluginProvider.IResolver.Get() + => S2(); + global::IPluginHost PluginProvider.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable PluginProvider.IResolver>.Get() + => new global::IPlugin[] { S0(), S1(), S2() }; + global::System.Collections.Generic.IEnumerable PluginProvider.IResolver>.Get() + => new global::IPluginHost[] { S3() }; + global::System.IServiceProvider PluginProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScanOpenGeneric.cs b/src/Tests/IoCExamples/ScanOpenGeneric.cs new file mode 100644 index 00000000..46678d65 --- /dev/null +++ b/src/Tests/IoCExamples/ScanOpenGeneric.cs @@ -0,0 +1,23 @@ +using AutoCtor; + +// A scan for an open generic finds every construction of it, and registers each type under +// the closed service it implements. +[ServiceProvider] +[ScanTransient(typeof(IHandler<>))] +[Transient] +public sealed partial class HandlerProvider; + +public interface IHandler; + +public class CreateOrder; +public class CancelOrder; + +public class CreateOrderHandler : IHandler; +public class CancelOrderHandler : IHandler; + +public interface IDispatcher; + +public class Dispatcher : IDispatcher +{ + public Dispatcher(IHandler create, IHandler cancel) { } +} diff --git a/src/Tests/IoCExamples/ScanOpenGeneric.cs#HandlerProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScanOpenGeneric.cs#HandlerProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..5662e4e2 --- /dev/null +++ b/src/Tests/IoCExamples/ScanOpenGeneric.cs#HandlerProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,249 @@ +//HintName: HandlerProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class HandlerProvider : + global::System.IServiceProvider, + global::System.IDisposable, + HandlerProvider.IResolver>, + HandlerProvider.IResolver>, + HandlerProvider.IResolver, + HandlerProvider.IResolver>>, + HandlerProvider.IResolver>>, + HandlerProvider.IResolver>, + HandlerProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + private global::IDispatcher S2() => new global::Dispatcher(S1(), S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IDispatcher)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDispatcher[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IHandler HandlerProvider.IResolver>.Get() + => S0(); + global::IHandler HandlerProvider.IResolver>.Get() + => S1(); + global::IDispatcher HandlerProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable> HandlerProvider.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> HandlerProvider.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable HandlerProvider.IResolver>.Get() + => new global::IDispatcher[] { S2() }; + global::System.IServiceProvider HandlerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + HandlerProvider.IResolver>, + HandlerProvider.IResolver>, + HandlerProvider.IResolver, + HandlerProvider.IResolver>>, + HandlerProvider.IResolver>>, + HandlerProvider.IResolver>, + HandlerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly HandlerProvider _root; + public Scope(HandlerProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + private global::IDispatcher S2() => new global::Dispatcher(S1(), S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IDispatcher)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDispatcher[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HandlerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IHandler HandlerProvider.IResolver>.Get() + => S0(); + global::IHandler HandlerProvider.IResolver>.Get() + => S1(); + global::IDispatcher HandlerProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable> HandlerProvider.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> HandlerProvider.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.Collections.Generic.IEnumerable HandlerProvider.IResolver>.Get() + => new global::IDispatcher[] { S2() }; + global::System.IServiceProvider HandlerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScanTypeNameFilter.cs b/src/Tests/IoCExamples/ScanTypeNameFilter.cs new file mode 100644 index 00000000..b091d042 --- /dev/null +++ b/src/Tests/IoCExamples/ScanTypeNameFilter.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// TypeNameFilter narrows a scan to types whose name matches, where * matches anything. +[ServiceProvider] +[ScanScoped(TypeNameFilter = "*Repository", As = ScanAs.ImplementedInterfaces)] +public sealed partial class DataStoreProvider; + +public interface ICustomerRepository; +public interface IOrderRepository; +public class CustomerRepository : ICustomerRepository; +public class OrderRepository : IOrderRepository; +public class RepositoryHelper : IOrderRepository; diff --git a/src/Tests/IoCExamples/ScanTypeNameFilter.cs#DataStoreProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScanTypeNameFilter.cs#DataStoreProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..e38a6c6d --- /dev/null +++ b/src/Tests/IoCExamples/ScanTypeNameFilter.cs#DataStoreProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,221 @@ +//HintName: DataStoreProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DataStoreProvider : + global::System.IServiceProvider, + global::System.IDisposable, + DataStoreProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DataStoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DataStoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::System.IServiceProvider DataStoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + DataStoreProvider.IResolver, + DataStoreProvider.IResolver, + DataStoreProvider.IResolver>, + DataStoreProvider.IResolver>, + DataStoreProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DataStoreProvider _root; + public Scope(DataStoreProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private volatile global::ICustomerRepository? _S0; + private global::ICustomerRepository S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::CustomerRepository()); + } + private volatile global::IOrderRepository? _S1; + private global::IOrderRepository S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::OrderRepository()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ICustomerRepository)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IOrderRepository)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICustomerRepository[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IOrderRepository[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DataStoreProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DataStoreProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ICustomerRepository DataStoreProvider.IResolver.Get() + => S0(); + global::IOrderRepository DataStoreProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable DataStoreProvider.IResolver>.Get() + => new global::ICustomerRepository[] { S0() }; + global::System.Collections.Generic.IEnumerable DataStoreProvider.IResolver>.Get() + => new global::IOrderRepository[] { S1() }; + global::System.IServiceProvider DataStoreProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs b/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs new file mode 100644 index 00000000..ea56ecb7 --- /dev/null +++ b/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// ? in a TypeNameFilter matches exactly one character. +[ServiceProvider] +[ScanSingleton(typeof(IStage), TypeNameFilter = "Stage?")] +public sealed partial class StageProvider; + +public interface IStage; +public class Stage1 : IStage; +public class Stage2 : IStage; +public class Stage10 : IStage; +public class Stage : IStage; diff --git a/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs#StageProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs#StageProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f04eee01 --- /dev/null +++ b/src/Tests/IoCExamples/ScanTypeNameFilterSingleChar.cs#StageProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,223 @@ +//HintName: StageProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class StageProvider : + global::System.IServiceProvider, + global::System.IDisposable, + StageProvider.IResolver, + StageProvider.IResolver>, + StageProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IStage? _S0; + private global::IStage S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Stage1()); + } + private volatile global::IStage? _S1; + private global::IStage S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Stage2()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStage)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStage[] { S0(), S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StageProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StageProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IStage StageProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable StageProvider.IResolver>.Get() + => new global::IStage[] { S0(), S1() }; + global::System.IServiceProvider StageProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + StageProvider.IResolver, + StageProvider.IResolver>, + StageProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly StageProvider _root; + public Scope(StageProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IStage S0() => _root.S0(); + private global::IStage S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IStage)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IStage[] { S0(), S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is StageProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is StageProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IStage StageProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable StageProvider.IResolver>.Get() + => new global::IStage[] { S0(), S1() }; + global::System.IServiceProvider StageProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs b/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs new file mode 100644 index 00000000..cf362c70 --- /dev/null +++ b/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs @@ -0,0 +1,20 @@ +using AutoCtor; + +// Nothing here is scoped and nothing references Microsoft.Extensions.DependencyInjection, +// and the provider still has CreateScope. The scope is an IServiceProvider that is also +// IDisposable, so `using var scope = provider.CreateScope()` compiles either way; the +// Microsoft interfaces are added on top of that shape wherever the reference exists. +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class ScopelessProvider; + +public interface IClock; +public class Clock : IClock; + +public interface IJob; + +public class Job : IJob +{ + public Job(IClock clock) { } +} diff --git a/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs#ScopelessProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs#ScopelessProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..3a3b63bb --- /dev/null +++ b/src/Tests/IoCExamples/ScopeWithoutAbstractions.cs#ScopelessProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,235 @@ +//HintName: ScopelessProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ScopelessProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ScopelessProvider.IResolver, + ScopelessProvider.IResolver, + ScopelessProvider.IResolver>, + ScopelessProvider.IResolver>, + ScopelessProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + private global::IJob S1() => new global::Job(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IJob)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IJob[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopelessProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopelessProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock ScopelessProvider.IResolver.Get() + => S0(); + global::IJob ScopelessProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopelessProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopelessProvider.IResolver>.Get() + => new global::IJob[] { S1() }; + global::System.IServiceProvider ScopelessProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ScopelessProvider.IResolver, + ScopelessProvider.IResolver, + ScopelessProvider.IResolver>, + ScopelessProvider.IResolver>, + ScopelessProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ScopelessProvider _root; + public Scope(ScopelessProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IJob S1() => new global::Job(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IJob)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IJob[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ScopelessProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ScopelessProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock ScopelessProvider.IResolver.Get() + => S0(); + global::IJob ScopelessProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable ScopelessProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ScopelessProvider.IResolver>.Get() + => new global::IJob[] { S1() }; + global::System.IServiceProvider ScopelessProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonGenericAttribute.cs b/src/Tests/IoCExamples/SingletonGenericAttribute.cs new file mode 100644 index 00000000..5f804762 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonGenericAttribute.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +public sealed partial class GenericAttributeProvider; + +public interface ITheService; +public class TheService : ITheService; diff --git a/src/Tests/IoCExamples/SingletonGenericAttribute.cs#GenericAttributeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonGenericAttribute.cs#GenericAttributeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f4b7c905 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonGenericAttribute.cs#GenericAttributeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: GenericAttributeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class GenericAttributeProvider : + global::System.IServiceProvider, + global::System.IDisposable, + GenericAttributeProvider.IResolver, + GenericAttributeProvider.IResolver>, + GenericAttributeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ITheService? _S0; + private global::ITheService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::TheService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITheService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITheService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is GenericAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is GenericAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ITheService GenericAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable GenericAttributeProvider.IResolver>.Get() + => new global::ITheService[] { S0() }; + global::System.IServiceProvider GenericAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + GenericAttributeProvider.IResolver, + GenericAttributeProvider.IResolver>, + GenericAttributeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly GenericAttributeProvider _root; + public Scope(GenericAttributeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ITheService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITheService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITheService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is GenericAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is GenericAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ITheService GenericAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable GenericAttributeProvider.IResolver>.Get() + => new global::ITheService[] { S0() }; + global::System.IServiceProvider GenericAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonMultipleServices.cs b/src/Tests/IoCExamples/SingletonMultipleServices.cs new file mode 100644 index 00000000..e00e8ce2 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonMultipleServices.cs @@ -0,0 +1,14 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class MultipleServicesProvider; + +public interface IServiceOne; +public interface IServiceTwo; +public interface IServiceThree; +public class ServiceOne : IServiceOne; +public class ServiceTwo : IServiceTwo; +public class ServiceThree : IServiceThree; diff --git a/src/Tests/IoCExamples/SingletonMultipleServices.cs#MultipleServicesProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonMultipleServices.cs#MultipleServicesProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..8f23b6bc --- /dev/null +++ b/src/Tests/IoCExamples/SingletonMultipleServices.cs#MultipleServicesProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: MultipleServicesProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class MultipleServicesProvider : + global::System.IServiceProvider, + global::System.IDisposable, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IServiceOne? _S0; + private global::IServiceOne S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ServiceOne()); + } + private volatile global::IServiceTwo? _S1; + private global::IServiceTwo S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ServiceTwo()); + } + private volatile global::IServiceThree? _S2; + private global::IServiceThree S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::ServiceThree()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IServiceOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IServiceTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IServiceThree)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceThree[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MultipleServicesProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MultipleServicesProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IServiceOne MultipleServicesProvider.IResolver.Get() + => S0(); + global::IServiceTwo MultipleServicesProvider.IResolver.Get() + => S1(); + global::IServiceThree MultipleServicesProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceOne[] { S0() }; + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceThree[] { S2() }; + global::System.IServiceProvider MultipleServicesProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver>, + MultipleServicesProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly MultipleServicesProvider _root; + public Scope(MultipleServicesProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IServiceOne S0() => _root.S0(); + private global::IServiceTwo S1() => _root.S1(); + private global::IServiceThree S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IServiceOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IServiceTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IServiceThree)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IServiceThree[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is MultipleServicesProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is MultipleServicesProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IServiceOne MultipleServicesProvider.IResolver.Get() + => S0(); + global::IServiceTwo MultipleServicesProvider.IResolver.Get() + => S1(); + global::IServiceThree MultipleServicesProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceOne[] { S0() }; + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable MultipleServicesProvider.IResolver>.Get() + => new global::IServiceThree[] { S2() }; + global::System.IServiceProvider MultipleServicesProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonNestedProvider.cs b/src/Tests/IoCExamples/SingletonNestedProvider.cs new file mode 100644 index 00000000..0517e45a --- /dev/null +++ b/src/Tests/IoCExamples/SingletonNestedProvider.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +namespace Nested.Namespace; + +public partial class OuterContainer +{ + [ServiceProvider] + [Singleton] + public sealed partial class InnerProvider; +} + +public interface INestedService; +public class NestedService : INestedService; diff --git a/src/Tests/IoCExamples/SingletonNestedProvider.cs#Nested.Namespace.OuterContainer.InnerProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonNestedProvider.cs#Nested.Namespace.OuterContainer.InnerProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9bdceb72 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonNestedProvider.cs#Nested.Namespace.OuterContainer.InnerProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,219 @@ +//HintName: Nested.Namespace.OuterContainer.InnerProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +namespace Nested.Namespace +{ + partial class OuterContainer + { + partial class InnerProvider : + global::System.IServiceProvider, + global::System.IDisposable, + InnerProvider.IResolver, + InnerProvider.IResolver>, + InnerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::Nested.Namespace.INestedService? _S0; + private global::Nested.Namespace.INestedService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Nested.Namespace.NestedService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Nested.Namespace.INestedService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Nested.Namespace.INestedService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is InnerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is InnerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::Nested.Namespace.INestedService InnerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable InnerProvider.IResolver>.Get() + => new global::Nested.Namespace.INestedService[] { S0() }; + global::System.IServiceProvider InnerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + InnerProvider.IResolver, + InnerProvider.IResolver>, + InnerProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly InnerProvider _root; + public Scope(InnerProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::Nested.Namespace.INestedService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Nested.Namespace.INestedService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Nested.Namespace.INestedService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is InnerProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is InnerProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::Nested.Namespace.INestedService InnerProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable InnerProvider.IResolver>.Get() + => new global::Nested.Namespace.INestedService[] { S0() }; + global::System.IServiceProvider InnerProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs b/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs new file mode 100644 index 00000000..ffc4728b --- /dev/null +++ b/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs @@ -0,0 +1,12 @@ +using System; +using AutoCtor; + +[ServiceProvider] +[Singleton] +public sealed partial class ExistingMembersProvider : IServiceProvider +{ + public string Name => "provider"; +} + +public interface IExistingService; +public class ExistingService : IExistingService; diff --git a/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs#ExistingMembersProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs#ExistingMembersProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..fde0166b --- /dev/null +++ b/src/Tests/IoCExamples/SingletonProviderWithExistingMembers.cs#ExistingMembersProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: ExistingMembersProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ExistingMembersProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ExistingMembersProvider.IResolver, + ExistingMembersProvider.IResolver>, + ExistingMembersProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IExistingService? _S0; + private global::IExistingService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ExistingService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IExistingService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IExistingService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ExistingMembersProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ExistingMembersProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IExistingService ExistingMembersProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable ExistingMembersProvider.IResolver>.Get() + => new global::IExistingService[] { S0() }; + global::System.IServiceProvider ExistingMembersProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ExistingMembersProvider.IResolver, + ExistingMembersProvider.IResolver>, + ExistingMembersProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ExistingMembersProvider _root; + public Scope(ExistingMembersProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IExistingService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IExistingService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IExistingService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ExistingMembersProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ExistingMembersProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IExistingService ExistingMembersProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable ExistingMembersProvider.IResolver>.Get() + => new global::IExistingService[] { S0() }; + global::System.IServiceProvider ExistingMembersProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonSelfRegistration.cs b/src/Tests/IoCExamples/SingletonSelfRegistration.cs new file mode 100644 index 00000000..cb9cc168 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonSelfRegistration.cs @@ -0,0 +1,9 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +[Singleton(typeof(SelfTypeofService))] +public sealed partial class SelfRegistrationProvider; + +public class SelfService; +public class SelfTypeofService; diff --git a/src/Tests/IoCExamples/SingletonSelfRegistration.cs#SelfRegistrationProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonSelfRegistration.cs#SelfRegistrationProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..87cd3fc5 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonSelfRegistration.cs#SelfRegistrationProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: SelfRegistrationProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class SelfRegistrationProvider : + global::System.IServiceProvider, + global::System.IDisposable, + SelfRegistrationProvider.IResolver, + SelfRegistrationProvider.IResolver, + SelfRegistrationProvider.IResolver>, + SelfRegistrationProvider.IResolver>, + SelfRegistrationProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::SelfService? _S0; + private global::SelfService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SelfService()); + } + private volatile global::SelfTypeofService? _S1; + private global::SelfTypeofService S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SelfTypeofService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::SelfService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::SelfTypeofService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SelfService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SelfTypeofService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SelfRegistrationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SelfRegistrationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::SelfService SelfRegistrationProvider.IResolver.Get() + => S0(); + global::SelfTypeofService SelfRegistrationProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable SelfRegistrationProvider.IResolver>.Get() + => new global::SelfService[] { S0() }; + global::System.Collections.Generic.IEnumerable SelfRegistrationProvider.IResolver>.Get() + => new global::SelfTypeofService[] { S1() }; + global::System.IServiceProvider SelfRegistrationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + SelfRegistrationProvider.IResolver, + SelfRegistrationProvider.IResolver, + SelfRegistrationProvider.IResolver>, + SelfRegistrationProvider.IResolver>, + SelfRegistrationProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly SelfRegistrationProvider _root; + public Scope(SelfRegistrationProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::SelfService S0() => _root.S0(); + private global::SelfTypeofService S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::SelfService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::SelfTypeofService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SelfService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::SelfTypeofService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is SelfRegistrationProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is SelfRegistrationProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::SelfService SelfRegistrationProvider.IResolver.Get() + => S0(); + global::SelfTypeofService SelfRegistrationProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable SelfRegistrationProvider.IResolver>.Get() + => new global::SelfService[] { S0() }; + global::System.Collections.Generic.IEnumerable SelfRegistrationProvider.IResolver>.Get() + => new global::SelfTypeofService[] { S1() }; + global::System.IServiceProvider SelfRegistrationProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonTypeofAttribute.cs b/src/Tests/IoCExamples/SingletonTypeofAttribute.cs new file mode 100644 index 00000000..3d96d1e6 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonTypeofAttribute.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton(typeof(ITypeofService), typeof(TypeofService))] +public sealed partial class TypeofAttributeProvider; + +public interface ITypeofService; +public class TypeofService : ITypeofService; diff --git a/src/Tests/IoCExamples/SingletonTypeofAttribute.cs#TypeofAttributeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonTypeofAttribute.cs#TypeofAttributeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..79b6dd21 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonTypeofAttribute.cs#TypeofAttributeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: TypeofAttributeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TypeofAttributeProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TypeofAttributeProvider.IResolver, + TypeofAttributeProvider.IResolver>, + TypeofAttributeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ITypeofService? _S0; + private global::ITypeofService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::TypeofService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITypeofService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITypeofService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ITypeofService TypeofAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable TypeofAttributeProvider.IResolver>.Get() + => new global::ITypeofService[] { S0() }; + global::System.IServiceProvider TypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TypeofAttributeProvider.IResolver, + TypeofAttributeProvider.IResolver>, + TypeofAttributeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TypeofAttributeProvider _root; + public Scope(TypeofAttributeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ITypeofService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITypeofService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITypeofService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ITypeofService TypeofAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable TypeofAttributeProvider.IResolver>.Get() + => new global::ITypeofService[] { S0() }; + global::System.IServiceProvider TypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonValueTypeService.cs b/src/Tests/IoCExamples/SingletonValueTypeService.cs new file mode 100644 index 00000000..9d30fa2e --- /dev/null +++ b/src/Tests/IoCExamples/SingletonValueTypeService.cs @@ -0,0 +1,22 @@ +using AutoCtor; + +// A value typed service has no null to mean "not built yet" and cannot be volatile, so it is +// held in a plain field with a volatile bool beside it. The flag is written after the value, +// which is what publishes it: a reader that sees the flag set also sees the value stored. +[ServiceProvider] +[Singleton(typeof(Stamp), typeof(Stamp))] +[Singleton(typeof(IMarker), typeof(MarkerStruct))] +[Singleton] +public sealed partial class ValueTypeProvider; + +public struct Stamp; + +public interface IMarker; +public struct MarkerStruct : IMarker; + +public interface IPrinter; + +public class Printer : IPrinter +{ + public Printer(Stamp stamp, IMarker marker) { } +} diff --git a/src/Tests/IoCExamples/SingletonValueTypeService.cs#ValueTypeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonValueTypeService.cs#ValueTypeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..098f68e0 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonValueTypeService.cs#ValueTypeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: ValueTypeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ValueTypeProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile object? _S0; + private global::Stamp S0() + { + var service = _S0; + if (service is not null) + return (global::Stamp)service; + lock (_lock) + return (global::Stamp)(_S0 ??= new global::Stamp()); + } + private volatile global::IMarker? _S1; + private global::IMarker S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::MarkerStruct()); + } + private volatile global::IPrinter? _S2; + private global::IPrinter S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Printer(S0(), S1())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Stamp)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IMarker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IPrinter)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Stamp[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IMarker[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPrinter[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ValueTypeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ValueTypeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::Stamp ValueTypeProvider.IResolver.Get() + => S0(); + global::IMarker ValueTypeProvider.IResolver.Get() + => S1(); + global::IPrinter ValueTypeProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::Stamp[] { S0() }; + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::IMarker[] { S1() }; + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::IPrinter[] { S2() }; + global::System.IServiceProvider ValueTypeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver>, + ValueTypeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ValueTypeProvider _root; + public Scope(ValueTypeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::Stamp S0() => _root.S0(); + private global::IMarker S1() => _root.S1(); + private global::IPrinter S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::Stamp)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IMarker)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IPrinter)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::Stamp[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IMarker[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPrinter[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ValueTypeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ValueTypeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::Stamp ValueTypeProvider.IResolver.Get() + => S0(); + global::IMarker ValueTypeProvider.IResolver.Get() + => S1(); + global::IPrinter ValueTypeProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::Stamp[] { S0() }; + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::IMarker[] { S1() }; + global::System.Collections.Generic.IEnumerable ValueTypeProvider.IResolver>.Get() + => new global::IPrinter[] { S2() }; + global::System.IServiceProvider ValueTypeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs new file mode 100644 index 00000000..076d5865 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs @@ -0,0 +1,26 @@ +using AutoCtor; + +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class AutoConstructChainProvider; + +public interface IChainDepOne; +public interface IChainDepTwo; +public class ChainDepOne : IChainDepOne; +public class ChainDepTwo : IChainDepTwo; + +public interface IDerivedService; + +[AutoConstruct] +public partial class BaseService +{ + private readonly IChainDepOne _one; +} + +[AutoConstruct] +public partial class DerivedService : BaseService, IDerivedService +{ + private readonly IChainDepTwo _two; +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#AutoConstructChainProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#AutoConstructChainProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..fbc222e1 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#AutoConstructChainProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: AutoConstructChainProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class AutoConstructChainProvider : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IChainDepOne? _S0; + private global::IChainDepOne S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ChainDepOne()); + } + private volatile global::IChainDepTwo? _S1; + private global::IChainDepTwo S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ChainDepTwo()); + } + private volatile global::IDerivedService? _S2; + private global::IDerivedService S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::DerivedService(S0(), S1())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainDepOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainDepTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IDerivedService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainDepOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainDepTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDerivedService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IChainDepOne AutoConstructChainProvider.IResolver.Get() + => S0(); + global::IChainDepTwo AutoConstructChainProvider.IResolver.Get() + => S1(); + global::IDerivedService AutoConstructChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IChainDepOne[] { S0() }; + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IChainDepTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IDerivedService[] { S2() }; + global::System.IServiceProvider AutoConstructChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver>, + AutoConstructChainProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly AutoConstructChainProvider _root; + public Scope(AutoConstructChainProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IChainDepOne S0() => _root.S0(); + private global::IChainDepTwo S1() => _root.S1(); + private global::IDerivedService S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainDepOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainDepTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IDerivedService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainDepOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainDepTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDerivedService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IChainDepOne AutoConstructChainProvider.IResolver.Get() + => S0(); + global::IChainDepTwo AutoConstructChainProvider.IResolver.Get() + => S1(); + global::IDerivedService AutoConstructChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IChainDepOne[] { S0() }; + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IChainDepTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable AutoConstructChainProvider.IResolver>.Get() + => new global::IDerivedService[] { S2() }; + global::System.IServiceProvider AutoConstructChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#BaseService.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#BaseService.g.verified.cs new file mode 100644 index 00000000..d90635c1 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#BaseService.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: BaseService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class BaseService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public BaseService(global::IChainDepOne one) + { + this._one = one; + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#DerivedService.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#DerivedService.g.verified.cs new file mode 100644 index 00000000..b64f8aa9 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructChain.cs#DerivedService.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: DerivedService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class DerivedService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public DerivedService(global::IChainDepOne one, global::IChainDepTwo two) : base(one) + { + this._two = two; + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs new file mode 100644 index 00000000..f2ba63b8 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs @@ -0,0 +1,19 @@ +using AutoCtor; + +// The implementation's constructor does not exist yet when the IoC stage runs, so the +// emitter has to use the constructor AutoCtor is about to generate. +[ServiceProvider] +[Singleton] +[Singleton] +public sealed partial class AutoConstructImplProvider; + +public interface IAcDependency; +public class AcDependency : IAcDependency; + +public interface IAcService; + +[AutoConstruct] +public partial class AcService : IAcService +{ + private readonly IAcDependency _dependency; +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AcService.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AcService.g.verified.cs new file mode 100644 index 00000000..925664a3 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AcService.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: AcService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class AcService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public AcService(global::IAcDependency dependency) + { + this._dependency = dependency; + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AutoConstructImplProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AutoConstructImplProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..489cf935 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructImpl.cs#AutoConstructImplProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: AutoConstructImplProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class AutoConstructImplProvider : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructImplProvider.IResolver, + AutoConstructImplProvider.IResolver, + AutoConstructImplProvider.IResolver>, + AutoConstructImplProvider.IResolver>, + AutoConstructImplProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IAcDependency? _S0; + private global::IAcDependency S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::AcDependency()); + } + private volatile global::IAcService? _S1; + private global::IAcService S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::AcService(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IAcService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAcDependency[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAcService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructImplProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructImplProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IAcDependency AutoConstructImplProvider.IResolver.Get() + => S0(); + global::IAcService AutoConstructImplProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable AutoConstructImplProvider.IResolver>.Get() + => new global::IAcDependency[] { S0() }; + global::System.Collections.Generic.IEnumerable AutoConstructImplProvider.IResolver>.Get() + => new global::IAcService[] { S1() }; + global::System.IServiceProvider AutoConstructImplProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructImplProvider.IResolver, + AutoConstructImplProvider.IResolver, + AutoConstructImplProvider.IResolver>, + AutoConstructImplProvider.IResolver>, + AutoConstructImplProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly AutoConstructImplProvider _root; + public Scope(AutoConstructImplProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IAcDependency S0() => _root.S0(); + private global::IAcService S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IAcService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAcDependency[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAcService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructImplProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructImplProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IAcDependency AutoConstructImplProvider.IResolver.Get() + => S0(); + global::IAcService AutoConstructImplProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable AutoConstructImplProvider.IResolver>.Get() + => new global::IAcDependency[] { S0() }; + global::System.Collections.Generic.IEnumerable AutoConstructImplProvider.IResolver>.Get() + => new global::IAcService[] { S1() }; + global::System.IServiceProvider AutoConstructImplProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs new file mode 100644 index 00000000..3ff48dee --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs @@ -0,0 +1,12 @@ +using AutoCtor; + +// AutoCtor emits nothing for an [AutoConstruct] type with no eligible members, so the +// implicit parameterless constructor survives and the emitter must fall back to it. +[ServiceProvider] +[Singleton] +public sealed partial class AutoConstructNoMembersProvider; + +public interface IEmptyService; + +[AutoConstruct] +public partial class EmptyService : IEmptyService; diff --git a/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs#AutoConstructNoMembersProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs#AutoConstructNoMembersProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..ef8cdb4e --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithAutoConstructNoMembers.cs#AutoConstructNoMembersProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,213 @@ +//HintName: AutoConstructNoMembersProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class AutoConstructNoMembersProvider : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructNoMembersProvider.IResolver, + AutoConstructNoMembersProvider.IResolver>, + AutoConstructNoMembersProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IEmptyService? _S0; + private global::IEmptyService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::EmptyService()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IEmptyService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IEmptyService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructNoMembersProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructNoMembersProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IEmptyService AutoConstructNoMembersProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable AutoConstructNoMembersProvider.IResolver>.Get() + => new global::IEmptyService[] { S0() }; + global::System.IServiceProvider AutoConstructNoMembersProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + AutoConstructNoMembersProvider.IResolver, + AutoConstructNoMembersProvider.IResolver>, + AutoConstructNoMembersProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly AutoConstructNoMembersProvider _root; + public Scope(AutoConstructNoMembersProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IEmptyService S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IEmptyService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IEmptyService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AutoConstructNoMembersProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AutoConstructNoMembersProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IEmptyService AutoConstructNoMembersProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable AutoConstructNoMembersProvider.IResolver>.Get() + => new global::IEmptyService[] { S0() }; + global::System.IServiceProvider AutoConstructNoMembersProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithDependencies.cs b/src/Tests/IoCExamples/SingletonWithDependencies.cs new file mode 100644 index 00000000..39691293 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithDependencies.cs @@ -0,0 +1,24 @@ +using AutoCtor; + +// Registered in reverse dependency order on purpose, to prove the emitter sorts. +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class DependencyChainProvider; + +public interface IChainA; +public interface IChainB; +public interface IChainC; + +public class ChainC : IChainC; + +public class ChainB : IChainB +{ + public ChainB(IChainC c) { } +} + +public class ChainA : IChainA +{ + public ChainA(IChainB b, IChainC c) { } +} diff --git a/src/Tests/IoCExamples/SingletonWithDependencies.cs#DependencyChainProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonWithDependencies.cs#DependencyChainProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..b798efb9 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithDependencies.cs#DependencyChainProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: DependencyChainProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class DependencyChainProvider : + global::System.IServiceProvider, + global::System.IDisposable, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IChainA? _S0; + private global::IChainA S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::ChainA(S1(), S2())); + } + private volatile global::IChainB? _S1; + private global::IChainB S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::ChainB(S2())); + } + private volatile global::IChainC? _S2; + private global::IChainC S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::ChainC()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainA)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainB)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChainC)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainA[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainB[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainC[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DependencyChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DependencyChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IChainA DependencyChainProvider.IResolver.Get() + => S0(); + global::IChainB DependencyChainProvider.IResolver.Get() + => S1(); + global::IChainC DependencyChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainA[] { S0() }; + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainB[] { S1() }; + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainC[] { S2() }; + global::System.IServiceProvider DependencyChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver>, + DependencyChainProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly DependencyChainProvider _root; + public Scope(DependencyChainProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IChainA S0() => _root.S0(); + private global::IChainB S1() => _root.S1(); + private global::IChainC S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainA)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainB)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChainC)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainA[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainB[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainC[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is DependencyChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is DependencyChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IChainA DependencyChainProvider.IResolver.Get() + => S0(); + global::IChainB DependencyChainProvider.IResolver.Get() + => S1(); + global::IChainC DependencyChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainA[] { S0() }; + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainB[] { S1() }; + global::System.Collections.Generic.IEnumerable DependencyChainProvider.IResolver>.Get() + => new global::IChainC[] { S2() }; + global::System.IServiceProvider DependencyChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithPostConstruct.cs b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs new file mode 100644 index 00000000..65468043 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs @@ -0,0 +1,25 @@ +using AutoCtor; + +// [AutoPostConstruct] adds its own parameters to the generated constructor, so the +// predicted parameter list has to come from the ctor emitter rather than the members. +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class PostConstructProvider; + +public interface IPostDepOne; +public interface IPostDepTwo; +public class PostDepOne : IPostDepOne; +public class PostDepTwo : IPostDepTwo; + +public interface IPostService; + +[AutoConstruct] +public partial class PostService : IPostService +{ + private readonly IPostDepOne _one; + + [AutoPostConstruct] + private void Initialise(IPostDepTwo two) { } +} diff --git a/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostConstructProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostConstructProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..0f072a19 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostConstructProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,273 @@ +//HintName: PostConstructProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PostConstructProvider : + global::System.IServiceProvider, + global::System.IDisposable, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPostDepOne? _S0; + private global::IPostDepOne S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::PostDepOne()); + } + private volatile global::IPostDepTwo? _S1; + private global::IPostDepTwo S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::PostDepTwo()); + } + private volatile global::IPostService? _S2; + private global::IPostService S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::PostService(S0(), S1())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPostDepOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPostDepTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IPostService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostDepOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostDepTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PostConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PostConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPostDepOne PostConstructProvider.IResolver.Get() + => S0(); + global::IPostDepTwo PostConstructProvider.IResolver.Get() + => S1(); + global::IPostService PostConstructProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostDepOne[] { S0() }; + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostDepTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostService[] { S2() }; + global::System.IServiceProvider PostConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver>, + PostConstructProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PostConstructProvider _root; + public Scope(PostConstructProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPostDepOne S0() => _root.S0(); + private global::IPostDepTwo S1() => _root.S1(); + private global::IPostService S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPostDepOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IPostDepTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IPostService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostDepOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostDepTwo[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPostService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PostConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PostConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPostDepOne PostConstructProvider.IResolver.Get() + => S0(); + global::IPostDepTwo PostConstructProvider.IResolver.Get() + => S1(); + global::IPostService PostConstructProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostDepOne[] { S0() }; + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostDepTwo[] { S1() }; + global::System.Collections.Generic.IEnumerable PostConstructProvider.IResolver>.Get() + => new global::IPostService[] { S2() }; + global::System.IServiceProvider PostConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostService.g.verified.cs b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostService.g.verified.cs new file mode 100644 index 00000000..deb24035 --- /dev/null +++ b/src/Tests/IoCExamples/SingletonWithPostConstruct.cs#PostService.g.verified.cs @@ -0,0 +1,16 @@ +//HintName: PostService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class PostService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public PostService(global::IPostDepOne one, global::IPostDepTwo two) + { + this._one = one; + Initialise(two); + } +} diff --git a/src/Tests/IoCExamples/TransientChain.cs b/src/Tests/IoCExamples/TransientChain.cs new file mode 100644 index 00000000..0fb51f9b --- /dev/null +++ b/src/Tests/IoCExamples/TransientChain.cs @@ -0,0 +1,25 @@ +using AutoCtor; + +// A transient depending on another transient nests the factory calls, so every level of +// the chain is rebuilt on each resolve. +[ServiceProvider] +[Transient] +[Transient] +[Transient] +public sealed partial class TransientChainProvider; + +public interface IChainOuter; +public interface IChainMiddle; +public interface IChainInner; + +public class ChainInner : IChainInner; + +public class ChainMiddle : IChainMiddle +{ + public ChainMiddle(IChainInner inner) { } +} + +public class ChainOuter : IChainOuter +{ + public ChainOuter(IChainMiddle middle, IChainInner inner) { } +} diff --git a/src/Tests/IoCExamples/TransientChain.cs#TransientChainProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientChain.cs#TransientChainProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..5f082cff --- /dev/null +++ b/src/Tests/IoCExamples/TransientChain.cs#TransientChainProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,249 @@ +//HintName: TransientChainProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TransientChainProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IChainOuter S0() => new global::ChainOuter(S1(), S2()); + private global::IChainMiddle S1() => new global::ChainMiddle(S2()); + private global::IChainInner S2() => new global::ChainInner(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainOuter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainMiddle)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChainInner)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainOuter[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainMiddle[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainInner[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IChainOuter TransientChainProvider.IResolver.Get() + => S0(); + global::IChainMiddle TransientChainProvider.IResolver.Get() + => S1(); + global::IChainInner TransientChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainOuter[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainMiddle[] { S1() }; + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainInner[] { S2() }; + global::System.IServiceProvider TransientChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver>, + TransientChainProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TransientChainProvider _root; + public Scope(TransientChainProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IChainOuter S0() => new global::ChainOuter(S1(), S2()); + private global::IChainMiddle S1() => new global::ChainMiddle(S2()); + private global::IChainInner S2() => new global::ChainInner(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IChainOuter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IChainMiddle)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IChainInner)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainOuter[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainMiddle[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IChainInner[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientChainProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientChainProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IChainOuter TransientChainProvider.IResolver.Get() + => S0(); + global::IChainMiddle TransientChainProvider.IResolver.Get() + => S1(); + global::IChainInner TransientChainProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainOuter[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainMiddle[] { S1() }; + global::System.Collections.Generic.IEnumerable TransientChainProvider.IResolver>.Get() + => new global::IChainInner[] { S2() }; + global::System.IServiceProvider TransientChainProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/TransientDisposable.cs b/src/Tests/IoCExamples/TransientDisposable.cs new file mode 100644 index 00000000..eec603c0 --- /dev/null +++ b/src/Tests/IoCExamples/TransientDisposable.cs @@ -0,0 +1,27 @@ +using AutoCtor; +using System; + +// A transient is disposed by whoever handed it out, so the provider tracks the ones it builds +// and drains that stack when it is disposed. Only the disposable transient is tracked -- the +// other stays a bare factory call and costs nothing. +[ServiceProvider] +[Singleton] +[Transient] +[Transient] +public sealed partial class ConnectionProvider; + +public interface IClock; +public class Clock : IClock; + +public interface IConnection; +public class Connection : IConnection, IDisposable +{ + public Connection(IClock clock) { } + public void Dispose() { } +} + +public interface IFormatter; +public class Formatter : IFormatter +{ + public Formatter(IClock clock) { } +} diff --git a/src/Tests/IoCExamples/TransientDisposable.cs#ConnectionProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientDisposable.cs#ConnectionProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..9fa36b1e --- /dev/null +++ b/src/Tests/IoCExamples/TransientDisposable.cs#ConnectionProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,257 @@ +//HintName: ConnectionProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ConnectionProvider : + global::System.IServiceProvider, + global::System.IDisposable, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + private global::IConnection S1() => Track(new global::Connection(S0())); + private global::IFormatter S2() => new global::Formatter(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IConnection)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IFormatter)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IConnection[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IFormatter[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ConnectionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ConnectionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock ConnectionProvider.IResolver.Get() + => S0(); + global::IConnection ConnectionProvider.IResolver.Get() + => S1(); + global::IFormatter ConnectionProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IConnection[] { S1() }; + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IFormatter[] { S2() }; + global::System.IServiceProvider ConnectionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver>, + ConnectionProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ConnectionProvider _root; + public Scope(ConnectionProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IConnection S1() => Track(new global::Connection(S0())); + private global::IFormatter S2() => new global::Formatter(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IConnection)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IFormatter)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IConnection[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IFormatter[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ConnectionProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ConnectionProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock ConnectionProvider.IResolver.Get() + => S0(); + global::IConnection ConnectionProvider.IResolver.Get() + => S1(); + global::IFormatter ConnectionProvider.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IConnection[] { S1() }; + global::System.Collections.Generic.IEnumerable ConnectionProvider.IResolver>.Get() + => new global::IFormatter[] { S2() }; + global::System.IServiceProvider ConnectionProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/TransientOnly.cs b/src/Tests/IoCExamples/TransientOnly.cs new file mode 100644 index 00000000..eb3ec782 --- /dev/null +++ b/src/Tests/IoCExamples/TransientOnly.cs @@ -0,0 +1,13 @@ +using AutoCtor; + +// Nothing is eagerly constructed, so no constructor should be emitted at all -- the +// implicit parameterless one is left alone. +[ServiceProvider] +[Transient] +[Transient] +public sealed partial class TransientOnlyProvider; + +public interface ITransientOnlyOne; +public interface ITransientOnlyTwo; +public class TransientOnlyOne : ITransientOnlyOne; +public class TransientOnlyTwo : ITransientOnlyTwo; diff --git a/src/Tests/IoCExamples/TransientOnly.cs#TransientOnlyProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientOnly.cs#TransientOnlyProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..3145486a --- /dev/null +++ b/src/Tests/IoCExamples/TransientOnly.cs#TransientOnlyProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,227 @@ +//HintName: TransientOnlyProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TransientOnlyProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TransientOnlyProvider.IResolver, + TransientOnlyProvider.IResolver, + TransientOnlyProvider.IResolver>, + TransientOnlyProvider.IResolver>, + TransientOnlyProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::ITransientOnlyOne S0() => new global::TransientOnlyOne(); + private global::ITransientOnlyTwo S1() => new global::TransientOnlyTwo(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITransientOnlyOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ITransientOnlyTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientOnlyOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientOnlyTwo[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientOnlyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientOnlyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ITransientOnlyOne TransientOnlyProvider.IResolver.Get() + => S0(); + global::ITransientOnlyTwo TransientOnlyProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientOnlyProvider.IResolver>.Get() + => new global::ITransientOnlyOne[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientOnlyProvider.IResolver>.Get() + => new global::ITransientOnlyTwo[] { S1() }; + global::System.IServiceProvider TransientOnlyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TransientOnlyProvider.IResolver, + TransientOnlyProvider.IResolver, + TransientOnlyProvider.IResolver>, + TransientOnlyProvider.IResolver>, + TransientOnlyProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TransientOnlyProvider _root; + public Scope(TransientOnlyProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ITransientOnlyOne S0() => new global::TransientOnlyOne(); + private global::ITransientOnlyTwo S1() => new global::TransientOnlyTwo(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITransientOnlyOne)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ITransientOnlyTwo)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientOnlyOne[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientOnlyTwo[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientOnlyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientOnlyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ITransientOnlyOne TransientOnlyProvider.IResolver.Get() + => S0(); + global::ITransientOnlyTwo TransientOnlyProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientOnlyProvider.IResolver>.Get() + => new global::ITransientOnlyOne[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientOnlyProvider.IResolver>.Get() + => new global::ITransientOnlyTwo[] { S1() }; + global::System.IServiceProvider TransientOnlyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/TransientTypeofAttribute.cs b/src/Tests/IoCExamples/TransientTypeofAttribute.cs new file mode 100644 index 00000000..655e7d87 --- /dev/null +++ b/src/Tests/IoCExamples/TransientTypeofAttribute.cs @@ -0,0 +1,8 @@ +using AutoCtor; + +[ServiceProvider] +[Transient(typeof(ITransientTypeofService), typeof(TransientTypeofService))] +public sealed partial class TransientTypeofAttributeProvider; + +public interface ITransientTypeofService; +public class TransientTypeofService : ITransientTypeofService; diff --git a/src/Tests/IoCExamples/TransientTypeofAttribute.cs#TransientTypeofAttributeProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientTypeofAttribute.cs#TransientTypeofAttributeProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..97469a0a --- /dev/null +++ b/src/Tests/IoCExamples/TransientTypeofAttribute.cs#TransientTypeofAttributeProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,205 @@ +//HintName: TransientTypeofAttributeProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TransientTypeofAttributeProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TransientTypeofAttributeProvider.IResolver, + TransientTypeofAttributeProvider.IResolver>, + TransientTypeofAttributeProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::ITransientTypeofService S0() => new global::TransientTypeofService(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITransientTypeofService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientTypeofService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientTypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientTypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ITransientTypeofService TransientTypeofAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable TransientTypeofAttributeProvider.IResolver>.Get() + => new global::ITransientTypeofService[] { S0() }; + global::System.IServiceProvider TransientTypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TransientTypeofAttributeProvider.IResolver, + TransientTypeofAttributeProvider.IResolver>, + TransientTypeofAttributeProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TransientTypeofAttributeProvider _root; + public Scope(TransientTypeofAttributeProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ITransientTypeofService S0() => new global::TransientTypeofService(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITransientTypeofService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITransientTypeofService[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientTypeofAttributeProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientTypeofAttributeProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ITransientTypeofService TransientTypeofAttributeProvider.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable TransientTypeofAttributeProvider.IResolver>.Get() + => new global::ITransientTypeofService[] { S0() }; + global::System.IServiceProvider TransientTypeofAttributeProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs new file mode 100644 index 00000000..a38f5511 --- /dev/null +++ b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs @@ -0,0 +1,19 @@ +using AutoCtor; + +// As with singletons, the implementation's constructor does not exist yet when the IoC +// stage runs, so the factory has to use the constructor AutoCtor is about to generate. +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class TransientAutoConstructProvider; + +public interface ITrAcDependency; +public class TrAcDependency : ITrAcDependency; + +public interface ITrAcService; + +[AutoConstruct] +public partial class TrAcService : ITrAcService +{ + private readonly ITrAcDependency _dependency; +} diff --git a/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TrAcService.g.verified.cs b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TrAcService.g.verified.cs new file mode 100644 index 00000000..6a3ad824 --- /dev/null +++ b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TrAcService.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: TrAcService.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class TrAcService +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public TrAcService(global::ITrAcDependency dependency) + { + this._dependency = dependency; + } +} diff --git a/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TransientAutoConstructProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TransientAutoConstructProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f7af3a11 --- /dev/null +++ b/src/Tests/IoCExamples/TransientWithAutoConstructImpl.cs#TransientAutoConstructProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,235 @@ +//HintName: TransientAutoConstructProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TransientAutoConstructProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TransientAutoConstructProvider.IResolver, + TransientAutoConstructProvider.IResolver, + TransientAutoConstructProvider.IResolver>, + TransientAutoConstructProvider.IResolver>, + TransientAutoConstructProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ITrAcDependency? _S0; + private global::ITrAcDependency S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::TrAcDependency()); + } + private global::ITrAcService S1() => new global::TrAcService(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITrAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ITrAcService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITrAcDependency[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITrAcService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientAutoConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientAutoConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ITrAcDependency TransientAutoConstructProvider.IResolver.Get() + => S0(); + global::ITrAcService TransientAutoConstructProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientAutoConstructProvider.IResolver>.Get() + => new global::ITrAcDependency[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientAutoConstructProvider.IResolver>.Get() + => new global::ITrAcService[] { S1() }; + global::System.IServiceProvider TransientAutoConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TransientAutoConstructProvider.IResolver, + TransientAutoConstructProvider.IResolver, + TransientAutoConstructProvider.IResolver>, + TransientAutoConstructProvider.IResolver>, + TransientAutoConstructProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TransientAutoConstructProvider _root; + public Scope(TransientAutoConstructProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ITrAcDependency S0() => _root.S0(); + private global::ITrAcService S1() => new global::TrAcService(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ITrAcDependency)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ITrAcService)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITrAcDependency[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ITrAcService[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientAutoConstructProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientAutoConstructProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ITrAcDependency TransientAutoConstructProvider.IResolver.Get() + => S0(); + global::ITrAcService TransientAutoConstructProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientAutoConstructProvider.IResolver>.Get() + => new global::ITrAcDependency[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientAutoConstructProvider.IResolver>.Get() + => new global::ITrAcService[] { S1() }; + global::System.IServiceProvider TransientAutoConstructProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/IoCExamples/TransientWithSingletonDependency.cs b/src/Tests/IoCExamples/TransientWithSingletonDependency.cs new file mode 100644 index 00000000..0d54a570 --- /dev/null +++ b/src/Tests/IoCExamples/TransientWithSingletonDependency.cs @@ -0,0 +1,18 @@ +using AutoCtor; + +// A transient may freely depend on a singleton -- the getter reads the backing field +// every time it builds a new instance. +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class TransientDependencyProvider; + +public interface ISharedConfig; +public class SharedConfig : ISharedConfig; + +public interface IRequestHandler; + +public class RequestHandler : IRequestHandler +{ + public RequestHandler(ISharedConfig config) { } +} diff --git a/src/Tests/IoCExamples/TransientWithSingletonDependency.cs#TransientDependencyProvider.ServiceProvider.g.verified.cs b/src/Tests/IoCExamples/TransientWithSingletonDependency.cs#TransientDependencyProvider.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..771a92b0 --- /dev/null +++ b/src/Tests/IoCExamples/TransientWithSingletonDependency.cs#TransientDependencyProvider.ServiceProvider.g.verified.cs @@ -0,0 +1,235 @@ +//HintName: TransientDependencyProvider.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TransientDependencyProvider : + global::System.IServiceProvider, + global::System.IDisposable, + TransientDependencyProvider.IResolver, + TransientDependencyProvider.IResolver, + TransientDependencyProvider.IResolver>, + TransientDependencyProvider.IResolver>, + TransientDependencyProvider.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ISharedConfig? _S0; + private global::ISharedConfig S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::SharedConfig()); + } + private global::IRequestHandler S1() => new global::RequestHandler(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISharedConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IRequestHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISharedConfig[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequestHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientDependencyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientDependencyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ISharedConfig TransientDependencyProvider.IResolver.Get() + => S0(); + global::IRequestHandler TransientDependencyProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientDependencyProvider.IResolver>.Get() + => new global::ISharedConfig[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientDependencyProvider.IResolver>.Get() + => new global::IRequestHandler[] { S1() }; + global::System.IServiceProvider TransientDependencyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + TransientDependencyProvider.IResolver, + TransientDependencyProvider.IResolver, + TransientDependencyProvider.IResolver>, + TransientDependencyProvider.IResolver>, + TransientDependencyProvider.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TransientDependencyProvider _root; + public Scope(TransientDependencyProvider root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ISharedConfig S0() => _root.S0(); + private global::IRequestHandler S1() => new global::RequestHandler(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISharedConfig)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IRequestHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISharedConfig[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IRequestHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TransientDependencyProvider.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TransientDependencyProvider.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ISharedConfig TransientDependencyProvider.IResolver.Get() + => S0(); + global::IRequestHandler TransientDependencyProvider.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TransientDependencyProvider.IResolver>.Get() + => new global::ISharedConfig[] { S0() }; + global::System.Collections.Generic.IEnumerable TransientDependencyProvider.IResolver>.Get() + => new global::IRequestHandler[] { S1() }; + global::System.IServiceProvider TransientDependencyProvider.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/Issue73.cs#B.TheClass.g.verified.cs b/src/Tests/Issue73.cs#B.TheClass.g.verified.cs index 6e1719d8..ec92dc92 100644 --- a/src/Tests/Issue73.cs#B.TheClass.g.verified.cs +++ b/src/Tests/Issue73.cs#B.TheClass.g.verified.cs @@ -9,10 +9,7 @@ namespace B { partial class TheClass { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public TheClass(global::A.Interface i) : base(i) { } diff --git a/src/Tests/LangExamples/Verified_3_11/PropertiesTest.cs#PropertiesTest.g.verified.cs b/src/Tests/LangExamples/Verified_3_11/PropertiesTest.cs#PropertiesTest.g.verified.cs index 70c9d1a7..e68775ff 100644 --- a/src/Tests/LangExamples/Verified_3_11/PropertiesTest.cs#PropertiesTest.g.verified.cs +++ b/src/Tests/LangExamples/Verified_3_11/PropertiesTest.cs#PropertiesTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PropertiesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PropertiesTest( string getProperty, string protectedProperty, diff --git a/src/Tests/LangExamples/Verified_4_0/PropertiesTest.cs#PropertiesTest.g.verified.cs b/src/Tests/LangExamples/Verified_4_0/PropertiesTest.cs#PropertiesTest.g.verified.cs index 70c9d1a7..e68775ff 100644 --- a/src/Tests/LangExamples/Verified_4_0/PropertiesTest.cs#PropertiesTest.g.verified.cs +++ b/src/Tests/LangExamples/Verified_4_0/PropertiesTest.cs#PropertiesTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PropertiesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PropertiesTest( string getProperty, string protectedProperty, diff --git a/src/Tests/LangExamples/Verified_4_4/PropertiesTest.cs#PropertiesTest.g.verified.cs b/src/Tests/LangExamples/Verified_4_4/PropertiesTest.cs#PropertiesTest.g.verified.cs index 220428b5..0bf7e3f8 100644 --- a/src/Tests/LangExamples/Verified_4_4/PropertiesTest.cs#PropertiesTest.g.verified.cs +++ b/src/Tests/LangExamples/Verified_4_4/PropertiesTest.cs#PropertiesTest.g.verified.cs @@ -7,10 +7,7 @@ partial class PropertiesTest { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PropertiesTest( string getProperty, string protectedProperty, diff --git a/src/Tests/ModuleReferencedAssemblyTests.ImportsModuleFromReferencedAssembly#Container.ServiceProvider.g.verified.cs b/src/Tests/ModuleReferencedAssemblyTests.ImportsModuleFromReferencedAssembly#Container.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..fcfe69f9 --- /dev/null +++ b/src/Tests/ModuleReferencedAssemblyTests.ImportsModuleFromReferencedAssembly#Container.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: Container.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class Container : + global::System.IServiceProvider, + global::System.IDisposable, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ILibraryService? _S0; + private global::ILibraryService S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::LibraryService(S1())); + } + private volatile global::ILibraryClock? _S1; + private global::ILibraryClock S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(global::LibraryModule.CreateClock())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILibraryService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILibraryClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILibraryService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILibraryClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ILibraryService Container.IResolver.Get() + => S0(); + global::ILibraryClock Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::ILibraryService[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::ILibraryClock[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly Container _root; + public Scope(Container root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ILibraryService S0() => _root.S0(); + private global::ILibraryClock S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ILibraryService)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ILibraryClock)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILibraryService[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILibraryClock[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ILibraryService Container.IResolver.Get() + => S0(); + global::ILibraryClock Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::ILibraryService[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::ILibraryClock[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ModuleReferencedAssemblyTests.cs b/src/Tests/ModuleReferencedAssemblyTests.cs new file mode 100644 index 00000000..ad87b633 --- /dev/null +++ b/src/Tests/ModuleReferencedAssemblyTests.cs @@ -0,0 +1,130 @@ +using AutoCtor; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using static ExampleTests; +using static ExampleTestsHelper; + +internal sealed class ModuleReferencedAssemblyTests +{ + private const string Library = """ + [AutoCtor.Singleton(typeof(ILibraryService), typeof(LibraryService))] + [AutoCtor.Singleton(typeof(ILibraryClock), Factory = nameof(CreateClock))] + public class LibraryModule + { + public static ILibraryClock CreateClock() => new LibraryClock(); + } + public interface ILibraryService; + public class LibraryService(ILibraryClock clock) : ILibraryService + { + public ILibraryClock Clock => clock; + } + public interface ILibraryClock; + internal class LibraryClock : ILibraryClock; + """; + + private const string App = """ + [AutoCtor.ServiceProvider] + [AutoCtor.Import(typeof(LibraryModule))] + public sealed partial class Container; + """; + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task ImportsModuleFromReferencedAssembly(BareCompilationBuilderFactory builderFactory) + { + var builder = builderFactory.Builder; + // Without the symbol the registration attributes are not compiled into the library. + var library = await EmitReference(builder + .WithPreprocessorSymbols([.. PreprocessorSymbols, "AUTOCTOR_USAGES"]) + .AddCodes(Library).Build("Library")).ConfigureAwait(false); + var app = builder.AddReferences(library).AddCodes(App); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(app.ParseOptions) + .RunGeneratorsAndUpdateCompilation( + app.Build("App"), + out var outputCompilation, + out _, + TestHelper.CancellationToken); + + // Each compilation carries its own copy of the test polyfills, which only warns. + await Assert.That(outputCompilation.GetDiagnostics(TestHelper.CancellationToken) + .Where(d => d.Severity == DiagnosticSeverity.Error)) + .IsEmpty() + .ConfigureAwait(false); + + await Verify(driver) + .IgnoreParameters() + .ConfigureAwait(false); + } + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task ModuleBuiltWithoutUsagesIsReported(BareCompilationBuilderFactory builderFactory) + { + var builder = builderFactory.Builder; + var library = await EmitReference(builder.AddCodes(Library).Build("Library")).ConfigureAwait(false); + var app = builder.AddReferences(library).AddCodes(App); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(app.ParseOptions) + .RunGenerators(app.Build("App"), TestHelper.CancellationToken); + + await Assert.That(driver.GetRunResult().Diagnostics.Select(d => d.Id)) + .Contains("ACTR027") + .ConfigureAwait(false); + } + + // A compilation reference hands over source symbols, which keep [Conditional] attributes + // whatever the symbols say. Only emitted metadata shows what a real library carries. + private static async Task EmitReference(CSharpCompilation library) + { + using var stream = new MemoryStream(); + var result = library.Emit(stream, cancellationToken: TestHelper.CancellationToken); + await Assert.That(result.Diagnostics.Where(d => d.Severity == DiagnosticSeverity.Error)) + .IsEmpty() + .ConfigureAwait(false); + return MetadataReference.CreateFromImage(stream.ToArray()); + } + +#if ROSLYN_4_4 + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task EditingAModuleRegeneratesTheProvider(BareCompilationBuilderFactory builderFactory) + { + const string provider = """ + [AutoCtor.ServiceProvider] + [AutoCtor.Import(typeof(LocalModule))] + public sealed partial class Container; + """; + const string module = """ + [AutoCtor.Singleton(typeof(LocalService))] + public class LocalModule; + public class LocalService; + """; + + var app = builderFactory.Builder.AddCodes(provider, module); + var compilation = app.Build("App"); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(app.ParseOptions) + .RunGenerators(compilation, TestHelper.CancellationToken); + + // Only the module's file changes; the provider's syntax tree is the same one. + var moduleTree = compilation.SyntaxTrees.Single(t => t.ToString().Contains("class LocalModule", StringComparison.Ordinal)); + var editedTree = CSharpSyntaxTree.ParseText( + "[AutoCtor.Singleton(typeof(AddedService))]\n" + module + "\npublic class AddedService;", + app.ParseOptions, + cancellationToken: TestHelper.CancellationToken); + driver = driver.RunGenerators(compilation.ReplaceSyntaxTree(moduleTree, editedTree), TestHelper.CancellationToken); + + await Assert.That(driver.GetRunResult().GeneratedTrees + .Any(t => t.ToString().Contains("global::AddedService", StringComparison.Ordinal))) + .IsTrue() + .ConfigureAwait(false); + } +#endif +} diff --git a/src/Tests/PartialDeclarationTests.cs b/src/Tests/PartialDeclarationTests.cs new file mode 100644 index 00000000..65579f1e --- /dev/null +++ b/src/Tests/PartialDeclarationTests.cs @@ -0,0 +1,51 @@ +using AutoCtor; +using Microsoft.CodeAnalysis; +using static ExampleTests; + +internal sealed class PartialDeclarationTests +{ + // Every generator version visits each declaration, so a type declared in several files with + // attributes on more than one of them must still be generated once. + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task PartialTypesAreGeneratedOnce(BareCompilationBuilderFactory builderFactory) + { + var builder = builderFactory.Builder.AddCodes( + """ + public partial class Service; + [AutoCtor.ServiceProvider] + public sealed partial class Container; + """, + """ + [AutoCtor.AutoConstruct] + public partial class Service { private readonly IClock _clock; } + [AutoCtor.Singleton(typeof(IClock), typeof(Clock))] + public sealed partial class Container; + """, + """ + [System.Serializable] + public partial class Service; + [AutoCtor.Singleton(typeof(Service))] + public sealed partial class Container; + public interface IClock; + public class Clock : IClock; + """); + + new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(builder.ParseOptions) + .RunGeneratorsAndUpdateCompilation( + builder.Build(nameof(PartialDeclarationTests)), + out var outputCompilation, + out var diagnostics, + TestHelper.CancellationToken); + + await Assert.That(diagnostics.Concat(outputCompilation.GetDiagnostics(TestHelper.CancellationToken)) + .Where(d => d.Severity == DiagnosticSeverity.Error)) + .IsEmpty() + .ConfigureAwait(false); + await Assert.That(outputCompilation.SyntaxTrees.Count(t => t.FilePath.EndsWith("Container.ServiceProvider.g.cs", StringComparison.Ordinal))) + .IsEqualTo(1) + .ConfigureAwait(false); + } +} diff --git a/src/Tests/ReadmeExamples/Basic.cs#Basic.g.verified.cs b/src/Tests/ReadmeExamples/Basic.cs#Basic.g.verified.cs index e1f8a827..4f52b0fa 100644 --- a/src/Tests/ReadmeExamples/Basic.cs#Basic.g.verified.cs +++ b/src/Tests/ReadmeExamples/Basic.cs#Basic.g.verified.cs @@ -7,10 +7,7 @@ partial class Basic { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Basic(global::IService service) { this._service = service; diff --git a/src/Tests/ReadmeExamples/Guarded.cs#Guarded.g.verified.cs b/src/Tests/ReadmeExamples/Guarded.cs#Guarded.g.verified.cs index a846957b..ab9d597f 100644 --- a/src/Tests/ReadmeExamples/Guarded.cs#Guarded.g.verified.cs +++ b/src/Tests/ReadmeExamples/Guarded.cs#Guarded.g.verified.cs @@ -7,10 +7,7 @@ partial class Guarded { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Guarded(global::IService service) { this._service = service ?? throw new global::System.ArgumentNullException("service"); diff --git a/src/Tests/ReadmeExamples/Inherited.cs#Inherited.g.verified.cs b/src/Tests/ReadmeExamples/Inherited.cs#Inherited.g.verified.cs index 31de1482..3cf09176 100644 --- a/src/Tests/ReadmeExamples/Inherited.cs#Inherited.g.verified.cs +++ b/src/Tests/ReadmeExamples/Inherited.cs#Inherited.g.verified.cs @@ -7,10 +7,7 @@ partial class Inherited { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Inherited( global::IAnotherService anotherService, global::IService service diff --git a/src/Tests/ReadmeExamples/Keyed.cs#Keyed.g.verified.cs b/src/Tests/ReadmeExamples/Keyed.cs#Keyed.g.verified.cs index 76cdd0a4..a1838c7b 100644 --- a/src/Tests/ReadmeExamples/Keyed.cs#Keyed.g.verified.cs +++ b/src/Tests/ReadmeExamples/Keyed.cs#Keyed.g.verified.cs @@ -7,10 +7,7 @@ partial class Keyed { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Keyed( [global::Microsoft.Extensions.DependencyInjection.FromKeyedServices("key")] global::IService keyedService ) diff --git a/src/Tests/ReadmeExamples/PostConstruct.cs#PostConstruct.g.verified.cs b/src/Tests/ReadmeExamples/PostConstruct.cs#PostConstruct.g.verified.cs index f1bce281..f20f816c 100644 --- a/src/Tests/ReadmeExamples/PostConstruct.cs#PostConstruct.g.verified.cs +++ b/src/Tests/ReadmeExamples/PostConstruct.cs#PostConstruct.g.verified.cs @@ -7,10 +7,7 @@ partial class PostConstruct { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstruct(global::IService service) { this._service = service; diff --git a/src/Tests/ReadmeExamples/PostConstructWithDefaultParameter.cs#PostConstructWithDefaultParameter.g.verified.cs b/src/Tests/ReadmeExamples/PostConstructWithDefaultParameter.cs#PostConstructWithDefaultParameter.g.verified.cs index 253e2b40..3111b024 100644 --- a/src/Tests/ReadmeExamples/PostConstructWithDefaultParameter.cs#PostConstructWithDefaultParameter.g.verified.cs +++ b/src/Tests/ReadmeExamples/PostConstructWithDefaultParameter.cs#PostConstructWithDefaultParameter.g.verified.cs @@ -7,10 +7,7 @@ partial class PostConstructWithDefaultParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithDefaultParameter(global::Service service = default) { Initialize(service); diff --git a/src/Tests/ReadmeExamples/PostConstructWithOutParameter.cs#PostConstructWithOutParameter.g.verified.cs b/src/Tests/ReadmeExamples/PostConstructWithOutParameter.cs#PostConstructWithOutParameter.g.verified.cs index eee8c678..ae4c5a48 100644 --- a/src/Tests/ReadmeExamples/PostConstructWithOutParameter.cs#PostConstructWithOutParameter.g.verified.cs +++ b/src/Tests/ReadmeExamples/PostConstructWithOutParameter.cs#PostConstructWithOutParameter.g.verified.cs @@ -7,10 +7,7 @@ partial class PostConstructWithOutParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithOutParameter( global::IOtherService otherService, global::IServiceProvider services diff --git a/src/Tests/ReadmeExamples/PostConstructWithParameter.cs#PostConstructWithParameter.g.verified.cs b/src/Tests/ReadmeExamples/PostConstructWithParameter.cs#PostConstructWithParameter.g.verified.cs index 490ecc7a..cb32c70a 100644 --- a/src/Tests/ReadmeExamples/PostConstructWithParameter.cs#PostConstructWithParameter.g.verified.cs +++ b/src/Tests/ReadmeExamples/PostConstructWithParameter.cs#PostConstructWithParameter.g.verified.cs @@ -7,10 +7,7 @@ partial class PostConstructWithParameter { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public PostConstructWithParameter( global::IService service, global::IInitializeService initialiseService diff --git a/src/Tests/ReadmeExamples/Properties.cs#Properties.g.verified.cs b/src/Tests/ReadmeExamples/Properties.cs#Properties.g.verified.cs index b41ef8a0..990b44d4 100644 --- a/src/Tests/ReadmeExamples/Properties.cs#Properties.g.verified.cs +++ b/src/Tests/ReadmeExamples/Properties.cs#Properties.g.verified.cs @@ -7,10 +7,7 @@ partial class Properties { - [global::System.Runtime.CompilerServices.CompilerGenerated] [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCode] - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] public Properties( string getProperty, string protectedProperty, diff --git a/src/Tests/ReadmeExamples/ServiceProvider.cs b/src/Tests/ReadmeExamples/ServiceProvider.cs new file mode 100644 index 00000000..159e0428 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProvider.cs @@ -0,0 +1,21 @@ +using AutoCtor; + +public interface IGreeter; +public interface IClock; + +public class Clock : IClock; + +#region ServiceProvider + +[ServiceProvider] +[Singleton] +[Singleton] +public sealed partial class Container; + +[AutoConstruct] +public partial class Greeter : IGreeter +{ + private readonly IClock _clock; +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProvider.cs#Container.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProvider.cs#Container.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..4c66f67e --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProvider.cs#Container.ServiceProvider.g.verified.cs @@ -0,0 +1,294 @@ +//HintName: Container.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class Container : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Clock()); + } + private volatile global::IGreeter? _S1; + private global::IGreeter S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::Greeter(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock Container.IResolver.Get() + => S0(); + global::IGreeter Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + Container.IResolver, + Container.IResolver, + Container.IResolver>, + Container.IResolver>, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver, + Container.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly Container _root; + public Scope(Container root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IGreeter S1() => _root.S1(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock Container.IResolver.Get() + => S0(); + global::IGreeter Container.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IGreeter[] { S1() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider Container.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProvider.cs#Greeter.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProvider.cs#Greeter.g.verified.cs new file mode 100644 index 00000000..5e936441 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProvider.cs#Greeter.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: Greeter.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class Greeter +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public Greeter(global::IClock clock) + { + this._clock = clock; + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs b/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs new file mode 100644 index 00000000..8d5f5142 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs @@ -0,0 +1,24 @@ +using AutoCtor; +using System.Collections.Generic; + +public interface INotifier; + +public class EmailNotifier : INotifier; +public class SmsNotifier : INotifier; + +#region ServiceProviderEnumerable + +[ServiceProvider] +[Singleton] +[Singleton] +[Singleton] +public sealed partial class AlertContainer; + +public interface IAlertService; + +public class AlertService : IAlertService +{ + public AlertService(IEnumerable notifiers) { } +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs#AlertContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs#AlertContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..afce1b48 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderEnumerable.cs#AlertContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,304 @@ +//HintName: AlertContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class AlertContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver>, + AlertContainer.IResolver>, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::INotifier? _S0; + private global::INotifier S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::EmailNotifier()); + } + private volatile global::INotifier? _S1; + private global::INotifier S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SmsNotifier()); + } + private volatile global::IAlertService? _S2; + private global::IAlertService S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::AlertService(new global::INotifier[] { S0(), S1() })); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::INotifier)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IAlertService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::INotifier[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAlertService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AlertContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AlertContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::INotifier AlertContainer.IResolver.Get() + => S1(); + global::IAlertService AlertContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable AlertContainer.IResolver>.Get() + => new global::INotifier[] { S0(), S1() }; + global::System.Collections.Generic.IEnumerable AlertContainer.IResolver>.Get() + => new global::IAlertService[] { S2() }; + global::System.IServiceProvider AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService AlertContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver>, + AlertContainer.IResolver>, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver, + AlertContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly AlertContainer _root; + public Scope(AlertContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::INotifier S0() => _root.S0(); + private global::INotifier S1() => _root.S1(); + private global::IAlertService S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::INotifier)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IAlertService)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::INotifier[] { S0(), S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAlertService[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is AlertContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is AlertContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::INotifier AlertContainer.IResolver.Get() + => S1(); + global::IAlertService AlertContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable AlertContainer.IResolver>.Get() + => new global::INotifier[] { S0(), S1() }; + global::System.Collections.Generic.IEnumerable AlertContainer.IResolver>.Get() + => new global::IAlertService[] { S2() }; + global::System.IServiceProvider AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider AlertContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService AlertContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderFactory.cs b/src/Tests/ReadmeExamples/ServiceProviderFactory.cs new file mode 100644 index 00000000..8ff931a2 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderFactory.cs @@ -0,0 +1,35 @@ +using AutoCtor; + +public interface IClock; +public interface IAuditLog; +public interface IBanner; + +public class SystemClock(string timeZone) : IClock +{ + public string TimeZone => timeZone; +} +public class AuditLog(System.IServiceProvider services) : IAuditLog +{ + public System.IServiceProvider Services => services; +} +public class Banner(string text) : IBanner +{ + public string Text => text; +} + +#region ServiceProviderFactory + +[ServiceProvider] +[Singleton(Factory = nameof(CreateClock))] +[Singleton(Factory = nameof(_banner))] +[Scoped(Factory = nameof(CreateAuditLog))] +public sealed partial class HostContainer +{ + private readonly IBanner _banner = new Banner("AutoCtor"); + + private IClock CreateClock() => new SystemClock("UTC"); + + private IAuditLog CreateAuditLog(System.IServiceProvider services) => new AuditLog(services); +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderFactory.cs#HostContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderFactory.cs#HostContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..bf5e6ca6 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderFactory.cs#HostContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,313 @@ +//HintName: HostContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class HostContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClock? _S0; + private global::IClock S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= Track(this.CreateClock())); + } + private volatile global::IBanner? _S1; + private global::IBanner S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(this._banner)); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IClock HostContainer.IResolver.Get() + => S0(); + global::IBanner HostContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IBanner[] { S1() }; + global::System.IServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HostContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver>, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver, + HostContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly HostContainer _root; + public Scope(HostContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClock S0() => _root.S0(); + private global::IBanner S1() => _root.S1(); + private volatile global::IAuditLog? _S2; + private global::IAuditLog S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= Track(_root.CreateAuditLog(this))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IClock)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IBanner)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IAuditLog)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IClock[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IBanner[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAuditLog[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is HostContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is HostContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IClock HostContainer.IResolver.Get() + => S0(); + global::IBanner HostContainer.IResolver.Get() + => S1(); + global::IAuditLog HostContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IClock[] { S0() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IBanner[] { S1() }; + global::System.Collections.Generic.IEnumerable HostContainer.IResolver>.Get() + => new global::IAuditLog[] { S2() }; + global::System.IServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider HostContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService HostContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderFallback.cs b/src/Tests/ReadmeExamples/ServiceProviderFallback.cs new file mode 100644 index 00000000..009a5197 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderFallback.cs @@ -0,0 +1,21 @@ +using AutoCtor; + +public interface ILogSink; +public interface IGreeter; +public class Greeter(ILogSink log) : IGreeter +{ + public ILogSink Log => log; +} + +#region ServiceProviderFallback + +[ServiceProvider(Fallback = nameof(_host))] +[Singleton] +public sealed partial class FallbackContainer +{ + private readonly System.IServiceProvider _host; + + public FallbackContainer(System.IServiceProvider host) => _host = host; +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderFallback.cs#FallbackContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderFallback.cs#FallbackContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..39235669 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderFallback.cs#FallbackContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,265 @@ +//HintName: FallbackContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class FallbackContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + FallbackContainer.IResolver, + FallbackContainer.IResolver>, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IGreeter? _S0; + private global::IGreeter S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Greeter(Required(GetKeyedService(typeof(global::ILogSink), null)))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => this._host; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IGreeter FallbackContainer.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable FallbackContainer.IResolver>.Get() + => new global::IGreeter[] { S0() }; + global::System.IServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService FallbackContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + FallbackContainer.IResolver, + FallbackContainer.IResolver>, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver, + FallbackContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly FallbackContainer _root; + public Scope(FallbackContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IGreeter S0() => _root.S0(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IGreeter)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IGreeter[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? _fallback; + private global::System.IServiceProvider? Fallback { get { lock (_lock) return _fallback ??= _root._host is global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory f ? Track(f.CreateScope()).ServiceProvider : _root._host; } } + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is FallbackContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IGreeter FallbackContainer.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable FallbackContainer.IResolver>.Get() + => new global::IGreeter[] { S0() }; + global::System.IServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider FallbackContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService FallbackContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs b/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs new file mode 100644 index 00000000..5e361c97 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs @@ -0,0 +1,23 @@ +using AutoCtor; + +public interface IClient; + +public class LiveClient : IClient; +public class SandboxClient : IClient; + +#region ServiceProviderKeyed + +[ServiceProvider] +[Singleton(Key = "live")] +[Singleton(Key = "sandbox")] +[Singleton] +public sealed partial class PaymentContainer; + +public interface IPaymentGateway; + +public class PaymentGateway : IPaymentGateway +{ + public PaymentGateway([AutoKeyedService("live")] IClient client) { } +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs#PaymentContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs#PaymentContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..8dff9f6d --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderKeyed.cs#PaymentContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,304 @@ +//HintName: PaymentContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class PaymentContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + PaymentContainer.IResolver, + PaymentContainer.IResolver>, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IClient? _S0; + private global::IClient S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::LiveClient()); + } + private volatile global::IClient? _S1; + private global::IClient S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::SandboxClient()); + } + private volatile global::IPaymentGateway? _S2; + private global::IPaymentGateway S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::PaymentGateway(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IClient) && object.Equals(key, "live")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClient) && object.Equals(key, "sandbox")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "live")) + return probe ? this : (object)(new global::IClient[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sandbox")) + return probe ? this : (object)(new global::IClient[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IClient[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPaymentGateway PaymentContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PaymentContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S2() }; + global::System.IServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PaymentContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + PaymentContainer.IResolver, + PaymentContainer.IResolver>, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver, + PaymentContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly PaymentContainer _root; + public Scope(PaymentContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IClient S0() => _root.S0(); + private global::IClient S1() => _root.S1(); + private global::IPaymentGateway S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + if (type == typeof(global::IClient) && object.Equals(key, "live")) + return probe ? this : (object)(S0()); + if (type == typeof(global::IClient) && object.Equals(key, "sandbox")) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "live")) + return probe ? this : (object)(new global::IClient[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, "sandbox")) + return probe ? this : (object)(new global::IClient[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable) && object.Equals(key, global::Microsoft.Extensions.DependencyInjection.KeyedService.AnyKey)) + return probe ? this : (object)(new global::IClient[] { S0(), S1() }); + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is PaymentContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPaymentGateway PaymentContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable PaymentContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S2() }; + global::System.IServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider PaymentContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService PaymentContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderModule.cs b/src/Tests/ReadmeExamples/ServiceProviderModule.cs new file mode 100644 index 00000000..e6645662 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderModule.cs @@ -0,0 +1,26 @@ +using AutoCtor; + +public interface IPaymentGateway; +public interface ILedger; +public class StripeGateway : IPaymentGateway; +public class FakeGateway : IPaymentGateway; +public class Ledger(IPaymentGateway gateway) : ILedger +{ + public IPaymentGateway Gateway => gateway; +} + +#region ServiceProviderModule + +[Singleton] +[Scoped(Factory = nameof(CreateLedger))] +public class PaymentsModule +{ + public static ILedger CreateLedger(IPaymentGateway gateway) => new Ledger(gateway); +} + +[ServiceProvider] +[Import] +[Singleton] +public sealed partial class TestContainer; + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderModule.cs#TestContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderModule.cs#TestContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..24344fc7 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderModule.cs#TestContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,293 @@ +//HintName: TestContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class TestContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + TestContainer.IResolver, + TestContainer.IResolver>, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPaymentGateway? _S0; + private global::IPaymentGateway S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::StripeGateway()); + } + private volatile global::IPaymentGateway? _S2; + private global::IPaymentGateway S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::FakeGateway()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S0(), S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPaymentGateway TestContainer.IResolver.Get() + => S2(); + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S0(), S2() }; + global::System.IServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService TestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver>, + TestContainer.IResolver>, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver, + TestContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly TestContainer _root; + public Scope(TestContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPaymentGateway S0() => _root.S0(); + private volatile global::ILedger? _S1; + private global::ILedger S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(global::PaymentsModule.CreateLedger(S2()))); + } + private global::IPaymentGateway S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPaymentGateway)) + return probe ? this : (object)(S2()); + if (type == typeof(global::ILedger)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPaymentGateway[] { S0(), S2() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ILedger[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is TestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is TestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPaymentGateway TestContainer.IResolver.Get() + => S2(); + global::ILedger TestContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::IPaymentGateway[] { S0(), S2() }; + global::System.Collections.Generic.IEnumerable TestContainer.IResolver>.Get() + => new global::ILedger[] { S1() }; + global::System.IServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider TestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService TestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs b/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs new file mode 100644 index 00000000..afba217d --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs @@ -0,0 +1,30 @@ +using AutoCtor; + +public interface ISession; +public class Session : ISession; + +public class Customer; + +#region ServiceProviderOpenGeneric + +[ServiceProvider] +[Singleton] +[Singleton(typeof(IRepository<>), typeof(Repository<>))] +[Singleton] +public sealed partial class ReportContainer; + +public interface IRepository; + +public class Repository : IRepository +{ + public Repository(ISession session) { } +} + +public interface ICustomerReport; + +public class CustomerReport : ICustomerReport +{ + public CustomerReport(IRepository customers) { } +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs#ReportContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs#ReportContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..4621bf7a --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderOpenGeneric.cs#ReportContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,324 @@ +//HintName: ReportContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class ReportContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver>, + ReportContainer.IResolver>, + ReportContainer.IResolver>, + ReportContainer.IResolver>>, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::ISession? _S0; + private global::ISession S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Session()); + } + private volatile global::ICustomerReport? _S1; + private global::ICustomerReport S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::CustomerReport(S2())); + } + private volatile global::IRepository? _S2; + private global::IRepository S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::Repository(S0())); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISession)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICustomerReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRepository)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISession[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICustomerReport[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IRepository[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ReportContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ReportContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::ISession ReportContainer.IResolver.Get() + => S0(); + global::ICustomerReport ReportContainer.IResolver.Get() + => S1(); + global::IRepository ReportContainer.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ReportContainer.IResolver>.Get() + => new global::ISession[] { S0() }; + global::System.Collections.Generic.IEnumerable ReportContainer.IResolver>.Get() + => new global::ICustomerReport[] { S1() }; + global::System.Collections.Generic.IEnumerable> ReportContainer.IResolver>>.Get() + => new global::IRepository[] { S2() }; + global::System.IServiceProvider ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ReportContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver>, + ReportContainer.IResolver>, + ReportContainer.IResolver>, + ReportContainer.IResolver>>, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver, + ReportContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly ReportContainer _root; + public Scope(ReportContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::ISession S0() => _root.S0(); + private global::ICustomerReport S1() => _root.S1(); + private global::IRepository S2() => _root.S2(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::ISession)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICustomerReport)) + return probe ? this : (object)(S1()); + if (type == typeof(global::IRepository)) + return probe ? this : (object)(S2()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ISession[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICustomerReport[] { S1() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IRepository[] { S2() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is ReportContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is ReportContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::ISession ReportContainer.IResolver.Get() + => S0(); + global::ICustomerReport ReportContainer.IResolver.Get() + => S1(); + global::IRepository ReportContainer.IResolver>.Get() + => S2(); + global::System.Collections.Generic.IEnumerable ReportContainer.IResolver>.Get() + => new global::ISession[] { S0() }; + global::System.Collections.Generic.IEnumerable ReportContainer.IResolver>.Get() + => new global::ICustomerReport[] { S1() }; + global::System.Collections.Generic.IEnumerable> ReportContainer.IResolver>>.Get() + => new global::IRepository[] { S2() }; + global::System.IServiceProvider ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider ReportContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService ReportContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderScan.cs b/src/Tests/ReadmeExamples/ServiceProviderScan.cs new file mode 100644 index 00000000..1610be0b --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderScan.cs @@ -0,0 +1,17 @@ +using AutoCtor; + +public class CreateOrder; +public class CancelOrder; + +#region ServiceProviderScan + +[ServiceProvider] +[ScanTransient(typeof(IHandler<>))] +public sealed partial class OrderContainer; + +public interface IHandler; + +public class CreateOrderHandler : IHandler; +public class CancelOrderHandler : IHandler; + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderScan.cs#OrderContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderScan.cs#OrderContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f531bd06 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderScan.cs#OrderContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,278 @@ +//HintName: OrderContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class OrderContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + OrderContainer.IResolver>, + OrderContainer.IResolver>, + OrderContainer.IResolver>>, + OrderContainer.IResolver>>, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IHandler OrderContainer.IResolver>.Get() + => S0(); + global::IHandler OrderContainer.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.IServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService OrderContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + OrderContainer.IResolver>, + OrderContainer.IResolver>, + OrderContainer.IResolver>>, + OrderContainer.IResolver>>, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver, + OrderContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly OrderContainer _root; + public Scope(OrderContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IHandler S0() => new global::CancelOrderHandler(); + private global::IHandler S1() => new global::CreateOrderHandler(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IHandler)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable>)) + return probe ? this : (object)(new global::IHandler[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is OrderContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IHandler OrderContainer.IResolver>.Get() + => S0(); + global::IHandler OrderContainer.IResolver>.Get() + => S1(); + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S0() }; + global::System.Collections.Generic.IEnumerable> OrderContainer.IResolver>>.Get() + => new global::IHandler[] { S1() }; + global::System.IServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider OrderContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService OrderContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderScoped.cs b/src/Tests/ReadmeExamples/ServiceProviderScoped.cs new file mode 100644 index 00000000..eb02f337 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderScoped.cs @@ -0,0 +1,22 @@ +using AutoCtor; +using System; + +public interface IAppSettings; +public interface IDbContext; + +public class AppSettings : IAppSettings; + +#region ServiceProviderScoped + +[ServiceProvider] +[Singleton] +[Scoped] +public sealed partial class RequestContainer; + +public class DbContext : IDbContext, IDisposable +{ + public DbContext(IAppSettings settings) { } + public void Dispose() { } +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderScoped.cs#RequestContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderScoped.cs#RequestContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..f85e30f9 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderScoped.cs#RequestContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,283 @@ +//HintName: RequestContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class RequestContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + RequestContainer.IResolver, + RequestContainer.IResolver>, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IAppSettings? _S0; + private global::IAppSettings S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::AppSettings()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAppSettings)) + return probe ? this : (object)(S0()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAppSettings[] { S0() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RequestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RequestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IAppSettings RequestContainer.IResolver.Get() + => S0(); + global::System.Collections.Generic.IEnumerable RequestContainer.IResolver>.Get() + => new global::IAppSettings[] { S0() }; + global::System.IServiceProvider RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService RequestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver>, + RequestContainer.IResolver>, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver, + RequestContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly RequestContainer _root; + public Scope(RequestContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IAppSettings S0() => _root.S0(); + private volatile global::IDbContext? _S1; + private global::IDbContext S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= Track(new global::DbContext(S0()))); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IAppSettings)) + return probe ? this : (object)(S0()); + if (type == typeof(global::IDbContext)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IAppSettings[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IDbContext[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is RequestContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is RequestContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IAppSettings RequestContainer.IResolver.Get() + => S0(); + global::IDbContext RequestContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable RequestContainer.IResolver>.Get() + => new global::IAppSettings[] { S0() }; + global::System.Collections.Generic.IEnumerable RequestContainer.IResolver>.Get() + => new global::IDbContext[] { S1() }; + global::System.IServiceProvider RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider RequestContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService RequestContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderTransient.cs b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs new file mode 100644 index 00000000..e026304a --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs @@ -0,0 +1,21 @@ +using AutoCtor; + +public interface IConnection; +public interface ICommand; + +public class Connection : IConnection; + +#region ServiceProviderTransient + +[ServiceProvider] +[Singleton] +[Transient] +public sealed partial class CommandContainer; + +[AutoConstruct] +public partial class Command : ICommand +{ + private readonly IConnection _connection; +} + +#endregion diff --git a/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#Command.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#Command.g.verified.cs new file mode 100644 index 00000000..5ba23a36 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#Command.g.verified.cs @@ -0,0 +1,15 @@ +//HintName: Command.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +partial class Command +{ + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public Command(global::IConnection connection) + { + this._connection = connection; + } +} diff --git a/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#CommandContainer.ServiceProvider.g.verified.cs b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#CommandContainer.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..e50826f9 --- /dev/null +++ b/src/Tests/ReadmeExamples/ServiceProviderTransient.cs#CommandContainer.ServiceProvider.g.verified.cs @@ -0,0 +1,286 @@ +//HintName: CommandContainer.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class CommandContainer : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver>, + CommandContainer.IResolver>, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IConnection? _S0; + private global::IConnection S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::Connection()); + } + private global::ICommand S1() => new global::Command(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IConnection)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICommand)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IConnection[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICommand[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CommandContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CommandContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IConnection CommandContainer.IResolver.Get() + => S0(); + global::ICommand CommandContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable CommandContainer.IResolver>.Get() + => new global::IConnection[] { S0() }; + global::System.Collections.Generic.IEnumerable CommandContainer.IResolver>.Get() + => new global::ICommand[] { S1() }; + global::System.IServiceProvider CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService CommandContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(this); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService, + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider, + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService, + global::System.IDisposable, + global::Microsoft.Extensions.DependencyInjection.IServiceScope, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver>, + CommandContainer.IResolver>, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver, + CommandContainer.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly CommandContainer _root; + public Scope(CommandContainer root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IConnection S0() => _root.S0(); + private global::ICommand S1() => new global::Command(S0()); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IConnection)) + return probe ? this : (object)(S0()); + if (type == typeof(global::ICommand)) + return probe ? this : (object)(S1()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IConnection[] { S0() }); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::ICommand[] { S1() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)) + return probe ? this : (object)(this); + if (type == typeof(global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : (Fallback as global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider)?.GetKeyedService(serviceType, serviceKey)); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is CommandContainer.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is CommandContainer.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IConnection CommandContainer.IResolver.Get() + => S0(); + global::ICommand CommandContainer.IResolver.Get() + => S1(); + global::System.Collections.Generic.IEnumerable CommandContainer.IResolver>.Get() + => new global::IConnection[] { S0() }; + global::System.Collections.Generic.IEnumerable CommandContainer.IResolver>.Get() + => new global::ICommand[] { S1() }; + global::System.IServiceProvider CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IKeyedServiceProvider CommandContainer.IResolver.Get() + => this; + global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService CommandContainer.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null || (serviceKey == null ? Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsService s && s.IsService(serviceType) : Fallback is global::Microsoft.Extensions.DependencyInjection.IServiceProviderIsKeyedService k && k.IsKeyedService(serviceType, serviceKey)); + + public Scope CreateScope() => new Scope(_root); + global::Microsoft.Extensions.DependencyInjection.IServiceScope global::Microsoft.Extensions.DependencyInjection.IServiceScopeFactory.CreateScope() => CreateScope(); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ScanReferencedAssemblyTests.ScansReferencedAssemblies#Container.ServiceProvider.g.verified.cs b/src/Tests/ScanReferencedAssemblyTests.ScansReferencedAssemblies#Container.ServiceProvider.g.verified.cs new file mode 100644 index 00000000..bce15bf3 --- /dev/null +++ b/src/Tests/ScanReferencedAssemblyTests.ScansReferencedAssemblies#Container.ServiceProvider.g.verified.cs @@ -0,0 +1,243 @@ +//HintName: Container.ServiceProvider.g.cs +//------------------------------------------------------------------------------ +// +// This code was generated by https://github.com/distantcam/AutoCtor +// +//------------------------------------------------------------------------------ + +#nullable enable + +partial class Container : + global::System.IServiceProvider, + global::System.IDisposable, + Container.IResolver, + Container.IResolver>, + Container.IResolver + +{ + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + + private volatile global::IPlugin? _S0; + private global::IPlugin S0() + { + var service = _S0; + if (service is not null) + return service; + lock (_lock) + return (_S0 ??= new global::FriendPlugin()); + } + private volatile global::IPlugin? _S1; + private global::IPlugin S1() + { + var service = _S1; + if (service is not null) + return service; + lock (_lock) + return (_S1 ??= new global::InternalFriendPlugin()); + } + private volatile global::IPlugin? _S2; + private global::IPlugin S2() + { + var service = _S2; + if (service is not null) + return service; + lock (_lock) + return (_S2 ??= new global::LocalPlugin()); + } + private volatile global::IPlugin? _S3; + private global::IPlugin S3() + { + var service = _S3; + if (service is not null) + return service; + lock (_lock) + return (_S3 ??= new global::PublicPlugin()); + } + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1(), S2(), S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + private interface IResolver { T Get(); } + global::IPlugin Container.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IPlugin[] { S0(), S1(), S2(), S3() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(this); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + + [global::System.CodeDom.Compiler.GeneratedCode("AutoCtor", "0.0.0.0")] + public sealed class Scope : + global::System.IServiceProvider, + global::System.IDisposable, + Container.IResolver, + Container.IResolver>, + Container.IResolver + + { + private readonly object _lock = new object(); + private global::System.Collections.Generic.List? _disposables; + private readonly Container _root; + public Scope(Container root) => _root = root; + public global::System.IServiceProvider ServiceProvider => this; + + private global::IPlugin S0() => _root.S0(); + private global::IPlugin S1() => _root.S1(); + private global::IPlugin S2() => _root.S2(); + private global::IPlugin S3() => _root.S3(); + + private object? Resolve(global::System.Type type, object? key, bool probe) + { + if (key is null) + { + if (type == typeof(global::IPlugin)) + return probe ? this : (object)(S3()); + if (type == typeof(global::System.Collections.Generic.IEnumerable)) + return probe ? this : (object)(new global::IPlugin[] { S0(), S1(), S2(), S3() }); + if (type == typeof(global::System.IServiceProvider)) + return probe ? this : (object)(this); + return null; + } + return null; + } + + private global::System.IServiceProvider? Fallback => null; + + public object? GetService(global::System.Type serviceType) + => GetKeyedService(serviceType, null); + + public object? GetKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, false) + ?? (serviceKey == null ? Fallback?.GetService(serviceType) : null); + + public object GetRequiredKeyedService(global::System.Type serviceType, object? serviceKey) + => GetKeyedService(serviceType, serviceKey) + ?? throw new global::System.InvalidOperationException("No service for type '" + serviceType + "' has been registered."); + + public T? GetService() + => this is Container.IResolver resolver ? resolver.Get() : GetService(typeof(T)) is T service ? service : default; + + public T GetRequiredService() + => this is Container.IResolver resolver ? resolver.Get() : (T)GetRequiredKeyedService(typeof(T), null); + + global::IPlugin Container.IResolver.Get() + => S3(); + global::System.Collections.Generic.IEnumerable Container.IResolver>.Get() + => new global::IPlugin[] { S0(), S1(), S2(), S3() }; + global::System.IServiceProvider Container.IResolver.Get() + => this; + + public bool IsService(global::System.Type serviceType) + => IsKeyedService(serviceType, null); + + public bool IsKeyedService(global::System.Type serviceType, object? serviceKey) + => Resolve(serviceType, serviceKey, true) != null; + + public Scope CreateScope() => new Scope(_root); + + private static T Required(object? service) + => service is T t ? t : throw new global::System.InvalidOperationException("No service for type '" + typeof(T) + "' has been registered."); + + private T Track(T service) + { + if (service is global::System.IDisposable) + lock (_lock) + (_disposables ??= new global::System.Collections.Generic.List()).Add(service); + return service; + } + + private object[] Drain() + { + lock (_lock) + { + var items = _disposables?.ToArray() ?? new object[0]; + _disposables = null; + global::System.Array.Reverse(items); + return items; + } + } + + public void Dispose() + { + foreach (var item in Drain()) + { + if (item is global::System.IDisposable disposable) + { + disposable.Dispose(); + } + } + } + } +} diff --git a/src/Tests/ScanReferencedAssemblyTests.cs b/src/Tests/ScanReferencedAssemblyTests.cs new file mode 100644 index 00000000..f742655c --- /dev/null +++ b/src/Tests/ScanReferencedAssemblyTests.cs @@ -0,0 +1,114 @@ +using AutoCtor; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using static ExampleTests; + +internal sealed class ScanReferencedAssemblyTests +{ + private const string PluginLibrary = """ + public interface IPlugin; + public class PluginMarker; + public class PublicPlugin : IPlugin; + internal class InternalPlugin : IPlugin; + """; + + // Grants its internals to the app, so its internal plugin is found and the other + // library's is not. + private const string FriendLibrary = """ + [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("App")] + public class FriendMarker; + public class FriendPlugin : IPlugin; + internal class InternalFriendPlugin : IPlugin; + """; + + private const string App = """ + [AutoCtor.ServiceProvider] + [AutoCtor.ScanSingleton(typeof(IPlugin), + FromAssembliesOf = new[] { typeof(Container), typeof(PluginMarker), typeof(FriendMarker) })] + public sealed partial class Container; + public class LocalPlugin : IPlugin; + """; + + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task ScansReferencedAssemblies(BareCompilationBuilderFactory builderFactory) + { + var builder = builderFactory.Builder; + var pluginLibrary = builder.AddCodes(PluginLibrary).Build("PluginLibrary"); + var friendLibrary = builder.AddCompilationReference(pluginLibrary) + .AddCodes(FriendLibrary).Build("FriendLibrary"); + var app = builder.AddCompilationReference(pluginLibrary) + .AddCompilationReference(friendLibrary) + .AddCodes(App); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(app.ParseOptions) + .RunGeneratorsAndUpdateCompilation( + app.Build("App"), + out var outputCompilation, + out _, + TestHelper.CancellationToken); + + // Each compilation carries its own copy of the test polyfills, which only warns. + await Assert.That(outputCompilation.GetDiagnostics(TestHelper.CancellationToken) + .Where(d => d.Severity == DiagnosticSeverity.Error)) + .IsEmpty() + .ConfigureAwait(false); + + await Verify(driver) + .IgnoreParameters() + .ConfigureAwait(false); + } + +#if ROSLYN_4_4 + [Test] + [ClassDataSource(Shared = SharedType.PerTestSession)] + public async Task NewMatchingTypesArePickedUp(BareCompilationBuilderFactory builderFactory) + { + var builder = builderFactory.Builder; + var pluginLibrary = builder.AddCodes(PluginLibrary).Build("PluginLibrary"); + var friendLibrary = builder.AddCompilationReference(pluginLibrary) + .AddCodes(FriendLibrary).Build("FriendLibrary"); + var app = builder.AddCompilationReference(pluginLibrary) + .AddCompilationReference(friendLibrary) + .AddCodes(App); + + var driver = new GeneratorDriverBuilder() + .AddGenerator(new AutoConstructSourceGenerator()) + .Build(app.ParseOptions) + .RunGenerators(app.Build("App"), TestHelper.CancellationToken); + + // A new type in the provider's own assembly. + var addedLocally = app.AddCodes("public class AddedPlugin : IPlugin;"); + driver = driver.RunGenerators(addedLocally.Build("App"), TestHelper.CancellationToken); + await AssertPickedUp(driver, "AddedPlugin").ConfigureAwait(false); + + // A new type in a referenced assembly, which reaches the app as a changed reference. + var changedLibrary = pluginLibrary.AddSyntaxTrees(CSharpSyntaxTree.ParseText( + "public class AddedLibraryPlugin : IPlugin;", app.ParseOptions, + cancellationToken: TestHelper.CancellationToken)); + var addedInLibrary = builder.AddCompilationReference(changedLibrary) + .AddCompilationReference(friendLibrary) + .AddCodes(App, "public class AddedPlugin : IPlugin;"); + driver = driver.RunGenerators(addedInLibrary.Build("App"), TestHelper.CancellationToken); + await AssertPickedUp(driver, "AddedLibraryPlugin").ConfigureAwait(false); + } + + private static async Task AssertPickedUp(GeneratorDriver driver, string typeName) + { + var result = driver.GetRunResult().Results[0]; + + await Assert.That(result.TrackedSteps[AutoConstructSourceGenerator.TrackingNames.ServiceProviders] + .SelectMany(s => s.Outputs) + .Select(o => o.Reason)) + .Contains(IncrementalStepRunReason.Modified) + .ConfigureAwait(false); + + await Assert.That(result.GeneratedSources + .Any(s => s.SourceText.ToString().Contains($"global::{typeName}", StringComparison.Ordinal))) + .IsTrue() + .ConfigureAwait(false); + } +#endif +} diff --git a/src/Tests/Tests.csproj b/src/Tests/Tests.csproj index 47b4e67d..b7957139 100644 --- a/src/Tests/Tests.csproj +++ b/src/Tests/Tests.csproj @@ -32,22 +32,8 @@ - - - - - - - - - - - - - - - - + + diff --git a/src/Tests/Utilities/CodeFileTheoryData.cs b/src/Tests/Utilities/CodeFileTheoryData.cs index e1d95067..5e4ab002 100644 --- a/src/Tests/Utilities/CodeFileTheoryData.cs +++ b/src/Tests/Utilities/CodeFileTheoryData.cs @@ -10,6 +10,9 @@ internal sealed record CodeFileTheoryData public bool LangPreview { get; set; } public string[] IgnoredCompileDiagnostics { get; set; } = []; + // For examples that deliberately report a diagnostic, so generate nothing to compile against. + public bool SnapshotOnly { get; set; } + [SetsRequiredMembers] public CodeFileTheoryData(string file, params string[] codes) { diff --git a/src/Tests/Utilities/ExampleTestsHelper.cs b/src/Tests/Utilities/ExampleTestsHelper.cs index 72eada66..03a8d277 100644 --- a/src/Tests/Utilities/ExampleTestsHelper.cs +++ b/src/Tests/Utilities/ExampleTestsHelper.cs @@ -55,7 +55,7 @@ public virtual async Task InitializeAsync() .WithPreprocessorSymbols(PreprocessorSymbols); Builder = await Builder - .AddReference(ReferenceAssemblies.NetStandard.NetStandard20) + .AddReference(BaseReferenceAssemblies) .ConfigureAwait(false); foreach (var id in GetNuGetIds()) { @@ -81,6 +81,8 @@ public sealed class CompilerFeatureRequiredAttribute(string featureName) : Attri "); } + protected virtual ReferenceAssemblies BaseReferenceAssemblies => ReferenceAssemblies.NetStandard.NetStandard20; + protected virtual IEnumerable GetNuGetIds() => []; public CompilationBuilder Create(CodeFileTheoryData theoryData) diff --git a/src/WebApiExample/Program.cs b/src/WebApiExample/Program.cs new file mode 100644 index 00000000..27f914d6 --- /dev/null +++ b/src/WebApiExample/Program.cs @@ -0,0 +1,70 @@ +using System.Text.Json.Serialization; +using AutoCtor; +using Microsoft.AspNetCore.Http.HttpResults; + +var builder = WebApplication.CreateSlimBuilder(args); + +// Replace the service provider with our own +builder.Host.UseServiceProviderFactory(new ServiceProviderFactory()); + +builder.Services.ConfigureHttpJsonOptions(options => +{ + options.SerializerOptions.TypeInfoResolverChain.Insert(0, AppJsonSerializerContext.Default); +}); + +var app = builder.Build(); + +var todosApi = app.MapGroup("/todos"); +todosApi.MapGet("/", (TodoService todoService) => todoService.GetTodos()) + .WithName("GetTodos"); + +todosApi.MapGet("/{id}", Results, NotFound> (int id, TodoService todoService) => + todoService.GetTodos().FirstOrDefault(a => a.Id == id) is { } todo + ? TypedResults.Ok(todo) + : TypedResults.NotFound()) + .WithName("GetTodoById"); + +app.Run(); + +// ---------------------------------------------------------------------------- + +internal record Todo(int Id, string? Title, DateOnly? DueBy = null, bool IsComplete = false); + +[JsonSerializable(typeof(Todo[]))] +internal partial class AppJsonSerializerContext : JsonSerializerContext; + +// ---------------------------------------------------------------------------- + +[ServiceProvider(Fallback = nameof(_fallback))] +[Singleton] +internal sealed partial class ExampleServiceProvider(IServiceProvider fallback) +{ + private readonly IServiceProvider _fallback = fallback; +} + +internal class ServiceProviderFactory : IServiceProviderFactory +{ + public IServiceCollection CreateBuilder(IServiceCollection services) => services; + + public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) + { + ExampleServiceProvider provider = null!; + containerBuilder.AddSingleton(_ => new DefaultHttpContextFactory(provider)); + return provider = new ExampleServiceProvider(containerBuilder.BuildServiceProvider(true)); + } +} + +[AutoConstruct] +internal partial class TodoService +{ + public Todo[] GetTodos() + { + return new Todo[] { + new(1, "Walk the dog"), + new(2, "Do the dishes", DateOnly.FromDateTime(DateTime.Now)), + new(3, "Do the laundry", DateOnly.FromDateTime(DateTime.Now.AddDays(1))), + new(4, "Clean the bathroom"), + new(5, "Clean the car", DateOnly.FromDateTime(DateTime.Now.AddDays(2))) + }; + } +} diff --git a/src/WebApiExample/Properties/launchSettings.json b/src/WebApiExample/Properties/launchSettings.json new file mode 100644 index 00000000..14ae47c1 --- /dev/null +++ b/src/WebApiExample/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "todos", + "applicationUrl": "http://localhost:5132", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/src/WebApiExample/WebApiExample.csproj b/src/WebApiExample/WebApiExample.csproj new file mode 100644 index 00000000..ba96cc43 --- /dev/null +++ b/src/WebApiExample/WebApiExample.csproj @@ -0,0 +1,17 @@ + + + + net10.0 + enable + enable + true + true + default + + + + + + + + diff --git a/src/WebApiExample/WebApiExample.http b/src/WebApiExample/WebApiExample.http new file mode 100644 index 00000000..4bf67f5c --- /dev/null +++ b/src/WebApiExample/WebApiExample.http @@ -0,0 +1,11 @@ +@WebApiExample_HostAddress = http://localhost:5132 + +GET {{WebApiExample_HostAddress}}/todos/ +Accept: application/json + +### + +GET {{WebApiExample_HostAddress}}/todos/1 +Accept: application/json + +### diff --git a/src/WebApiExample/appsettings.Development.json b/src/WebApiExample/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/src/WebApiExample/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/src/WebApiExample/appsettings.json b/src/WebApiExample/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/src/WebApiExample/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +}