Repository: z4kn4fein/stashbox Branch: master Commit: 433e18fc3afc Files: 336 Total size: 1.5 MB Directory structure: gitextract_6bqpv2p6/ ├── .gitattributes ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ ├── codeql-analysis.yml │ ├── docs.yml │ ├── linux-macOS-CI.yml │ ├── sonar-analysis.yml │ └── stale.yml ├── .gitignore ├── .version ├── CHANGELOG.md ├── LICENSE ├── README.md ├── appveyor-release.yml ├── appveyor.yml ├── docs/ │ ├── .gitignore │ ├── babel.config.js │ ├── docs/ │ │ ├── advanced/ │ │ │ ├── child-containers.md │ │ │ ├── decorators.md │ │ │ ├── generics.md │ │ │ ├── special-resolution-cases.md │ │ │ └── wrappers-resolvers.md │ │ ├── configuration/ │ │ │ ├── container-configuration.md │ │ │ └── registration-configuration.md │ │ ├── diagnostics/ │ │ │ ├── utilities.md │ │ │ └── validation.md │ │ ├── getting-started/ │ │ │ ├── glossary.md │ │ │ ├── introduction.md │ │ │ └── overview.md │ │ └── guides/ │ │ ├── advanced-registration.md │ │ ├── basics.md │ │ ├── lifetimes.md │ │ ├── scopes.md │ │ └── service-resolution.md │ ├── docusaurus.config.js │ ├── package.json │ ├── sidebars.js │ ├── src/ │ │ ├── components/ │ │ │ ├── CodeDescPanel/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ ├── NavbarItems/ │ │ │ │ ├── SeparatorNavbarItem/ │ │ │ │ │ ├── index.tsx │ │ │ │ │ └── styles.module.scss │ │ │ │ └── SvgNavbarItem/ │ │ │ │ ├── index.tsx │ │ │ │ └── styles.module.scss │ │ │ └── SvgIcon/ │ │ │ └── index.tsx │ │ ├── css/ │ │ │ ├── custom.scss │ │ │ ├── pagination.scss │ │ │ ├── search.scss │ │ │ ├── sidebar.scss │ │ │ ├── tab.scss │ │ │ └── toc.scss │ │ ├── pages/ │ │ │ ├── index.js │ │ │ └── index.module.scss │ │ ├── theme/ │ │ │ ├── CodeBlock/ │ │ │ │ └── index.js │ │ │ ├── Footer/ │ │ │ │ ├── index.js │ │ │ │ └── styles.module.scss │ │ │ └── NavbarItem/ │ │ │ └── ComponentTypes.js │ │ └── utils/ │ │ └── prismDark.mjs │ └── static/ │ └── .nojekyll ├── sandbox/ │ ├── stashbox.assemblyload/ │ │ ├── Program.cs │ │ └── stashbox.assemblyload.csproj │ ├── stashbox.benchmarks/ │ │ ├── BeginScopeBenchmarks.cs │ │ ├── ChildContainerBenchmarks.cs │ │ ├── DecoratorBenchmarks.cs │ │ ├── DisposeBenchmarks.cs │ │ ├── EnumerableBenchmarks.cs │ │ ├── FinalizerBenchmarks.cs │ │ ├── FuncBenchmarks.cs │ │ ├── NullableBenchmarks.cs │ │ ├── Program.cs │ │ ├── PropertyBenchmarks.cs │ │ ├── RegisterBenchmarks.cs │ │ ├── ResolveBenchmarks.cs │ │ ├── ScopedBenchmarks.cs │ │ ├── SingletonBenchmarks.cs │ │ ├── Stashbox.Benchmarks.csproj │ │ └── TreeBenchmarks.cs │ ├── stashbox.sandbox.sln │ └── stashbox.trimmed/ │ ├── Program.cs │ └── stashbox.trimmed.csproj ├── sn.snk ├── src/ │ ├── Attributes/ │ │ ├── DependencyAttribute.cs │ │ ├── DependencyNameAttribute.cs │ │ └── InjectionMethodAttribute.cs │ ├── Configuration/ │ │ ├── ContainerConfiguration.cs │ │ ├── ContainerConfigurator.cs │ │ └── Rules.cs │ ├── ContainerContext.cs │ ├── Exceptions/ │ │ ├── CompositionRootNotFoundException.cs │ │ ├── ConstructorNotFoundException.cs │ │ ├── InvalidRegistrationException.cs │ │ ├── LifetimeValidationFailedException.cs │ │ ├── ResolutionFailedException.cs │ │ └── ServiceAlreadyRegisteredException.cs │ ├── Expressions/ │ │ ├── Compile/ │ │ │ ├── Closure.cs │ │ │ ├── CompilerContext.cs │ │ │ ├── Emitters/ │ │ │ │ ├── Emitter.Assign.cs │ │ │ │ ├── Emitter.Call.cs │ │ │ │ ├── Emitter.Constant.cs │ │ │ │ ├── Emitter.Convert.cs │ │ │ │ ├── Emitter.Default.cs │ │ │ │ ├── Emitter.Invoke.cs │ │ │ │ ├── Emitter.Lambda.cs │ │ │ │ ├── Emitter.MemberAccess.cs │ │ │ │ ├── Emitter.MemberInit.cs │ │ │ │ ├── Emitter.New.cs │ │ │ │ ├── Emitter.NewArrayInit.cs │ │ │ │ ├── Emitter.Parameter.cs │ │ │ │ └── Emitter.cs │ │ │ ├── ExpressionEmitter.cs │ │ │ ├── Extensions/ │ │ │ │ ├── CollectionExtensions.cs │ │ │ │ └── ILGeneratorExtensions.cs │ │ │ ├── NestedLambda.cs │ │ │ ├── TreeAnalyzer.cs │ │ │ └── Utils.cs │ │ ├── ExpressionBuilder.Default.cs │ │ ├── ExpressionBuilder.Factory.cs │ │ ├── ExpressionBuilder.Func.cs │ │ ├── ExpressionBuilder.cs │ │ ├── ExpressionFactory.Member.cs │ │ ├── ExpressionFactory.Method.cs │ │ └── ExpressionFactory.cs │ ├── Extensions/ │ │ ├── AssemblyExtensions.cs │ │ ├── CollectionExtensions.cs │ │ ├── EnumerableExtensions.cs │ │ ├── ExpressionExtensions.cs │ │ └── TypeExtensions.cs │ ├── ICompositionRoot.cs │ ├── IContainerContext.cs │ ├── IDecoratorRegistrator.cs │ ├── IDependencyCollectionRegistrator.cs │ ├── IDependencyReMapper.cs │ ├── IDependencyRegistrator.cs │ ├── IDependencyResolver.cs │ ├── IFuncRegistrator.cs │ ├── IResolutionScope.cs │ ├── IStashboxContainer.cs │ ├── Lifetime/ │ │ ├── AutoLifetime.cs │ │ ├── EmptyLifetime.cs │ │ ├── ExpressionLifetimeDescriptor.cs │ │ ├── FactoryLifetimeDescriptor.cs │ │ ├── LifetimeDescriptor.cs │ │ ├── Lifetimes.cs │ │ ├── NamedScopeLifetime.cs │ │ ├── PerRequestLifetime.cs │ │ ├── ScopedLifetime.cs │ │ ├── SingletonLifetime.cs │ │ └── TransientLifetime.cs │ ├── Metadata.cs │ ├── Multitenant/ │ │ ├── ITenantDistributor.cs │ │ └── TenantDistributor.cs │ ├── Override.cs │ ├── ReadOnlyKeyValue.cs │ ├── Registration/ │ │ ├── DecoratorRepository.cs │ │ ├── Extensions/ │ │ │ ├── CollectionRegistratorExtensions.cs │ │ │ ├── DependencyRegistratorExtensions.cs │ │ │ ├── DependencyRemapperExtensions.cs │ │ │ └── ServiceRepositoryExtensions.cs │ │ ├── Fluent/ │ │ │ ├── BaseDecoratorConfigurator.cs │ │ │ ├── BaseFluentConfigurator.cs │ │ │ ├── DecoratorConfigurator.cs │ │ │ ├── FluentServiceConfigurator.cs │ │ │ ├── RegistrationConfigurator.cs │ │ │ └── UnknownRegistrationConfigurator.cs │ │ ├── IDecoratorRepository.cs │ │ ├── IRegistrationRepository.cs │ │ ├── OpenGenericRegistration.cs │ │ ├── RegistrationDiagnosticsInfo.cs │ │ ├── RegistrationRepository.cs │ │ ├── SelectionRules/ │ │ │ ├── ConditionRule.cs │ │ │ ├── DecoratorRule.cs │ │ │ ├── EnumerableNameRule.cs │ │ │ ├── IRegistrationSelectionRule.cs │ │ │ ├── MetadataRule.cs │ │ │ ├── NameRule.cs │ │ │ ├── OpenGenericRule.cs │ │ │ ├── RegistrationSelectionRules.cs │ │ │ └── ScopeNameRule.cs │ │ ├── ServiceRegistration.cs │ │ └── ServiceRegistrator.cs │ ├── Resolution/ │ │ ├── DelegateCache.cs │ │ ├── DelegateCacheEntry.cs │ │ ├── Extensions/ │ │ │ ├── DependencyResolverExtensions.cs │ │ │ ├── InjectionParameterExtensions.cs │ │ │ └── ResolutionBehaviorExtensions.cs │ │ ├── ILookupResolver.cs │ │ ├── IRequestContext.cs │ │ ├── IResolutionStrategy.cs │ │ ├── IResolver.cs │ │ ├── IWrapper.cs │ │ ├── RequestContext.cs │ │ ├── ResolutionBehavior.cs │ │ ├── ResolutionContext.cs │ │ ├── ResolutionStrategy.cs │ │ ├── Resolvers/ │ │ │ ├── DefaultValueResolver.cs │ │ │ ├── OptionalValueResolver.cs │ │ │ ├── ParentContainerResolver.cs │ │ │ ├── ServiceProviderResolver.cs │ │ │ └── UnknownTypeResolver.cs │ │ ├── ServiceContext.cs │ │ ├── TypeInformation.cs │ │ └── Wrappers/ │ │ ├── EnumerableWrapper.cs │ │ ├── FuncWrapper.cs │ │ ├── KeyValueWrapper.cs │ │ ├── LazyWrapper.cs │ │ └── MetadataWrapper.cs │ ├── ResolutionScope.AsyncInitializer.cs │ ├── ResolutionScope.Disposable.cs │ ├── ResolutionScope.Resolver.cs │ ├── ResolutionScope.cs │ ├── StashboxContainer.CollectionRegistrator.cs │ ├── StashboxContainer.FuncRegistrator.cs │ ├── StashboxContainer.ReMapper.cs │ ├── StashboxContainer.Registrator.cs │ ├── StashboxContainer.Resolver.cs │ ├── StashboxContainer.cs │ ├── Utils/ │ │ ├── Constants.cs │ │ ├── Data/ │ │ │ ├── ExpandableArray.cs │ │ │ ├── HashTree.cs │ │ │ ├── Immutable/ │ │ │ │ ├── ImmutableBucket.cs │ │ │ │ ├── ImmutableLinkedList.cs │ │ │ │ └── ImmutableTree.cs │ │ │ ├── Pair.cs │ │ │ ├── Stack.cs │ │ │ └── Tree.cs │ │ ├── Shield.cs │ │ ├── Swap.cs │ │ └── TypeCache.cs │ └── stashbox.csproj ├── stashbox.sln └── test/ ├── ActivateTests.cs ├── AssemblyTests.cs ├── AsyncDisposeTests.cs ├── AttributeTests.cs ├── BuildUpTests.cs ├── CanResolveTests.cs ├── ChildContainerTests.cs ├── CircularDependencyTests.cs ├── CompilerTests/ │ ├── ConstantTests.cs │ ├── DefaultTests.cs │ └── NullableTests.cs ├── ComplexResolution.cs ├── CompositionTests.cs ├── ConditionalTests.cs ├── ConfigurationTests.cs ├── ConstructorSelectionTests.cs ├── ContainerTests.cs ├── DataTests/ │ └── TreeTests.cs ├── DecoratorTests.cs ├── DependencyBindingTests.cs ├── DisposeOrderTests.cs ├── DisposeTests.cs ├── EnumerableTests.cs ├── FactoryTests.cs ├── FuncTests.cs ├── GenericTests.cs ├── HierarchyTests.cs ├── InitializerFinalizerTests.cs ├── InjectionMemberTests.cs ├── InstanceBuilderTests.cs ├── IssueTests/ │ ├── 102_Resolving_Func_use_wrong_constructor.cs │ ├── 103_Resolving_base_class_dependencies.cs │ ├── 105_Question_How_to_work_with_dependency_overrides_from_factory_method.cs │ ├── 114_Unable_to_resolve_IHubContext.cs │ ├── 116_Different_types_registered_with_the_same_name.cs │ ├── 118_Named_resolution_using_ResolveAll_returns_all_named_and_unnamed_instanсes.cs │ ├── 119_generic_resolution_issue.cs │ ├── 129_Sharing_singleton_instances_between_Resolve_and_ResolveAll_and_subtypes.cs │ ├── 132_OpenGenericResolveIssue.cs │ ├── 141_Decorator_and_ResolveAll.cs │ ├── 144_Generic_decorators_broken.cs │ ├── 163_Last_write_win_problem_when_hash_collision_happens.cs │ ├── 16_Extensions_Identity_OptionsMonitor.cs │ ├── 213_Bug_Resolving_Lazy_Func.cs │ ├── 228_Stashbox_does_not_handle_Optional_correctly.cs │ ├── 33_ScopedLifetime_thread_safety.cs │ ├── 34_Resolution_from_parent_container.cs │ ├── 35_Mixture_of_named_and_non_named_registrations_result_in_the_wrong_type_resolved.cs │ ├── 37_Resolver_factory_invoke_doesnt_pass_different_parameters_given_when_theyre_the_same_type.cs │ ├── 38_Injecting_container_itself.cs │ ├── 42_Circular_dependency_tracking_doesnt_work_with_factory_resolution.cs │ ├── 43_Issue_with_Member_Injection_with_Attribute_but_private_setter.cs │ ├── 44_Lifetime_Issues.cs │ ├── 46_AspNetCore_Failing_spec_tests_forconstrained_generics.cs │ ├── 48_Chained_named_scopes_are_not_working_properly.cs │ ├── 49_Unable_to_use_nullable_types_with_injection_parameters.cs │ ├── 50_Generate_one_instance_for_multiple_interfaces.cs │ ├── 51_WithUnknownTypeResolution_breaks_constructor_selection_rules.cs │ ├── 52_Verify_child_container_working.cs │ ├── 53_ComposeBy_with_instance_or_injection.cs │ ├── 54_Make_InjectionParameter_configuration_more_fluent.cs │ ├── 55_Conditions_on_member_name_are_not_easy_to_recognize.cs │ ├── 58_InjectionParameter_NullReference.cs │ ├── 59_Static_factory_fails.cs │ ├── 63_named_unnamed_resolution_not_working_as_expected.cs │ ├── 64_WithFactory_MemberInjection_Not_Working_With_ImplementationType.cs │ ├── 66_Named_PutInstanceInScope.cs │ ├── 67_Dictionaries_get_resolved_to_arrays_of_key_type_by_default.cs │ ├── 68_Programmatic_multiple_instances_registration.cs │ ├── 70_UnkownType_overrides_instance_in_scope.cs │ ├── 71_FastExpressionCompiler_Issue.cs │ ├── 72_Default_lifetime_set.cs │ ├── 76_Exception_when_building_expressions.cs │ ├── 77_UnknownType_Resolution_Does_Not_Work.cs │ ├── 80_Expected_override_behaviour_not_working_with_scopes.cs │ ├── 84_DefinesScope_does_not_work_correctly.cs │ ├── 88_IdentityServer_not_compatible.cs │ ├── 89_Call_interception.cs │ ├── 91_Resolving_with_custom_parameter_values.cs │ ├── 97_Does_Scope_AttachToParent_only_affect_Dispose_behaviour.cs │ └── 98_Replace_doesnt_working_singleton.cs ├── KeyValueTests.cs ├── KeyedTests.cs ├── LazyTests.cs ├── LifetimeTests.cs ├── MetadataTests.cs ├── MultitenantTests.cs ├── NamedResolveTests.cs ├── NamedScopeTests.cs ├── OverrideTests.cs ├── PerRequestResolutionTests.cs ├── ReMapTests.cs ├── RegisterTypesTests.cs ├── ResolveFactoryTests.cs ├── ResolverTests.cs ├── ScopeTests.cs ├── ServiceProviderTests.cs ├── StandardResolveTests.cs ├── Utils/ │ ├── CompilerType.cs │ ├── CompilerTypeTestData.cs │ ├── ContainerConfiguratorExtensions.cs │ └── TypeGen.cs ├── WireUpTests.cs ├── WithDynamicResolutionTests.cs ├── stashbox.tests.csproj └── testassembly/ ├── Composition.cs ├── TestClasses.cs └── testassembly.csproj ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ ############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto ############################################################################### # Set default behavior for command prompt diff. # # This is need for earlier builds of msysgit that does not have it on by # default for csharp files. # Note: This is only used by command line ############################################################################### #*.cs diff=csharp ############################################################################### # Set the merge driver for project and solution files # # Merging from the command prompt will add diff markers to the files if there # are conflicts (Merging from VS is not affected by the settings below, in VS # the diff markers are never inserted). Diff markers may cause the following # file extensions to fail to load in VS. An alternative would be to treat # these files as binary and thus will always conflict and require user # intervention with every merge. To do so, just uncomment the entries below ############################################################################### #*.sln merge=binary #*.csproj merge=binary #*.vbproj merge=binary #*.vcxproj merge=binary #*.vcproj merge=binary #*.dbproj merge=binary #*.fsproj merge=binary #*.lsproj merge=binary #*.wixproj merge=binary #*.modelproj merge=binary #*.sqlproj merge=binary #*.wwaproj merge=binary ############################################################################### # behavior for image files # # image files are treated as binary by default. ############################################################################### #*.jpg binary #*.png binary #*.gif binary ############################################################################### # diff behavior for common document formats # # Convert binary document formats to text before diffing them. This feature # is only available from the command line. Turn it on by uncommenting the # entries below. ############################################################################### #*.doc diff=astextplain #*.DOC diff=astextplain #*.docx diff=astextplain #*.DOCX diff=astextplain #*.dot diff=astextplain #*.DOT diff=astextplain #*.pdf diff=astextplain #*.PDF diff=astextplain #*.rtf diff=astextplain #*.RTF diff=astextplain ================================================ FILE: .github/dependabot.yml ================================================ version: 2 enable-beta-ecosystems: true updates: - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" ================================================ FILE: .github/workflows/codeql-analysis.yml ================================================ # For most projects, this workflow file will not need changing; you simply need # to commit it to your repository. # # You may wish to alter this file to override the set of languages analyzed, # or to provide custom queries or build logic. # # ******** NOTE ******** # We have attempted to detect the languages in your repository. Please check # the `language` matrix defined below to confirm you have the correct set of # supported CodeQL languages. # name: "CodeQL" on: push: branches: [ master, dev ] paths-ignore: - '**.md' - 'docs/**' - 'appveyor*' pull_request: # The branches below must be a subset of the branches above branches: [ master ] schedule: - cron: '0 1 * * 2' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'csharp' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] # Learn more about CodeQL language support at https://git.io/codeql-language-support steps: - name: Checkout repository uses: actions/checkout@v5 - uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. # queries: ./path/to/local/query, your-org/your-repo/queries@main # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines # and modify them (or add more) to build your code if your project # uses a compiled language #- run: | # make bootstrap # make release - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v4 ================================================ FILE: .github/workflows/docs.yml ================================================ name: Deploy to GitHub Pages on: push: branches: [ master ] paths: - 'docs/**' - '.github/workflows/docs.yml' workflow_dispatch: defaults: run: working-directory: docs jobs: deploy: name: Deploy to GitHub Pages runs-on: ubuntu-latest steps: - uses: actions/checkout@v5 - uses: actions/setup-node@v6 with: node-version: 20 cache: yarn cache-dependency-path: ./docs/yarn.lock - name: Install dependencies run: yarn install --frozen-lockfile - name: Build website run: yarn build - name: Deploy to GitHub Pages uses: peaceiris/actions-gh-pages@v4 with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./docs/build ================================================ FILE: .github/workflows/linux-macOS-CI.yml ================================================ name: Build on Linux and macOS on: push: branches: [ master, dev ] paths-ignore: - '**.md' - 'docs/**' - 'appveyor*' pull_request: types: [opened, synchronize, reopened] env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: build-test: name: Build & test runs-on: ${{ matrix.os }} strategy: matrix: os: [ macos-latest, ubuntu-latest ] steps: - uses: actions/checkout@v5 - uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x 9.0.x 10.0.x - name: Restore run: dotnet restore - name: Test run: | dotnet test test/stashbox.tests.csproj -c Release -f net8.0 --no-restore dotnet test test/stashbox.tests.csproj -c Release -f net9.0 --no-restore dotnet test test/stashbox.tests.csproj -c Release -f net10.0 --no-restore ================================================ FILE: .github/workflows/sonar-analysis.yml ================================================ name: SonarCloud Analysis on: push: branches: [ master, dev ] paths-ignore: - '**.md' - 'docs/**' - 'appveyor*' pull_request: types: [opened, synchronize, reopened] env: DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true DOTNET_CLI_TELEMETRY_OPTOUT: true jobs: analysis: name: Run analysis & code coverage runs-on: windows-latest steps: - name: Set up JDK 17 uses: actions/setup-java@v5 with: java-version: 17 distribution: temurin - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: actions/setup-dotnet@v5 with: dotnet-version: | 8.0.x - name: Cache SonarCloud packages uses: actions/cache@v5 with: path: ~\sonar\cache key: ${{ runner.os }}-sonar restore-keys: ${{ runner.os }}-sonar - name: Cache SonarCloud scanner id: cache-sonar-scanner uses: actions/cache@v5 with: path: .\.sonar\scanner key: ${{ runner.os }}-sonar-scanner restore-keys: ${{ runner.os }}-sonar-scanner - name: Install SonarCloud scanner if: steps.cache-sonar-scanner.outputs.cache-hit != 'true' shell: powershell run: | New-Item -Path .\.sonar\scanner -ItemType Directory dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner - name: Build and analyze env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} shell: powershell run: | .\.sonar\scanner\dotnet-sonarscanner begin /k:"z4kn4fein_stashbox" /v:"${{ github.run_number }}" /o:"z4kn4fein" /d:sonar.token="${{ secrets.SONAR_TOKEN }}" /d:sonar.host.url="https://sonarcloud.io" /d:sonar.coverage.exclusions="test/**,src/Utils/**,src/Expressions/Compile/**" /d:sonar.exclusions="test/**,src/Utils/**,src/Expressions/Compile/**" /d:sonar.cs.opencover.reportsPaths="**/TestResults/**/coverage.opencover.xml" -d:sonar.cs.vstest.reportsPaths="**/TestResults/*.trx" dotnet test test\stashbox.tests.csproj -c Release -f net8.0 --logger trx --collect:"XPlat Code Coverage" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.Format=opencover .\.sonar\scanner\dotnet-sonarscanner end /d:sonar.token="${{ secrets.SONAR_TOKEN }}" ================================================ FILE: .github/workflows/stale.yml ================================================ # This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time. # # You can adjust the behavior by modifying this file. # For more information, see: # https://github.com/actions/stale name: Mark stale issues and pull requests on: schedule: - cron: '0 1 * * *' jobs: stale: runs-on: ubuntu-latest permissions: issues: write pull-requests: write steps: - uses: actions/stale@v10 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: 'This issue is marked stale because it has been open for 90 days with no activity.' stale-pr-message: 'This PR is marked stale because it has been open for 90 days with no activity.' close-issue-message: 'This issue was closed due to no activity.' close-pr-message: 'This PR was closed due to no activity.' days-before-issue-stale: 90 days-before-pr-stale: 90 days-before-issue-close: 10 days-before-pr-close: 10 ================================================ FILE: .gitignore ================================================ ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. # User-specific files *.suo *.user *.userosscache *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) *.userprefs # Build results [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ [Rr]eleases/ x64/ x86/ build/ bld/ [Bb]in/ [Oo]bj/ # Visual Studo 2015 cache/options directory .vs/ # MSTest test Results [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT *.VisualState.xml TestResult.xml # Build Results of an ATL Project [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c *_i.c *_p.c *_i.h *.ilk *.meta *.obj *.pch *.pdb *.pgc *.pgd *.rsp *.sbr *.tlb *.tli *.tlh *.tmp *.tmp_proj *.log *.vspscc *.vssscc .builds *.pidb *.svclog *.scc # Chutzpah Test files _Chutzpah* # Visual C++ cache files ipch/ *.aps *.ncb *.opensdf *.sdf *.cachefile # Visual Studio profiler *.psess *.vsp *.vspx # TFS 2012 Local Workspace $tf/ # Guidance Automation Toolkit *.gpState # ReSharper is a .NET coding add-in _ReSharper*/ *.[Rr]e[Ss]harper *.DotSettings.user # JustCode is a .NET coding addin-in .JustCode # TeamCity is a build add-in _TeamCity* # DotCover is a Code Coverage Tool *.dotCover # NCrunch _NCrunch_* .*crunch*.local.xml # MightyMoose *.mm.* AutoTest.Net/ # Web workbench (sass) .sass-cache/ # Installshield output folder [Ee]xpress/ # DocProject is a documentation generator add-in DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC DocProject/Help/*.hhc DocProject/Help/*.hhk DocProject/Help/*.hhp DocProject/Help/Html2 DocProject/Help/html # Click-Once directory publish/ # Publish Web Output *.[Pp]ublish.xml *.azurePubxml # TODO: Comment the next line if you want to checkin your web deploy settings # but database connection strings (with potential passwords) will be unencrypted *.pubxml *.publishproj # NuGet Packages *.nupkg # The packages folder can be ignored because of Package Restore **/packages/* # except build/, which is used as an MSBuild target. !**/packages/build/ # Uncomment if necessary however generally it will be regenerated when needed #!**/packages/repositories.config # Windows Azure Build Output csx/ *.build.csdef # Windows Store app package directory AppPackages/ # Others *.[Cc]ache ClientBin/ [Ss]tyle[Cc]op.* ~$* *~ *.dbmdl *.dbproj.schemaview *.pfx *.publishsettings node_modules/ bower_components/ # RIA/Silverlight projects Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML UpgradeLog*.htm # SQL Server files *.mdf *.ldf # Business Intelligence projects *.rdl.data *.bim.layout *.bim_*.settings # Microsoft Fakes FakesAssemblies/ # Node.js Tools for Visual Studio .ntvs_analysis.dat # Visual Studio 6 build log *.plg # Visual Studio 6 workspace options file *.opt tools coverageresults coverage.xml project.lock.json .sonarqube .idea ================================================ FILE: .version ================================================ 5.20.0 ================================================ FILE: CHANGELOG.md ================================================ Visit the [Github Releases](https://github.com/z4kn4fein/stashbox/releases) page of the repository for a complete changelog. ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2026 Peter Csajtai Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Stashbox [![Appveyor Build Status](https://img.shields.io/appveyor/build/pcsajtai/stashbox?logo=appveyor&logoColor=white)](https://ci.appveyor.com/project/pcsajtai/stashbox/branch/master) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/z4kn4fein/stashbox/linux-macOS-CI.yml?logo=GitHub&branch=master)](https://github.com/z4kn4fein/stashbox/actions/workflows/linux-macOS-CI.yml) [![NuGet Downloads](https://img.shields.io/nuget/dt/Stashbox?label=nuget)](https://www.nuget.org/packages/Stashbox) [![Sonar Tests](https://img.shields.io/sonar/tests/z4kn4fein_stashbox?compact_message&logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sonar Coverage](https://img.shields.io/sonar/coverage/z4kn4fein_stashbox?logo=SonarCloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/z4kn4fein_stashbox?logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sourcelink](https://img.shields.io/badge/sourcelink-enabled-brightgreen.svg)](https://github.com/dotnet/sourcelink) Stashbox is a lightweight, fast, and portable dependency injection framework for .NET-based solutions. It encourages the building of loosely coupled applications and simplifies the construction of hierarchical object structures. It can be integrated easily with .NET Core, Generic Host, ASP.NET, Xamarin, and many other applications. - [Documentation](https://z4kn4fein.github.io/stashbox) - [Release notes](https://github.com/z4kn4fein/stashbox/blob/master/CHANGELOG.md) - [ASP.NET Core sample](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection/tree/master/sample) Github (stable) | NuGet (stable) | NuGet (pre-release) --- | --- | --- [![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://img.shields.io/nuget/v/Stashbox)](https://www.nuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/) ## Core Attributes - 🚀 Fast, thread-safe, and lock-free operations. - ⚡️ Easy-to-use Fluent configuration API. - ♻️ Small memory footprint. - 🔄 Tracks the dependency tree for cycles. - 🚨 Detects and warns about misconfigurations. - 🔥 Gives fast feedback on registration/resolution issues. ## Supported Platforms - .NET 5+ - .NET Standard 2.0+ - .NET Framework 4.5+ - Mono - Universal Windows Platform - Xamarin (Android/iOS/Mac) - Unity ## Contact & Support - Create an [issue](https://github.com/z4kn4fein/stashbox/issues) for bug reports and feature requests. - Start a [discussion](https://github.com/z4kn4fein/stashbox/discussions) for your questions and ideas. - Add a ⭐️ to support the project! ## Extensions - ASP.NET Core - [Stashbox.Extensions.DependencyInjection](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection) - [Stashbox.Extensions.Hosting](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#net-generic-host) - [Stashbox.AspNetCore.Hosting](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection) - [Stashbox.AspNetCore.Multitenant](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#multitenant) - [Stashbox.AspNetCore.Testing](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#testing) - ASP.NET - [Stashbox.Web.WebApi](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-web-webapi) - [Stashbox.Web.Mvc](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-web-mvc) - [Stashbox.AspNet.SignalR](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-signalr) - OWIN - [Stashbox.Owin](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-owin) - [Stashbox.AspNet.WebApi.Owin](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-webapi-owin) - [Stashbox.AspNet.SignalR.Owin](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-signalr-owin) - WCF - [Stashbox.Extension.Wcf](https://github.com/devworker55/stashbox-extension-wcf) - Hangfire - [Stashbox.Hangfire](https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-hangfire) - Mocking - [Stashbox.Mocking](https://github.com/z4kn4fein/stashbox-mocking) (Moq, FakeItEasy, NSubstitute, RhinoMocks) ## Benchmarks - [Performance](https://github.com/danielpalme/IocPerformance)
*Powered by [Jetbrains'](https://www.jetbrains.com/?from=Stashbox) [Open Source License](https://www.jetbrains.com/community/opensource/?from=Stashbox)* [![Jetbrains](https://raw.githubusercontent.com/z4kn4fein/stashbox/master/assets/jetbrains.svg)](https://www.jetbrains.com/?from=Stashbox) ================================================ FILE: appveyor-release.yml ================================================ deploy: - provider: NuGet api_key: secure: RY9paWaFrCFasUjkcr9JU0d8U+9885/yViaEnXQ+gBeHUnh61S+VbuvUONcbqV5J - provider: NuGet server: https://nuget.pkg.github.com/z4kn4fein/index.json artifact: /.nupkg/ skip_symbols: true username: z4kn4fein api_key: secure: TaIug8cHioxT2qDznFpGtDinZiDi+20pEMQZUVAATWCvGLG9Y5LrjaxDUQtGyt38 - provider: GitHub tag: $(build_version) release: Stashbox v$(build_version) auth_token: secure: TaIug8cHioxT2qDznFpGtDinZiDi+20pEMQZUVAATWCvGLG9Y5LrjaxDUQtGyt38 artifact: /.*\.nupkg|.*\.snupkg/ environment: build_version: '' image: Visual Studio 2022 configuration: Release install: - ps: | $env:build_version = Get-Content ".version" Update-AppveyorBuild -Version "$env:build_version-$env:appveyor_build_number" dotnet tool install -g InheritDocTool Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile dotnet-install.ps1 ./dotnet-install.ps1 -Channel 10.0 dotnet_csproj: patch: true file: 'src\stashbox.csproj' version: $(build_version) package_version: $(build_version) assembly_version: $(build_version) file_version: $(build_version) informational_version: $(build_version) before_build: - dotnet restore stashbox.sln build_script: - dotnet build -c %configuration% /p:ContinuousIntegrationBuild=true stashbox.sln test_script: - dotnet test test\stashbox.tests.csproj -f net10.0 -c %configuration% --no-build after_build: - dotnet pack -c %configuration% /p:IncludeSymbols=true /p:PackageOutputPath=..\artifacts src\stashbox.csproj artifacts: - path: artifacts\Stashbox.*.nupkg name: NuGet Packages - path: artifacts\Stashbox.*.snupkg name: NuGet Symbol Packages notifications: - provider: Email to: - peter.csajtai@outlook.com on_build_success: false on_build_failure: true on_build_status_changed: true - provider: Slack auth_token: secure: /KAOQIEOWc7w1EUl6J01qNam+f+ujntrwh53yJ0zg4qRWsdfWbkjKP2UG7tQDW7/hSVJHqF7Hz/IPdS6Cp5ilsfgH6xYroLB/sawQ/pdC5k= channel: '#ci' ================================================ FILE: appveyor.yml ================================================ environment: github_auth_token: secure: z/dKTRVRPmpItPTM/lYdX7dBJk3roDLV98Uj1XzpDqqV868xhHX8dnyKwPAJooUj build_version: '' skip_tags: true skip_commits: files: - docs/ - .github/ - '**/*.md' image: Visual Studio 2022 configuration: Release install: - ps: | $env:build_version = Get-Content ".version" Update-AppveyorBuild -Version "$env:build_version-preview-$env:appveyor_build_number" Invoke-WebRequest 'https://dot.net/v1/dotnet-install.ps1' -OutFile dotnet-install.ps1 ./dotnet-install.ps1 -Channel 8.0 ./dotnet-install.ps1 -Channel 9.0 ./dotnet-install.ps1 -Channel 10.0 dotnet_csproj: patch: true file: 'src\stashbox.csproj' version: $(build_version) package_version: $(appveyor_build_version) assembly_version: $(build_version) file_version: $(build_version) informational_version: $(build_version) before_build: - dotnet restore stashbox.sln build_script: - dotnet build -c %configuration% /p:ContinuousIntegrationBuild=true stashbox.sln after_build: - dotnet pack -c %configuration% /p:IncludeSymbols=true /p:PackageOutputPath=..\artifacts src\stashbox.csproj test_script: - dotnet test test\stashbox.tests.csproj -f net8.0 -c %configuration% --no-build - dotnet test test\stashbox.tests.csproj -f net9.0 -c %configuration% --no-build - dotnet test test\stashbox.tests.csproj -f net10.0 -c %configuration% --no-build artifacts: - path: artifacts\Stashbox.*.nupkg name: NuGet Packages - path: artifacts\Stashbox.*.snupkg name: NuGet Symbol Packages notifications: - provider: Email to: - peter.csajtai@outlook.com on_build_success: false on_build_failure: true on_build_status_changed: true - provider: Slack auth_token: secure: /KAOQIEOWc7w1EUl6J01qNam+f+ujntrwh53yJ0zg4qRWsdfWbkjKP2UG7tQDW7/hSVJHqF7Hz/IPdS6Cp5ilsfgH6xYroLB/sawQ/pdC5k= channel: '#ci' ================================================ FILE: docs/.gitignore ================================================ # Dependencies /node_modules # Production /build # Generated files .docusaurus .cache-loader # Misc .DS_Store .env.local .env.development.local .env.test.local .env.production.local npm-debug.log* yarn-debug.log* yarn-error.log* ================================================ FILE: docs/babel.config.js ================================================ module.exports = { presets: [require.resolve('@docusaurus/core/lib/babel/preset')], }; ================================================ FILE: docs/docs/advanced/child-containers.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; # Child containers With child containers, you can build up parent-child relationships between containers. This means you can have a different subset of services present in a child than in the parent container. When a dependency is missing from the child container during a resolution request, the parent will be asked to resolve the missing service. If it's found there, the parent will return only the service's registration, and the resolution request will jump back to the child. Also, child registrations with the same [service type](/docs/getting-started/glossary#service-type--implementation-type) will override the parent's services. Resolving `IEnumerable` and [decorators](/docs/advanced/decorators) also considers parent containers by default. However, this behavior can be controlled with the [`ResolutionBehavior`](#resolution-behavior) parameter. :::info Child containers are the foundation of the [ASP.NET Core multi-tenant extension](https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#multitenant). ::: ## Example Here is an example case: ```cs interface IDependency {} class B : IDependency {} class C : IDependency {} class A { public A(IDependency dependency) { } } using (var container = new StashboxContainer()) { // register 'A' into the parent container. container.Register(); // register 'B' as a dependency into the parent container. container.Register(); var child = container.CreateChildContainer() // register 'C' as a dependency into the child container. child.Register(); // 'A' is resolved from the parent and gets // 'C' as IDependency because the resolution // request was initiated on the child. A fromChild = child.Resolve(); // 'A' gets 'B' as IDependency because the // resolution request was initiated on the parent. A fromParent = container.Resolve(); } // using will dispose the parent along with the child. ``` Let's see what's happening when we request `A` from the *child*: 1. `A` not found in the *child*, go up to the *parent* and check there. 2. `A` found in the *parent*, resolve. 3. `A` depends on `IDependency`, go back to the *child* and search `IDependency` implementations. 4. `C` found in the *child*, it does not have any dependencies, instantiate. 5. Inject the new `C` instance into `A`. 6. All dependencies are resolved; return `A`. When we make the same request on the parent, everything will go as usual because we have all dependencies in place. `B` will be injected into `A`. :::info You can [re-configure](/docs/configuration/container-configuration) child containers with the `.Configure()` method. It doesn't affect the parent container's configuration. ::: ## Accessing child containers You can identify child containers with the `identifier` parameter of `CreateChildContainer()`. Later, you can retrieve the given child container by passing its ID to `GetChildContainer()`. ```cs using var container = new StashboxContainer(); container.CreateChildContainer("child"); // ... var child = container.GetChildContainer("child"); ``` Also, each child container created by a container is available through the `IStashboxContainer.ChildContainers` propert. ```cs using var container = new StashboxContainer(); container.CreateChildContainer("child1"); container.CreateChildContainer("child2"); // ... foreach (var child in container.ChildContainers) { var id = child.Key; var childContainer = child.Value; } ``` ## Resolution behavior You can control which level of the container hierarchy can participate in the service resolution with the `ResolutionBehavior` parameter. Possible values: - `Default`: The default behavior, it's used when the parameter is not specified. Its value is `Parent | Current`, so both the current container (which initiated the resolution request) and its parents can participate in the resolution request's service selection. - `Parent`: Indicates that parent containers (including all indirect ancestors) can participate in the resolution request's service selection. - `Current`: Indicates that the current container (which initiated the resolution request) can participate in the service selection. - `ParentDependency`: Indicates that parent containers (including all indirect ancestors) can only provide dependencies for services that are already selected for resolution. - `PreferEnumerableInCurrent`: Upon enumerable resolution, when both `Current` and `Parent` behaviors are enabled, and the current container has the appropriate services, the resolution will prefer those and ignore the parent containers. When the current container doesn't have the requested services, the parent containers will serve the request. ```csharp interface IService {} class A : IService {} class B : IService {} using (var container = new StashboxContainer()) { // register 'A' into the parent container. container.Register(); var child = container.CreateChildContainer() // register 'B' into the child container. child.Register(); // 'A' is resolved because only parent // can participate in the resolution request. IService withParent = child.Resolve(ResolutionBehavior.Parent); // Only 'B' is in the collection because // only the caller container can take part // in the resolution request. IEnumerable allWithCurrent = child.Resolve>(ResolutionBehavior.Current); // Both 'A' and 'B' is in the collection // because both the parent and the caller container // participates in the resolution request. IEnumerable all = child.Resolve>(ResolutionBehavior.Current | ResolutionBehavior.Parent); } // using will dispose the parent along with the child. ``` ## Re-building singletons By default, singletons are instantiated and stored only in those containers that registered them. However, you can enable the re-instantiation of singletons in child containers with the `.WithReBuildSingletonsInChildContainer()` [container configuration option](/docs/configuration/container-configuration#re-build-singletons-in-child-containers). If it's enabled, all singletons will be re-created in those containers that initiated the resolution request. By this, re-built singletons can use overridden dependencies from child containers. Re-building in child containers does not affect the singletons instantiated in the parent container. ```cs interface IDependency {} class B : IDependency {} class C : IDependency {} class A { public A(IDependency dependency) { } } using (var container = new StashboxContainer(options => options.WithReBuildSingletonsInChildContainer())) { // register 'A' as a singleton into the parent container. container.RegisterSingleton(); // register 'B' as a dependency into the parent container. container.Register(); // 'A' gets 'B' as IDependency and will be stored // in the parent container as a singleton. A fromParent = container.Resolve(); var child = container.CreateChildContainer(); // register 'C' as a dependency into the child container. child.Register(); // a new 'A' singleton will be created in // the child container with 'C' as IDependency. A fromChild = child.Resolve(); } // using will dispose the parent along with the child. ``` ## Nested child containers
You can build up a hierarchical tree structure from containers by creating more child containers with the `.CreateChildContainer()` method.
```cs using var container = new StashboxContainer(); var child1 = container.CreateChildContainer(); var child2 = child1.CreateChildContainer(); ```
## Dispose By default, the parent container's disposal also disposes its child containers. You can control this behavior with the `CreateChildContainer()` method's `attachToParent` boolean parameter. ```cs using (var container = new StashboxContainer()) { using (var child1 = container.CreateChildContainer(attachToParent: false)) { } // child1 will be disposed only once here. var child2 = container.CreateChildContainer(); var child3 = container.CreateChildContainer(); } // using will dispose the parent along with child2 and child3. ``` You can safely dispose a child even if it's attached to its parent, in this case the parent's disposal will not dispose the already disposed child. ```cs using (var container = new StashboxContainer()) { using (var child1 = container.CreateChildContainer()) { } // child1 will be disposed only once here. var child2 = container.CreateChildContainer(); } // using will dispose only the parent and child2. ``` ================================================ FILE: docs/docs/advanced/decorators.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Decorators Stashbox supports decorator [service registration](/docs/getting-started/glossary#service-registration--registered-service) to take advantage of the [Decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern). This pattern is used to extend the functionality of a class without changing its implementation. This is also what the [Open–closed principle](https://en.wikipedia.org/wiki/Open%E2%80%93closed_principle) stands for; services should be open for extension but closed for modification. ## Simple use-case We define an `IEventProcessor` service used to process `Event` entities. Then we'll decorate this service with additional validation capabilities: ```cs class Event { } class UpdateEvent : Event { } interface IEventProcessor { void ProcessEvent(Event @event); } interface IEventValidator { bool IsValid(Event @event); } class EventValidator : IEventValidator { public bool IsValid(Event @event) { /* do the actual validation. */ } } class GeneralEventProcessor : IEventProcessor { public void ProcessEvent(Event @event) { // suppose this method is processing the given event. this.DoTheActualProcessing(@event); } } class ValidatorProcessor : IEventProcessor { private readonly IEventProcessor nextProcessor; private readonly IEventValidator eventValidator; public ValidatorProcessor(IEventProcessor eventProcessor, IEventValidator eventValidator) { this.nextProcessor = eventProcessor; this.eventValidator = eventValidator; } public void ProcessEvent(Event @event) { // validate the event first. if (!this.eventValidator.IsValid(@event)) throw new InvalidEventException(); // if everything is ok, call the next processor. this.nextProcessor.ProcessEvent(@event); } } using var container = new StashboxContainer(); container.Register(); container.Register(); container.RegisterDecorator(); // new ValidatorProcessor(new GeneralEventProcessor(), new EventValidator()) var eventProcessor = container.Resolve(); // process the event. eventProcessor.ProcessEvent(new UpdateEvent()); ``` The `GeneralEventProcessor` is an implementation of `IEventProcessor` and does the actual event processing logic. It does not have any other responsibilities. Rather than putting the event validation's burden onto its shoulder, we create a different service for validation purposes. Instead of injecting the validator into the `GeneralEventProcessor` directly, we let another `IEventProcessor` decorate it like an *event processing pipeline* that validates the event as a first step. ## Multiple decorators
You have the option to register multiple decorators for a service to extend its functionality. The decoration order will be the same as the registration order of the decorators. The first registered decorator will decorate the service itself. Then, all the subsequent decorators will wrap the already decorated service.
```cs container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); // new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor())); var processor = container.Resolve(); ```
## Conditional decoration With [conditional resolution](/docs/guides/service-resolution#conditional-resolution) you can control which decorator should be selected to decorate a given service. You have the option to set which decorator should be selected for a given implementation. For a single type filter, you can use the `.WhenDecoratedServiceIs()` configuration option. To select more types, you can use the more generic `.When()` option. ```cs container.Register(); container.Register(); container.RegisterDecorator(options => options // select when CustomProcessor or GeneralProcessor is resolved. .WhenDecoratedServiceIs() .WhenDecoratedServiceIs()); container.RegisterDecorator(options => options // select only when GeneralProcessor is resolved. .WhenDecoratedServiceIs()); // [ // new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor())), // new LoggerProcessor(new CustomProcessor()) // ] var processors = container.ResolveAll(); ``` You can filter for service names to control the decorator selection. ```cs container.Register("General"); container.Register("Custom"); container.RegisterDecorator(options => options // select when CustomProcessor or GeneralProcessor is resolved. .WhenDecoratedServiceIs("General") .WhenDecoratedServiceIs("Custom")); container.RegisterDecorator(options => options // select only when GeneralProcessor is resolved. .WhenDecoratedServiceIs("General")); // new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor())) var general = container.Resolve("General"); // new LoggerProcessor(new CustomProcessor()) var custom = container.Resolve("Custom"); ``` You can use your custom attributes to control the decorator selection. With **class attributes**, you can mark your classes for decoration. ```cs class LogAttribute : Attribute { } class ValidateAttribute : Attribute { } [Log, Validate] class GeneralProcessor : IEventProcessor { } [Log] class CustomProcessor : IEventProcessor { } container.Register(); container.Register(); container.RegisterDecorator(options => options // select when the resolving class has 'LogAttribute'. .WhenDecoratedServiceHas()); container.RegisterDecorator(options => options // select when the resolving class has 'ValidateAttribute'. .WhenDecoratedServiceHas()); // [ // new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor())), // new LoggerProcessor(new CustomProcessor()) // ] var processors = container.ResolveAll(); ``` You can also mark your dependencies for decoration with **property / field / parameter attributes**. ```cs class LogAttribute : Attribute { } class ValidateAttribute : Attribute { } class ProcessorExecutor { public ProcessorExecutor([Log, Validate]IEventProcessor eventProcessor) { } } container.Register(); container.Register(); container.RegisterDecorator(options => options // select when the resolving dependency has 'LogAttribute'. .WhenHas()); container.RegisterDecorator(options => options // select when the resolving dependency has 'ValidateAttribute'. .WhenHas()); // new ProcessorExecutor(new ValidatorProcessor(new LoggerProcessor(new GeneralProcessor()))) var executor = container.ResolveAll(); ``` ## Generic decorators Stashbox supports the registration of open-generic decorators, which allows the extension of open-generic services. Inspection of [generic parameter constraints](/docs/advanced/generics#generic-constraints) and [variance handling](/docs/advanced/generics#variance) is supported on generic decorators also. ```cs interface IEventProcessor { void ProcessEvent(TEvent @event); } class GeneralEventProcessor : IEventProcessor { public void ProcessEvent(TEvent @event) { /* suppose this method is processing the given event.*/ } } class ValidatorProcessor : IEventProcessor { private readonly IEventProcessor nextProcessor; public ValidatorProcessor(IEventProcessor eventProcessor) { this.nextProcessor = eventProcessor; } public void ProcessEvent(TEvent @event) { // validate the event first. if (!this.IsValid(@event)) throw new InvalidEventException(); // if everything is ok, call the next processor. this.nextProcessor.ProcessEvent(@event); } } using var container = new StashboxContainer(); container.Register(typeof(IEventProcessor<>), typeof(GeneralEventProcessor<>)); container.RegisterDecorator(typeof(IEventProcessor<>), typeof(ValidatorProcessor<>)); // new ValidatorProcessor(new GeneralEventProcessor()) var eventProcessor = container.Resolve>(); // process the event. eventProcessor.ProcessEvent(new UpdateEvent()); ``` ## Composite pattern The [Composite pattern](https://en.wikipedia.org/wiki/Composite_pattern) allows a group of objects to be treated the same way as a single instance of the same type. It's useful when you want to use the functionality of multiple instances behind the same interface. You can achieve this by registering a decorator that takes a collection of the same service as a dependency. ```cs public class CompositeValidator : IEventValidator { private readonly IEnumerable> validators; public CompositeValidator(IEnumerable> validators) { this.validators = validators; } public bool IsValid(TEvent @event) { return this.validators.All(validator => validator.IsValid(@event)); } } container.Register(typeof(IEventValidator<>), typeof(EventValidator<>)); container.Register(typeof(IEventValidator<>), typeof(AnotherEventValidator<>)); container.RegisterDecorator(typeof(IEventValidator<>), typeof(CompositeValidator<>)); ``` ## Decorating multiple services You have the option to organize similar decorating functionalities for different interfaces into the same decorator class. In this example, we would like to validate a given `Event` right before publishing and also before processing. ```cs public class EventValidator : IEventProcessor, IEventPublisher { private readonly IEventProcessor processor; private readonly IEventPublisher publisher; private readonly IEventValidator validator; public CompositeValidator(IEventProcessor processor, IEventPublisher publisher, IEventValidator validator) { this.processor = processor; this.publisher = publisher; this.validator = validator; } public void ProcessEvent(TEvent @event) { // validate the event first. if (!this.validator.IsValid(@event)) throw new InvalidEventException(); // if everything is ok, call the processor. this.processor.ProcessEvent(@event); } public void PublishEvent(TEvent @event) { // validate the event first. if (!this.validator.IsValid(@event)) throw new InvalidEventException(); // if everything is ok, call the publisher. this.publisher.PublishEvent(@event); } } container.Register(typeof(IEventProcessor<>), typeof(EventProcessor<>)); container.Register(typeof(IEventPublisher<>), typeof(EventPublisher<>)); container.Register(typeof(IEventValidator<>), typeof(EventValidator<>)); // without specifying the interface type, the container binds this registration to all of its implemented types container.RegisterDecorator(typeof(EventValidator<>)); ``` :::info You can also use the [Binding to multiple services](/docs/guides/advanced-registration#binding-to-multiple-services) options. ::: ## Lifetime
Just as other registrations, decorators also can have their lifetime. It means, in addition to the service's lifetime, all decorator's lifetime will be applied to the wrapped service. :::note When you don't set a decorator's lifetime, it'll implicitly inherit the decorated service's lifetime. :::
```cs container.Register(); // singleton decorator will change the transient // decorated service's lifetime to singleton. container.RegisterDecorator(options => options.WithLifetime(Lifetimes.Singleton)); // Singleton[new ValidatorProcessor()](Transien[new GeneralEventProcessor()]) var processor = container.Resolve(); ```
## Wrappers
Decorators are also applied to wrapped services. It means, in addition to the decoration, you can wrap your services in supported [wrappers](/docs/advanced/wrappers-resolvers#wrappers).
```cs container.Register(); container.RegisterDecorator(); // () => new ValidatorProcessor(new GeneralEventProcessor()) var processor = container.Resolve>(); ```
## Interception From the combination of Stashbox's decorator support and [Castle DynamicProxy's](http://www.castleproject.org/projects/dynamicproxy/) proxy generator, we can take advantage of the [Aspect-Oriented Programming's](https://en.wikipedia.org/wiki/Aspect-oriented_programming) benefits. The following example defines a `LoggingInterceptor` that will log additional messages related to the called service methods. ```cs public class LoggingInterceptor : IInterceptor { private readonly ILogger logger; public LoggingInterceptor(ILogger logger) { this.logger = logger; } public void Intercept(IInvocation invocation) { var stopwatch = new Stopwatch(); stopwatch.Start(); // log before we invoke the intercepted method. this.logger.Log($"Method begin: {invocation.GetConcreteMethod().Name}"); // call the intercepted method. invocation.Proceed(); // log after we invoked the intercepted method and print how long it ran. this.logger.Log($"Method end: {invocation.GetConcreteMethod().Name}, execution duration: {stopwatch.ElapsedMiliseconds} ms"); } } // create a DefaultProxyBuilder from the DynamicProxy library. var proxyBuilder = new DefaultProxyBuilder(); // build a proxy for the IEventProcessor interface. var eventProcessorProxy = proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface( typeof(IEventProcessor), new Type[0], ProxyGenerationOptions.Default); // register the logger for LoggingInterceptor. container.Register(); // register the service that we will intercept. container.Register(); // register the interceptor. container.Register(); // register the built proxy as a decorator. container.RegisterDecorator(eventProcessorProxy); ``` ================================================ FILE: docs/docs/advanced/generics.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Generics This section is about how Stashbox handles various usage scenarios that involve .NET Generic types. Including the registration of open-generic and closed-generic types, [generic decorators](/docs/advanced/decorators#generic-decorators), conditions based on generic constraints, and variance. ## Closed-generics
The registration of a closed-generic type does not differ from registering a simple non-generic service. You have all options available that you saw at the [basic](/docs/guides/basics) and [advanced registration](/docs/guides/advanced-registration) flows.
```cs container.Register, UserValidator>(); IValidator validator = container.Resolve>(); ``` ```cs container.Register(typeof(IValidator), typeof(UserValidator)); object validator = container.Resolve(typeof(IValidator)); ```
## Open-generics The registration of an open-generic type differs from registering a closed-generic one as C# doesn't allow the usage of open-generic types in generic method parameters. We have to get a runtime type from the open-generic type first with `typeof()`.
Open-generic types could help in such scenarios where you have generic interface-implementation pairs with numerous generic parameter variations. The registration of those different versions would look like this:
```cs container.Register, Validator>(); container.Register, Validator>(); container.Register, Validator>(); // and so on... ```
Rather than doing that, you can register your type's generic definition and let Stashbox bind the type parameters for you. When a matching closed [service type](/docs/getting-started/glossary#service-type--implementation-type) is requested, the container will construct an equivalent closed-generic implementation.
```cs container.Register(typeof(IValidator<>), typeof(Validator<>)); // Validator will be returned. IValidator userValidator = container.Resolve>(); // Validator will be returned. IValidator roleValidator = container.Resolve>(); ```
A registered closed-generic type always has priority over an open-generic type at service selection.
```cs container.Register, UserValidator>(); container.Register(typeof(IValidator<>), typeof(Validator<>)); // UserValidator will be returned. IValidator validator = container.Resolve>(); ```
## Generic constraints In the following examples, you can see how the container handles generic constraints during service resolution. Constraints can be used for [conditional resolution](/docs/guides/service-resolution#conditional-resolution) including collection filters. The container chooses `UpdatedEventHandler` because it is the only one that has a constraint satisfied by the requested `UserUpdatedEvent` generic parameter as it's implementing `IUpdatedEvent`. ```cs interface IEventHandler { } // event interfaces interface IUpdatedEvent { } interface ICreatedEvent { } // event handlers class UpdatedEventHandler : IEventHandler where TEvent : IUpdatedEvent { } class CreatedEventHandler : IEventHandler where TEvent : ICreatedEvent { } // event implementation class UserUpdatedEvent : IUpdatedEvent { } using var container = new StashboxContainer(); container.RegisterTypesAs(typeof(IEventHandler<>), new[] { typeof(UpdateEventHandler<>), typeof(CreateEventHandler<>) }); // eventHandler will be UpdatedEventHandler IEventHandler eventHandler = container.Resolve>(); ``` This example shows how the container is filtering out those services from the returned collection that does not satisfy the given generic constraint needed to create the closed generic type. ```cs interface IEventHandler { } // event interfaces interface IUpdatedEvent { } interface ICreatedEvent { } // event handlers class UpdatedEventHandler : IEventHandler where TEvent : IUpdatedEvent { } class CreatedEventHandler : IEventHandler where TEvent : ICreatedEvent { } // event implementation class UserUpdatedEvent : IUpdatedEvent { } using var container = new StashboxContainer(); container.RegisterTypesAs(typeof(IEventHandler<>), new[] { typeof(UpdateEventHandler<>), typeof(CreateEventHandler<>) }); // eventHandlers will contain only UpdatedEventHandler IEnumerable> eventHandlers = container.ResolveAll>(); ``` ## Variance Since .NET Framework 4.0, C# supports [covariance and contravariance](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/) in generic interfaces and delegates and allows implicit conversion of generic type parameters. In this section, we'll focus on variance in generic interfaces. [Here](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/covariance-contravariance/creating-variant-generic-interfaces) you can read more about how to create variant generic interfaces, and the following example will show how you can use them with Stashbox. **Contravariance** only allows argument types that are less derived than that defined by the generic parameters. You can declare a generic type parameter contravariant by using the `in` keyword. ```cs // contravariant generic event handler interface interface IEventHandler { } // event interfaces interface IGeneralEvent { } interface IUpdatedEvent : IGeneralEvent { } // event handlers class GeneralEventHandler : IEventHandler { } class UpdatedEventHandler : IEventHandler { } container.Register, GeneralEventHandler>(); container.Register, UpdatedEventHandler>(); // eventHandlers contain both GeneralEventHandler and UpdatedEventHandler IEnumerable> eventHandlers = container.ResolveAll>(); ``` Despite the fact that only `IEventHandler` implementations were requested, the result contains both `GeneralEventHandler` and `UpdatedEventHandler`. As `TEvent` is declared **contravariant** with the `in` keyword, and `IGeneralEvent` is less derived than `IUpdatedEvent`, `IEventHandler` implementations can be part of `IEventHandler` collections. If we request `IEventHandler`, only `GeneralEventHandler` would be returned, because `IUpdatedEvent` is more derived, so `IEventHandler` implementations are not fit into `IEventHandler` collections. **Covariance** only allows argument types that are more derived than that defined by the generic parameters. You can declare a generic type parameter covariant by using the `out` keyword. ```cs // covariant generic event handler interface interface IEventHandler { } // event interfaces interface IGeneralEvent { } interface IUpdatedEvent : IGeneralEvent { } // event handlers class GeneralEventHandler : IEventHandler { } class UpdatedEventHandler : IEventHandler { } container.Register, GeneralEventHandler>(); container.Register, UpdatedEventHandler>(); // eventHandlers contain both GeneralEventHandler and UpdatedEventHandler IEnumerable> eventHandlers = container.ResolveAll>(); ``` Despite the fact that only `IEventHandler` implementations were requested, the result contains both `GeneralEventHandler` and `UpdatedEventHandler`. As `TEvent` is declared **covariant** with the `out` keyword, and `IUpdatedEvent` is more derived than `IGeneralEvent`, `IEventHandler` implementations can be part of `IEventHandler` collections. If we request `IEventHandler`, only `UpdatedEventHandler` would be returned, because `IGeneralEvent` is less derived, so `IEventHandler` implementations are not fit into `IEventHandler` collections. :::info The check for variant generic types is enabled by default, but it can be turned off via a [container configuration option](/docs/configuration/container-configuration#generic-variance). ::: ================================================ FILE: docs/docs/advanced/special-resolution-cases.md ================================================ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Special resolution cases ## Unknown type resolution When this [feature](/docs/configuration/container-configuration#unknown-type-resolution) is enabled, the container will try to resolve unregistered types by registering them using a pre-defined configuration delegate. Without a registration configuration, the container can resolve only non-interface and non-abstract unknown types. In the following example, the container creates an implicit registration for `Dependency` and injects its instance into `Service`. ```cs class Dependency { } class Service { public Service(Dependency dependency) { } } var container = new StashboxContainer(config => config .WithUnknownTypeResolution()); container.Register(); var service = container.Resolve(); ``` With a registration configuration, you can control how an unknown type's individual registration should behave. You can also react to a service resolution request. In the following example, we tell the container that if it finds an unregistered `IDependency` for the first time, that should be mapped to `Dependency` and have a singleton lifetime. Next time, when the container comes across this service, it will use the registration created at the first request. ```cs interface IDependency { } class Dependency : IDependency { } class Service { public Service(IDependency dependency) { } } var container = new StashboxContainer(config => config .WithUnknownTypeResolution(options => { if(options.ServiceType == typeof(IDependency)) { options.SetImplementationType(typeof(Dependency)) .WithLifetime(Lifetimes.Singleton); } })); container.Register(); var service = container.Resolve(); ``` ## Default value injection When this [feature](/docs/configuration/container-configuration#default-value-injection) is enabled, the container will resolve unknown primitive dependencies with their default value. ```cs class Person { public Person(string name, int age) { } } var container = new StashboxContainer(config => config .WithDefaultValueInjection()); // the name parameter will be null and the age will be 0. var person = container.Resolve(); ``` :::note Unknown reference types are resolved to `null` only in properties and fields. ::: ## Optional value injection Stashbox respects the optional value of each constructor and method argument. ```cs class Person { public Person(string name = null, int age = 54, IContact contact = null) { } } // the name will be null // the age will be 54. // the contact will be null. var person = container.Resolve(); ``` ================================================ FILE: docs/docs/advanced/wrappers-resolvers.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Wrappers & resolvers Stashbox uses so-called *Wrapper* and *Resolver* implementations to handle special resolution requests that none of the [service registrations](/docs/getting-started/glossary#service-registration--registered-service) can fulfill. Functionalities like [wrapper](/docs/advanced/wrappers-resolvers#wrappers) and [unknown type](/docs/advanced/special-resolution-cases#unknown-type-resolution) resolution, [cross-container requests](/docs/advanced/child-containers), [optional](/docs/advanced/special-resolution-cases#optional-value-injection) and [default value](/docs/advanced/special-resolution-cases#default-value-injection) injection are all built with resolvers. ## Pre-defined wrappers & resolvers * `EnumerableWrapper`: Used to resolve a collection of services wrapped in one of the collection interfaces that a .NET `Array` implements. (`IEnumerable<>`, `IList<>`, `ICollection<>`, `IReadOnlyList<>`, `IReadOnlyCollection<>`) * `LazyWrapper`: Used to resolve services [wrapped](/docs/advanced/wrappers-resolvers#lazy) in `Lazy<>`. * `FuncWrapper`: Used to resolve services [wrapped](/docs/advanced/wrappers-resolvers#delegate) in a `Delegate` that has a non-void return type like `Func<>`. * `MetadataWrapper`: Used to resolve services [wrapped](/docs/advanced/wrappers-resolvers#metadata--tuple) in `ValueTuple<,>`, `Tuple<,>`, or `Metadata<,>`. * `KeyValueWrapper`: Used to resolve services [wrapped](/docs/advanced/wrappers-resolvers#keyvaluepair--readonlykeyvalue) in `KeyValuePair<,>` or `ReadOnlyKeyValue<,>`. * `ServiceProviderResolver`: Used to resolve the actual scope as `IServiceProvider` when no other implementation is registered. * `OptionalValueResolver`: Used to resolve optional parameters. * `DefaultValueResolver`: Used to resolve default values. * `ParentContainerResolver`: Used to resolve services that are only registered in one of the parent containers. * `UnknownTypeResolver`: Used to resolve services that are not registered into the container. ## Wrappers Stashbox can implicitly wrap your services into different data structures. All functionalities covered in the [service resolution](/docs/guides/service-resolution) are applied to the wrappers. Every wrapper request starts as a standard resolution; only the result is wrapped in the requested structure.
### Enumerable Stashbox can compose a collection from each implementation registered to a [service type](/docs/getting-started/glossary#service-type--implementation-type). The requested type can be wrapped by any of the collection interfaces that a .NET `Array` implements.
```cs IJob[] jobs = container.Resolve(); IEnumerable jobs = container.Resolve>(); IList jobs = container.Resolve>(); ICollection jobs = container.Resolve>(); IReadOnlyList jobs = container.Resolve>(); IReadOnlyCollection jobs = container.Resolve>(); ```
### Lazy When requesting `Lazy<>`, the container implicitly constructs a new `Lazy<>` instance with a factory delegate as its constructor argument used to instantiate the underlying service.
```cs container.Register(); // new Lazy(() => new DbBackup()) Lazy lazyJob = container.Resolve>(); IJob job = lazyJob.Value; ```
### Delegate When requesting a `Delegate`, the container implicitly creates a factory used to instantiate the underlying service. It's possible to request a delegate that expects some or all of the dependencies as delegate parameters. Parameters are used for sub-dependencies as well, like: `(arg) => new A(new B(arg))` When a dependency is not available as a parameter, it will be resolved from the container directly.
```cs container.Register(); // (conn, logger) => new DbBackup(conn, logger) Func funcOfJob = container .Resolve>(); IJob job = funcOfJob(config["connectionString"], new ConsoleLogger()); ``` ```cs private delegate IJob JobFactory(string connectionString, ILogger logger); container.Register(); var jobDelegate = container.Resolve(); IJob job = jobDelegate(config["connectionString"], new ConsoleLogger()); ```
### Metadata & Tuple With the `.WithMetadata()` registration option, you can attach additional information to a service. To gather this information, you can request the service wrapped in either `Metadata<,>`, `ValueTuple<,>`, or `Tuple<,>`. `Metadata<,>` is a type from the `Stashbox` package, so you might prefer using `ValueTuple<,>` or `Tuple<,>` if you want to avoid referencing Stashbox in certain parts of your project. You can also filter a collection of services by their metadata. Requesting `IEnumerable>` will yield only those services that have the given type of metadata.
```cs container.Register(options => options .WithMetadata("connection-string-to-db")); var jobWithConnectionString = container.Resolve>(); // prints: "connection-string-to-db" Console.WriteLine(jobWithConnectionString.Data); var alsoJobWithConnectionString = container.Resolve>(); // prints: "connection-string-to-db" Console.WriteLine(alsoJobWithConnectionString.Item2); var stillJobWithConnectionString = container.Resolve>(); // prints: "connection-string-to-db" Console.WriteLine(stillJobWithConnectionString.Item2); ``` ```cs container.Register(options => options .WithMetadata("meta-1")); container.Register(options => options .WithMetadata("meta-2")); container.Register(options => options .WithMetadata(5)); // the result is: [Service1, Service2] var servicesWithStringMetadata = container.Resolve[]>(); // the result is: [Service3] var servicesWithIntMetadata = container.Resolve[]>(); ```
:::note Metadata can also be a complex type e.g., an `IDictionary<,>`. ::: :::info When no service found for a particular metadata type, the container throws a [ResolutionFailedException](/docs/diagnostics/validation#resolution-validation). In case of an `IEnumerable<>` request, an empty collection will be returned for a non-existing metadata. :::
### KeyValuePair & ReadOnlyKeyValue With named registration, you can give your service unique identifiers. Requesting a service wrapped in a `KeyValuePair` or `ReadOnlyKeyValue` returns the requested service with its identifier as key. `ReadOnlyKeyValue<,>` is a type from the `Stashbox` package, so you might prefer using `KeyValuePair<,>` if you want to avoid referencing Stashbox in certain parts of your project. Requesting an `IEnumerable>` will return all services of the requested type along their identifiers. When a service don't have an identifier the `Key` will be set to `null`.
```cs container.Register("FirstServiceId"); container.Register("SecondServiceId"); container.Register(); var serviceKeyValue1 = container .Resolve>("FirstServiceId"); // prints: "FirstServiceId" Console.WriteLine(serviceKeyValue1.Key); var serviceKeyValue2 = container .Resolve>("SecondServiceId"); // prints: "SecondServiceId" Console.WriteLine(serviceKeyValue2.Key); // ["FirstServiceId": Service1, "SecondServiceId": Service2, null: Service3 ] var servicesWithKeys = container.Resolve[]>(); ```
:::note Wrappers can be composed e.g., `IEnumerable, string>>>`. ::: ## User-defined wrappers & resolvers You can add support for more wrapper types by implementing the `IServiceWrapper` interface. ```cs class CustomWrapper : IServiceWrapper { // this method is supposed to generate the expression for the given wrapper's // instantiation when it's selected by the container to resolve the actual service. public Expression WrapExpression( TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext) { // produce the expression for the wrapper. } // this method is called by the container to determine whether a // given requested type is wrapped by a supported wrapper type. public bool TryUnWrap(Type type, out Type unWrappedType) { // this is just a reference implementation of // un-wrapping a service from a given wrapper. if (!CanUnWrapServiceType(type)) { unWrappedType = typeof(object); return false; } unWrappedType = UnWrapServiceType(type); return true; } } ``` You can extend the functionality of the container by implementing the `IServiceResolver` interface. ```cs class CustomResolver : IServiceResolver { // called to generate the expression for the given service // when this resolver is selected (through CanUseForResolution()) // to fulfill the request. public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) { var expression = GenerateExpression(); // resolution expression generation. return expression.AsServiceContext(); } public bool CanUseForResolution( TypeInformation typeInfo, ResolutionContext resolutionContext) { // the predicate that determines whether the resolver // is able to resolve the requested service or not. return IsUsableFor(typeInfo); } } ``` Then you can register your custom wrapper or resolver like this: ```cs container.RegisterResolver(new CustomWrapper()); container.RegisterResolver(new CustomResolver()); ``` ## Visiting order Stashbox visits the wrappers and resolvers in the following order to satisfy the actual resolution request: 1. `EnumerableWrapper` 2. `LazyWrapper` 3. `FuncWrapper` 4. `MetadataWrapper` 5. `KeyValueWrapper` 6. **Custom, user-defined wrappers & resolvers** 7. `ServiceProviderResolver` 8. `OptionalValueResolver` 9. `DefaultValueResolver` 10. `ParentContainerResolver` 11. `UnknownTypeResolver` ================================================ FILE: docs/docs/configuration/container-configuration.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Container configuration
The container's constructor has an `Action` parameter used to configure its behavior. The configuration API is fluent, which means you can chain the configuration methods after each other.
```cs var container = new StashboxContainer(options => options .WithDisposableTransientTracking() .WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters) .WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException)); ```
**Re-configuration** of the container is also supported by calling its `.Configure()` method.
```cs var container = new StashboxContainer(); container.Configure(options => options.WithDisposableTransientTracking()); ```
## Default configuration These features are set by default: - [Constructor selection](/docs/configuration/container-configuration#constructor-selection): `Rules.ConstructorSelection.PreferMostParameters` - [Registration behavior](/docs/configuration/container-configuration#registration-behavior): `Rules.RegistrationBehavior.SkipDuplications` - [Default lifetime](/docs/configuration/container-configuration#default-lifetime): `Lifetimes.Transient` ## Tracking disposable transients
With this option, you can enable or disable the tracking of disposable transient objects.
```cs new StashboxContainer(options => options .WithDisposableTransientTracking()); ```
## Auto member-injection With this option, you can enable or disable the auto member-injection without [attributes](/docs/guides/service-resolution#attributes).
### `PropertiesWithPublicSetter` With this flag, the container will perform auto-injection on properties with public setters.
```cs new StashboxContainer(options => options .WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter)); ```
### `PropertiesWithLimitedAccess` With this flag, the container will perform auto-injection on properties even when they don't have a public setter.
```cs new StashboxContainer(options => options .WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)); ```
### `PrivateFields` With this flag, the container will perform auto-injection on private fields too.
```cs new StashboxContainer(options => options .WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PrivateFields)); ```
### Combined rules You can also combine these flags with bitwise logical operators to get a merged ruleset.
```cs new StashboxContainer(options => options .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields | Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter)); ```
#### Member selection filter You can pass your own member selection logic to control which members should be auto injected.
```cs new StashboxContainer(options => options .WithAutoMemberInjection( filter: member => member.Type != typeof(ILogger))); ```
## Required member injection
With this option, you can enable or disable the auto injection of members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword.
```cs new StashboxContainer(options => options .WithRequiredMemberInjection(enabled: false)); ```
:::note The required member injection option is **enabled** by default. ::: ## Constructor selection With this option, you can set the constructor selection rule used to determine which constructor the container should use for instantiation.
### `PreferMostParameters` It prefers the constructor which has the most extended parameter list.
```cs new StashboxContainer(options => options .WithConstructorSelectionRule( Rules.ConstructorSelection.PreferMostParameters)); ```
### `PreferLeastParameters` It prefers the constructor which has the shortest parameter list.
```cs new StashboxContainer(options => options .WithConstructorSelectionRule( Rules.ConstructorSelection.PreferLeastParameters)); ```
## Registration behavior With this option, you can set the actual behavior used when a new service is registered into the container. These options do not affect named registrations.
### `SkipDuplications` The container will skip new registrations when the given [implementation type](/docs/getting-started/glossary#service-type--implementation-type) is already registered.
```cs new StashboxContainer(options => options .WithRegistrationBehavior( Rules.RegistrationBehavior.SkipDuplications)); ```
### `ThrowException` The container throws an [exception](/docs/diagnostics/validation#servicealreadyregisteredexception) when the given [implementation type](/docs/getting-started/glossary#service-type--implementation-type) is already registered.
```cs new StashboxContainer(options => options .WithRegistrationBehavior( Rules.RegistrationBehavior.ThrowException)); ```
### `ReplaceExisting` The container will replace the already [registered service](/docs/getting-started/glossary#service-registration--registered-service) with the given one when they have the same [implementation type](/docs/getting-started/glossary#service-type--implementation-type).
```cs new StashboxContainer(options => options .WithRegistrationBehavior( Rules.RegistrationBehavior.ReplaceExisting)); ```
### `PreserveDuplications` The container will keep registering the new services with the same [implementation type](/docs/getting-started/glossary#service-type--implementation-type).
```cs new StashboxContainer(options => options .WithRegistrationBehavior( Rules.RegistrationBehavior.PreserveDuplications)); ```
## Default lifetime
With this option, you can set the default lifetime used when a service doesn't have a configured one.
```cs new StashboxContainer(options => options .WithDefaultLifetime(Lifetimes.Scoped)); ```
## Lifetime validation
With this option, you can enable or disable the life-span and [root scope](/docs/getting-started/glossary#root-scope) resolution [validation](/docs/diagnostics/validation#lifetime-validation) on the dependency tree.
```cs new StashboxContainer(options => options .WithLifetimeValidation()); ```
## Generic variance
With this option, you can enable or disable the check for [generic covariance and contravariance](/docs/advanced/generics#variance) during the resolution of generic type collections. _This option is enabled by default_.
```cs new StashboxContainer(options => options .WithVariantGenericTypes()); ```
## Empty collection handling
With this option, you can enable or disable the throwing of a `ResolutionFailedException` when no services are found for a collection resolution request. When this feature is disabled _(default)_, the container returns an empty array for those request.
```cs new StashboxContainer(options => options .WithExceptionOverEmptyCollection()); ```
## Conventional resolution
With this option, you can enable or disable conventional resolution, which means the container treats the constructor/method parameter or member names as dependency names used by [named resolution](/docs/getting-started/glossary#named-resolution).
```cs new StashboxContainer(options => options .TreatParameterAndMemberNameAsDependencyName()); ```
## Using named service for un-named requests
With this option, you can enable or disable the selection of named registrations when the resolution request is un-named but with the same type. The `enabledForCollectionRequests` argument controls whether named registrations should be returned for an unnamed collection resolution request. It's **enabled** by default.
```cs new StashboxContainer(options => options .WithNamedDependencyResolutionForUnNamedRequests( enabled: true, enabledForCollectionRequests: true )); ```
## Named service resolution
### `WithUniversalName` Sets the universal name that represents a special name which allows named resolution work for any given name.
```cs new StashboxContainer(options => options .WithUniversalName("Any")); ```
### `WithAdditionalDependencyNameAttribute` Adds an attribute type that is considered a dependency name indicator just like the [`DependencyName` attribute](/docs/guides/service-resolution#attributes).
```cs new StashboxContainer(options => options .WithAdditionalDependencyNameAttribute()); ```
### `WithAdditionalDependencyAttribute` Adds an attribute type that is considered a dependency indicator just like the [`Dependency` attribute](/docs/guides/service-resolution#attributes).
```cs new StashboxContainer(options => options .WithAdditionalDependencyAttribute()); ```
### `WithIgnoreServicesWithUniversalNameForUniversalNamedRequests` Enables or disables the selection of services with `UniversalName` for a universal named resolution request.
```cs new StashboxContainer(options => options .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests()); ```
### `WithForceThrowWhenNamedDependencyIsNotResolvable` Enables or disables throwing a `ResolutionFailedException` when a named dependency is not resolvable. Requests initiated by `.ResolveOrDefault()` may also throw when a named subdependency is not resolvable for example through the `Dependency` attribute.
```cs new StashboxContainer(options => options .WithForceThrowWhenNamedDependencyIsNotResolvable()); ```
## Overriding the exception thrown upon resolution failure
With this option, you can override the default `ResolutionFailedException` type thrown when the service resolution fails. The details of the exception remain the same, only the type gets overridden.
```cs new StashboxContainer(options => options .OverrideResolutionFailedExceptionWith()); ```
## Default value injection
With this option, you can enable or disable the default value injection.
```cs new StashboxContainer(options => options .WithDefaultValueInjection()); ```
## Unknown type resolution
With this option, you can enable or disable the resolution of unregistered types. You can also use a configurator delegate to configure the registrations the container will create from the unknown types.
```cs new StashboxContainer(options => options .WithUnknownTypeResolution(config => config.AsImplementedTypes())); ```
## Custom compiler
With this option, you can set an external expression tree compiler. It can be useful on platforms where the IL generator modules are not available; therefore, the expression compiler in Stashbox couldn't work.
```cs new StashboxContainer(options => options .WithExpressionCompiler( Rules.ExpressionCompilers.MicrosoftExpressionCompiler)); ```
## Re-build singletons in child containers
With this option, you can enable or disable the re-building of singletons in child containers. It allows the child containers to override singleton dependencies in the parent.
```cs new StashboxContainer(options => options .WithReBuildSingletonsInChildContainer()); ```
:::note This feature is not affecting the already built singleton instances in the parent. ::: ================================================ FILE: docs/docs/configuration/registration-configuration.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Registration configuration
Most of the registration methods have an `Action` parameter, enabling several customization options on the given registration. Here are three examples that show how the API's usage looks like. They cover the exact functionalities you've read about in the [basics](/docs/guides/basics) section but are achieved with the options API.
This is how you can use the options API to set a registration's name: ```cs container.Register(options => options .WithName("DbBackup")); ``` It was mentioned in the [Lifetime shortcuts](/docs/guides/basics#lifetime-shortcuts) section, that those methods are only sugars; under the curtain, they are also using this API: ```cs container.Register(options => options .WithLifetime(Lifetimes.Singleton)); ``` An example of how you can register an instance with the options API: ```cs container.Register(options => options .WithInstance(new DbBackup())); ```
The registration configuration API is fluent, which means all option methods can be chained after each other. This provides an easier way to configure complicated registrations.
```cs container.Register(options => options .WithName("DbBackup") .WithLifetime(Lifetimes.Singleton) .WithoutDisposalTracking()); ```
## General options
### `WithName` Sets the name identifier of the registration.
```cs container.Register(config => config .WithName("Console")); ```
### `WithInstance` Sets an existing instance for the registration. Passing true for the `wireUp` parameter means that the container performs member / method injection on the registered instance.
```cs container.Register(options => options .WithInstance(new ConsoleLogger())); ``` ```cs container.Register(options => options .WithInstance(new ConsoleLogger(), wireUp: true)); ```
### `WithoutDisposalTracking` Force disables the disposal tracking on the registration.
```cs container.Register(options => options .WithoutDisposalTracking()); ```
### `WithMetadata` Sets additional metadata for the registration. It's attached to the service upon its resolution through `ValueTuple<,>`, `Tuple<,>`, or `Metadata<,>` wrappers.
```cs container.Register(options => options .WithMetadata(connectionString)); var jobWithConnectionString = container.Resolve>(); Console.WriteLine(jobWithConnectionString.Item2); // prints the connection string. ```
### `WithDynamicResolution` Indicates that the service's resolution should be handled by a dynamic `Resolve()` call on the current `IDependencyResolver` instead of a pre-built instantiation expression.
```cs container.Register(); container.Register(options => options .WithDynamicResolution()); // new DbBackup(currentScope.Resolve()); var job = container.Resolve(); ```
### `HasServiceType` Used to build conditions based on service type in batch/assembly registrations. It determines whether the registration is mapped to the given service type.
```cs container.RegisterAssemblyContaining(configurator: options => { if (options.HasServiceType()) options.WithScopedLifetime(); }); ```
## Initializer / finalizer
### `WithFinalizer` Sets a custom cleanup delegate that will be invoked when the scope / container holding the instance is being disposed.
```cs container.Register(options => options .WithFinalizer(logger => logger .CloseFile())); ```
### `WithInitializer` Sets a custom initializer delegate that will be invoked when the given service is being instantiated.
```cs container.Register(options => options .WithInitializer((logger, resolver) => logger .OpenFile())); ```
## Replace Indicates whether the container should replace an existing registration with the current one (based on [implementation type](/docs/getting-started/glossary#service-type--implementation-type) and name). If there's no existing registration in place, the actual one will be added to the registration list. ```cs container.Register(options => options .ReplaceExisting()); ``` The same as `ReplaceExisting()` except that the container will do the replace only when there's an already [registered service](/docs/getting-started/glossary#service-registration--registered-service) with the same type or name. ```cs container.Register(options => options .ReplaceOnlyIfExists()); ``` ## Multiple services You can read more about binding a registration to multiple services [here](/docs/guides/advanced-registration#binding-to-multiple-services).
### `AsImplementedTypes` The service will be mapped to all of its implemented interfaces and base types.
```cs container.Register(options => options .AsImplementedTypes()); ```
### `AsServiceAlso` Binds the currently configured registration to an additional [service type](/docs/getting-started/glossary#service-type--implementation-type). The registered type must implement or extend the additional [service type](/docs/getting-started/glossary#service-type--implementation-type).
```cs container.Register(options => options .AsServiceAlso() // or .AsServiceAlso(typeof(IRepository))); ```
## Dependency configuration These options allows the same configuration functionality as the [dependency attribute](/docs/guides/service-resolution#attributes). Binds a constructor / method parameter or a property / field to a named registration by the parameter's type. The container will perform a [named resolution](/docs/getting-started/glossary#named-resolution) on the bound dependency. The second parameter used to set the name of the dependency. ```cs container.Register(options => options .WithDependencyBinding(typeof(ILogger), "FileLogger")); ``` Binds a constructor / method parameter or a property / field to a named registration by the parameter's name. The container will perform a [named resolution](/docs/getting-started/glossary#named-resolution) on the bound dependency. The second parameter used to set the name of the dependency. ```cs container.Register(options => options .WithDependencyBinding("logger", "FileLogger")); ``` Marks a member (property / field) as a dependency that should be filled by the container. The second parameter used to set the name of the dependency. ```cs container.Register(options => options .WithDependencyBinding(logger => logger.Logger, "ConsoleLogger")); ``` ## Lifetime You can read more about lifetimes [here](/docs/guides/lifetimes).
### `WithSingletonLifetime` Sets a singleton lifetime for the registration.
```cs container.Register(config => config .WithSingletonLifetime()); ```
### `WithScopedLifetime` Sets a scoped lifetime for the registration.
```cs container.Register(config => config .WithScopedLifetime()); ```
### `WithPerRequestLifetime` Sets the lifetime to `PerRequestLifetime`. This lifetime will create a new instance between resolution requests. Within the request the same instance will be re-used.
```cs container.Register(options => options .WithPerRequestLifetime()); ```
### `WithAutoLifetime` Sets the lifetime to auto lifetime. This lifetime aligns to the lifetime of the resolved service's dependencies. When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.
```cs container.Register(options => options .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); ```
### `WithLifetime` Sets a custom lifetime for the registration.
```cs container.Register(config => config .WithLifetime(new CustomLifetime())); ```
## Conditions You can read more about the concept of conditional resolution [here](/docs/guides/service-resolution#conditional-resolution).
### `WhenHas` Sets an attribute condition for the registration.
```cs container.Register(config => config .WhenHas()); ```
### `WhenResolutionPathHas` Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the target that has the given attribute. This means that only the direct and sub-dependencies of the target type that has the given attribute will get the configured service.
```cs container.Register(config => config // Each direct and sub-dependency of any service that has // a ConsoleAttribute will get FileLogger wherever they // depend on ILogger. .WhenResolutionPathHas()); ```
### `WhenDependantIs` Sets a parent target condition for the registration.
```cs container.Register(config => config .WhenDependantIs()); ```
### `WhenInResolutionPathOf` Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the given target. This means that only the direct and sub-dependencies of the target type will get the configured service.
```cs container.Register(config => config // Each direct and sub-dependency of UserRepository // will get FileLogger wherever they depend on ILogger. .WhenInResolutionPathOf()); ```
### `When` Sets a custom user-defined condition for the registration.
```cs container.Register(config => config .When(typeInfo => typeInfo.ParentType == typeof(UserRepository))); ```
## Constructor selection
### `WithConstructorSelectionRule` Sets the constructor selection rule for the registration.
```cs container.Register(options => options .WithConstructorSelectionRule(...)); ```
#### PreferMostParameters Selects the constructor which has the longest parameter list.
```cs options.WithConstructorSelectionRule( Rules.ConstructorSelection.PreferMostParameters) ```
#### PreferLeastParameters Selects the constructor which has the shortest parameter list.
```cs options.WithConstructorSelectionRule( Rules.ConstructorSelection.PreferLeastParameters) ```
#### Custom You can set your own custom constructor ordering logic.
```cs options.WithConstructorSelectionRule( constructors => { /* custom constructor sorting logic */ }) ```
### `WithConstructorByArgumentTypes` Selects a constructor by its argument types.
```cs container.Register(options => options .WithConstructorByArgumentTypes(typeof(ILogger))); ```
### `WithConstructorByArguments` Selects a constructor by its arguments to use during resolution. These arguments are used to invoke the selected constructor.
```cs container.Register(options => options .WithConstructorByArguments(new ConsoleLogger())); ```
## Property / field Injection
### `WithAutoMemberInjection` Enables the auto member injection and sets the rule for it.
```cs container.Register(options => options .WithAutoMemberInjection(...)); ```
#### PropertiesWithPublicSetter With this flag, the container will perform auto-injection on properties with a public setter.
```cs options.WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter) ```
#### PropertiesWithLimitedAccess With this flag, the container will perform auto-injection on properties which has a non-public setter as well.
```cs options.WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess) ```
#### PrivateFields With this flag, the container will perform auto-injection on private fields too.
```cs options.WithAutoMemberInjection( Rules.AutoMemberInjectionRules.PrivateFields) ```
#### Combined rules As these rules are [bit flags](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/builtin-types/enum#enumeration-types-as-bit-flags), you can use them combined together with bitwise logical operators.
```cs options.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields | Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter) ```
#### Member selection filter You can pass your own member selection logic to control which members should be auto injected.
```cs container.Register(options => options .WithAutoMemberInjection(filter: member => member.Type != typeof(ILogger))); ```
## Required member injection
With this option, you can enable or disable the auto injection of members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword.
```cs container.Register(options => options .WithRequiredMemberInjection(enabled: false)); ```
:::note The required member injection option is **enabled** by default. ::: ## Injection parameters
### `WithInjectionParameters` Sets multiple injection parameters for the registration.
```cs container.Register(options => options .WithInjectionParameters(new KeyValuePair("logger", new ConsoleLogger())); ```
### `WithInjectionParameter` Sets a single injection parameter for the registration.
```cs container.Register(options => options .WithInjectionParameter("logger", new ConsoleLogger()); ```
## Factory You can read more about the concept of factory registration [here](/docs/guides/advanced-registration?id=factory-registration). **WithFactory** - Sets a factory delegate that could take various number of pre-resolved dependencies as parameters and returns the service instance. ```cs // 1 parameter factory container.Register(options => options .WithFactory(logger => new UserRepository(logger)); // 2 parameters factory container.Register(options => options .WithFactory((logger, context) => new UserRepository(logger, context)); // 3 parameters factory container.Register(options => options .WithFactory((logger, context, options) => new UserRepository(logger, context, options)); // 4 parameters factory container.Register(options => options .WithFactory((logger, connection, options, validator) => new UserRepository(logger, connection, options, validator)); // 5 parameters factory container.Register(options => options .WithFactory( (logger, connection, options, validator, permissionManager) => new UserRepository(logger, connection, options, validator, permissionManager)); ``` You can also get the current [dependency resolver](/docs/getting-started/glossary#dependency-resolver) as a pre-resolved parameter: ```cs container.Register(options => options .WithFactory((logger, resolver) => new UserRepository(logger, resolver.Resolve()))); ``` **WithFactory** - Sets a parameter-less factory delegate that returns the service instance. ```cs container.Register(options => options .WithFactory(()) => new UserRepository(new ConsoleLogger())); ``` **WithFactory** - Sets a factory delegate that takes an `IDependencyResolver` as parameter and returns the service instance. ```cs container.Register(options => options .WithFactory(resolver => new UserRepository(resolver.Resolve())); ``` :::info All factory configuration method has an `isCompiledLambda` parameter which should be set to `true` if the passed delegate is compiled from an `Expression` tree. ::: ## Scope definition You can read more about the concept of defined scopes [here](/docs/guides/scopes?id=service-as-scope).
### `InNamedScope` Sets a scope name condition for the registration; it will be used only when a scope with the same name requests it.
```cs container.Register(options => options .InNamedScope("UserRepo")); ```
### `InScopeDefinedBy` Sets a condition for the registration; it will be used only within the scope defined by the given type.
```cs container.Register(options => options .InScopeDefinedBy()); container.Register(options => options .DefinesScope()); ```
### `DefinesScope` This registration is used as a logical scope for it's dependencies. Dependencies registered with `InNamedScope()` with the same name are preferred during resolution. When the `name` is not set, the [service type](/docs/getting-started/glossary#service-type--implementation-type) is used as the name. Dependencies registered with `InScopeDefinedBy()` are selected.
```cs container.Register(options => options .DefinesScope("UserRepo")); // or container.Register(options => options .DefinesScope()); ```
## Decorator specific You can read more about decorators [here](/docs/advanced/decorators).
### `WhenDecoratedServiceIs` Sets a decorated target condition for the registration.
```cs container.RegisterDecorator(options => options .WhenDecoratedServiceIs()); ```
### `WhenDecoratedServiceHas` Sets an attribute condition that the decorated target has to satisfy.
```cs container.RegisterDecorator(options => options .WhenDecoratedServiceHas()); ```
## Unknown registration specific You can read more about unknown type resolution [here](/docs/advanced/special-resolution-cases#unknown-type-resolution).
### `SetImplementationType` Sets the current registration's [implementation type](/docs/getting-started/glossary#service-type--implementation-type).
```cs var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { if (config.ServiceType == typeof(IService)) config.SetImplementationType(typeof(Service)); })); ```
### `Skip` Marks the current unknown type registration as skipped.
```cs var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { if (config.ServiceType == typeof(IService)) config.Skip(); })); ```
================================================ FILE: docs/docs/diagnostics/utilities.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Utilities ## Is registered?
With the `IsRegistered()` function, you can find out whether a service is registered into the container or not. It returns `true` only when the container has a registration with the given type (and name). It only checks the actual container's registrations. For every cases, you should use the `CanResolve()` method.
```cs bool isIJobRegistered = container.IsRegistered(); ``` ```cs bool isIJobRegistered = container.IsRegistered(typeof(IJob)); ``` #### **Named** ```cs bool isIJobRegistered = container.IsRegistered("DbBackup"); ```
## Can resolve?
There might be cases when you are more interested in whether a service is resolvable from the container's actual state rather than finding out whether it's registered. `CanResolve()` returns `true` only when at least one of the following is true: - The requested type is registered in the current or one of the parent containers. - The requested type is a closed generic type, and its open generic definition is registered. - The requested type is a wrapper (`IEnumerable<>`, `Lazy<>`, `Func<>`, `KeyValuePair<,>`, `ReadOnlyKeyValue<,>`, `Metadata<,>`, `ValueTuple<,>`, or `Tuple<,>`), and the underlying type is registered. - The requested type is not registered, but it's resolvable, and [unknown type resolution](/docs/configuration/container-configuration#unknown-type-resolution) is enabled.
```cs bool isIJobResolvable = container.CanResolve(); ``` ```cs bool isIJobResolvable = container.CanResolve(typeof(IJob)); ``` ```cs bool isIJobResolvable = container.CanResolve("DbBackup"); ```
## Get all mappings
You can get all registrations in a key-value pair collection (where the key is the [service type](/docs/getting-started/glossary#service-type--implementation-type) and the value is the actual registration) by calling the `.GetRegistrationMappings()` method.
```cs IEnumerable> mappings = container.GetRegistrationMappings(); ```
## Registration diagnostics
You can get a much more readable version of the registration mappings by calling the `.GetRegistrationDiagnostics()` method. `RegistrationDiagnosticsInfo` has an overridden `.ToString()` method that returns the mapping details formatted in a human-readable form.
```cs container.Register("DbBackupJob"); container.Register(typeof(IEventHandler<>), typeof(EventHandler<>)); IEnumerable diagnostics = container.GetRegistrationDiagnostics(); diagnostics.ForEach(Console.WriteLine); // output: // IJob => DbBackup, name: DbBackupJob // IEventHandler<> => EventHandler<>, name: null ```
================================================ FILE: docs/docs/diagnostics/validation.md ================================================ import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Validation Stashbox validation routines help you detect and solve common misconfiguration issues. You can verify the container's actual state with its `.Validate()` method. It walks through the whole [resolution tree](/docs/getting-started/glossary#resolution-tree) and collects all issues into an `AggregateException`. ## Registration validation During registration, the container validates the passed types and throws the following exceptions when the validation fails. ### InvalidRegistrationException 1. **When the [implementation type](/docs/getting-started/glossary#service-type--implementation-type) is not resolvable.** (it's an interface or an abstract class registered like: `Register()`): ``` The type Namespace.IService could not be resolved. It's probably an interface, abstract class, or primitive type. ``` 2. **When the [implementation type](/docs/getting-started/glossary#service-type--implementation-type) does not implement the [service type](/docs/getting-started/glossary#service-type--implementation-type)**. ``` The type Namespace.MotorCycle does not implement the '[service type](/docs/getting-started/glossary#service-type--implementation-type)' Namespace.ICar. ``` ### ServiceAlreadyRegisteredException **When the given [implementation type](/docs/getting-started/glossary#service-type--implementation-type) is already registered** and the `RegistrationBehavior` [container configuration option](/docs/configuration/container-configuration#registration-behavior) is set to `ThrowException`: ``` The type Namespace.Service is already registered. ``` ## Resolution validation During the [resolution tree's](/docs/getting-started/glossary#resolution-tree) construction, the container continuously checks its actual state to ensure stability. When any of the following issues occur, the container throws a `ResolutionFailedException`. 1. **When a dependency is missing from the [resolution tree](/docs/getting-started/glossary#resolution-tree)**. ```cs class Service { public Service(Dependency dep) { } public Service(Dependency2 dep2) { } } container.Register(); var service = container.Resolve(); ``` This will result in the following exception message: ``` Could not resolve type Namespace.Service. Constructor Void .ctor(Dependency) found with unresolvable parameter: (Namespace.Dependency)dep. Constructor Void .ctor(Dependency2) found with unresolvable parameter: (Namespace.Dependency2)dep2. ``` ```cs class Service { public Dependency Dep { get; set; } } container.Register(options => options.WithDependencyBinding(s => s.Dep)); var service = container.Resolve(); ``` This will show the following message: ``` Could not resolve type Namespace.Service. Unresolvable property: (Namespace.Dependency)Dep. ``` 2. **When the requested type is unresolvable.** E.g., it doesn't have a public constructor. ``` Could not resolve type Namespace.Service. Service is not registered or unresolvable type requested. ``` ## Lifetime validation This validation enforces the following rules. When they are violated, the container throws a `LifetimeValidationFailedException`. 1. **When a scoped service is requested from the [root scope](/docs/getting-started/glossary#root-scope)**. As the [root scope's](/docs/getting-started/glossary#root-scope) lifetime is bound to the container's lifetime, this action unintentionally promotes the scoped service's lifetime to singleton: ``` Resolution of Namespace.Service (ScopedLifetime) from the '[root scope](/docs/getting-started/glossary#root-scope)' is not allowed, that would promote the service's lifetime to a singleton. ``` 2. **When the life-span of a dependency is shorter than its parent's**. It's called [captive dependency](https://blog.ploeh.dk/2014/06/02/captive-dependency/). Every lifetime has a `LifeSpan` value, which determines how long the related service lives. The main rule is that services may not contain dependencies with shorter life spans. E.g., singletons should not depend on scoped services. The only exception is the life span value `0`, which indicates that the related service is state-less and could be injected into any service. These are the `LifeSpan` values of the pre-defined lifetimes: - **Singleton**: 20 - **Scoped**: 10 - **NamedScope**: 10 - **PerRequest**: 0 - **Transient**: 0 In case of a failed validation the exception message would be: ``` The life-span of Namespace.Service (ScopedLifetime|10) is shorter than its direct or indirect parent's Namespace.Dependency (Singleton|20). This could lead to incidental lifetime promotions with longer life-span, it's recommended to double-check your lifetime configurations. ``` ## Circular dependency When the container encounters a circular dependency loop in the [resolution tree](/docs/getting-started/glossary#resolution-tree), it throws a `ResolutionFailedException`. ```cs class Service1 { public Service1(Service2 service2) { } } class Service2 { public Service2(Service1 service1) { } } container.Register(); container.Register(); var service = container.Resolve(); ``` The exception message is: ``` Circular dependency was detected while resolving Namespace.Service1. ``` ## Other exceptions ### CompositionRootNotFoundException This exception pops up when we try to compose an assembly, but it doesn't contain an `ICompositionRoot` implementation. ```cs container.ComposeAssembly(typeof(Service).Assembly); ``` The exception message is: ``` No ICompositionRoot found in the given assembly: {your-assembly-name} ``` ### ConstructorNotFoundException During the registration phase, when you are using the [`WithConstructorByArgumentTypes()`](/docs/configuration/registration-configuration#withconstructorbyargumenttypes) or [`WithConstructorByArguments()`](/docs/configuration/registration-configuration#withconstructorbyarguments) options, you can accidentally point to a non-existing constructor. In that case, the container throws a `ConstructorNotFoundException`. ```cs class Service { public Service(Dependency dep) { } } container.Register(options => options.WithConstructorByArgumentTypes(typeof(string), typeof(int))); ``` The exception message is: ``` Constructor not found for Namespace.Service with the given argument types: System.String, System.Int32. ``` ================================================ FILE: docs/docs/getting-started/glossary.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; # Glossary The following terms and definitions are used in this documentation. ## Service type | Implementation type The *Service type* is usually an interface or an abstract class type used for service resolution or dependency injection. The *Implementation type* is the actual type registered to the *Service type*. A registration maps the *Service type* to an *Implementation type*. The *Implementation type* must implement or extend the *Service type*.
Example where a *Service type* is mapped to an *Implementation type*:
```cs container.Register(); ```
The *Service type* used for requesting a service from the container:
```cs container.Resolve(); // returns Implementation ```
## Service registration | Registered service It's an entity created by Stashbox when a service is registered. The service registration stores required information about how to instantiate the service, e.g., reflected type information, name, lifetime, conditions, and more.
In this example, we are registering a named service. The container will create a service registration entity to store the type mapping and the name. During resolution, the container will find the registration by checking for the *Service type* and the *name*.
```cs // the registration entity will look like: // IService => Implementation, name: Example container.Register("Example"); var service = container.Resolve("Example"); ```
## Injectable dependency
It's a constructor/method argument or a property/field of a registered *Implementation type* that gets evaluated (*injected*) by Stashbox during the service's construction. In this example, `Implementation` has an `IDependency` *injectable dependency* in its constructor.
```cs class Implementation : IService { public Implementation(IDependency dependency) { } } ```
## Resolution tree It's the structural representation of a service's resolution process. It describes the instantiation order of the dependencies required to resolve the desired type. Let's see through an example: ```cs class A { public A(B b, C c) { } } class B { public B(C c, D d) { } } class C { } class D { } ``` When we request the service `A`, the container constructs the following resolution tree based on the dependencies and sub-dependencies. ``` A / \ B C / \ C D ``` The container instantiates those services first that don't have any dependencies. `C` and `D` will be injected into `B`. Then, a new `C` is instantiated (if it's [transient](/docs/guides/lifetimes#transient-lifetime)) and injected into `A` along with the previously created `B`. ## Dependency resolver It's the container itself or the [current scope](/docs/guides/scopes), depending on which was asked to resolve a particular service. They are both implementing Stashbox's `IDependencyResolver` and the .NET framework's `IServiceProvider` interface and can be used for service resolution. :::info Stashbox implicitly injects the [current scope](/docs/guides/scopes) wherever `IDependencyResolver` or `IServiceProvider` is requested. ::: ## Root scope It's the [main scope](/docs/guides/scopes) created inside every container instance. It stores and handles the lifetime of all singletons. It's the base of each subsequent scope created by the container with the `.BeginScope()` method. :::caution [Scoped services](/docs/guides/lifetimes#scoped-lifetime) requested from the container (and not from a [scope](/docs/guides/scopes)) are managed by the root scope. This can lead to issues because their lifetime will effectively switch to singleton. Always be sure that you don't resolve scoped services directly from the container, only from a [scope](/docs/guides/scopes). This case is monitored by the [lifetime](/docs/diagnostics/validation#lifetime-validation) validation rule when it's [enabled](/docs/configuration/container-configuration#lifetime-validation). ::: ## Named resolution
It's a resolution request for a named service. The same applies, when the container sees a dependency in the resolution tree with a name (set by [attributes](/docs/guides/service-resolution#attributes) or [bindings](/docs/guides/service-resolution#dependency-binding)); it will search for a matching [Named registration](/docs/guides/basics#named-registration) to inject.
```cs container.Register("Example"); // the named request. var service = container.Resolve("Example"); ```
## Self registration
It's a service registration that's mapped to itself. This means its service and implementation type is the same.
```cs // equivalent to container.Register(); container.Register(); ```
================================================ FILE: docs/docs/getting-started/introduction.md ================================================ --- title: Introduction --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; Stashbox and its extensions are distributed via [NuGet](https://www.nuget.org/packages?q=stashbox) packages. You can install the package by typing the following into the Package Manager Console: ```powershell Install-Package Stashbox -Version 5.20.0 ``` You can install the package by using the dotnet cli: ```bash dotnet add package Stashbox --version 5.20.0 ``` You can add the package into the package references of your `.csproj`: ```xml ``` ## Usage The general idea behind using Stashbox is that you structure your code with loosely coupled components with the [Dependency Inversion Principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle), [Inversion Of Control](https://en.wikipedia.org/wiki/Inversion_of_control) and [Dependency Injection](https://martinfowler.com/articles/injection.html) in mind. Rather than letting the services instantiate their dependencies inside themselves, inject the dependencies on construction. Also, rather than creating the object hierarchy manually, you can use a Dependency Injection framework that does the work for you. That's why you are here, I suppose. 🙂 To achieve the most efficient usage of Stashbox, you should follow these steps: - At the startup of your application, instantiate a `StashboxContainer`. - Register your services into the container. - [Validate](/docs/diagnostics/validation) the state of the container and the registrations with the `.Validate()` method. *(Optional)* - During the lifetime of the application, use the container to resolve your services. - Create [scopes](/docs/guides/scopes) and use them to resolve your services. *(Optional)* - On application exit, call the container's `.Dispose()` or `.DisposeAsync()` method to clean up the resources. *(Optional)* :::caution You should create only a single instance from `StashboxContainer` (plus child containers if you use them) per application domain. `StashboxContainer` instances are thread-safe. Do not create new container instances continuously, such action will bypass the container's internal delegate cache and could lead to performance degradation. ::: ## How it works? Stashbox builds and maintains a collection of [registered services](/docs/getting-started/glossary#service-registration--registered-service). When a service is requested for resolution, Stashbox starts looking for a matching registration that has the same [service type](/docs/getting-started/glossary#service-type--implementation-type) as the type that was requested. If it finds one, the container initiates a scan on the [implementation type's](/docs/getting-started/glossary#service-type--implementation-type) available constructors and selects the one with the most arguments it knows how to resolve by matching argument types to other registrations. When every constructor argument has a companion registration, Stashbox jumps to the first one and continues the same scanning operation. This process is repeated until every [injectable dependency](/docs/getting-started/glossary#injectable-dependency) has a matching registration in the [resolution tree](/docs/getting-started/glossary#resolution-tree). At the end of the process, Stashbox will have each dependency node built-up in a hierarchical object structure to instantiate the initially requested service object. ## Example Let's see a quick example. We have three services `DbBackup`, `MessageBus` and `ConsoleLogger`. `DbBackup` has a dependency on `IEventBroadcaster` (implemented by `MessageBus`) and `ILogger` (implemented by `ConsoleLogger`), `MessageBus` also depending on an `ILogger`: ```cs public interface IJob { void DoTheJob(); } public interface ILogger { void Log(string message); } public interface IEventBroadcaster { void Broadcast(IEvent @event); } public class ConsoleLogger : ILogger { public void Log(string message) => Console.WriteLine(message); } public class MessageBus : IEventBroadcaster { private readonly ILogger logger; public MessageBus(ILogger logger) { this.logger = logger; } void Broadcast(IEvent @event) { this.logger.Log($"Sending event to bus: {@event.Name}"); // Do the actual event broadcast. } } public class DbBackup : IJob { private readonly ILogger logger; private readonly IEventBroadcaster eventBroadcaster; public DbBackup(ILogger logger, IEventBroadcaster eventBroadcaster) { this.logger = logger; this.eventBroadcaster = eventBroadcaster; } public void DoTheJob() { this.logger.Log("Backing up!"); // Do the actual backup. this.eventBroadcaster.Broadcast(new DbBackupCompleted()); } } ``` :::info By depending only on interfaces, you decouple your services from concrete implementations. This gives you the flexibility of a more comfortable implementation replacement and also isolates your components from each other. For example, unit testing benefits a lot from the possibility of replacing real implementations with mocks. ::: The example above configured with Stashbox in a Console Application: ```cs using Stashbox; using System; namespace Example { public class Program { private static readonly IStashboxContainer container; static Program() { // 1. Create container container = new StashboxContainer(); // 2. Register your services container.RegisterSingleton(); container.Register(); container.Register(); // 3. Validate the configuration. container.Validate(); } static void Main(string[] args) { // 4. Resolve and use your service var job = container.Resolve(); job.DoTheJob(); } } } ``` ================================================ FILE: docs/docs/getting-started/overview.md ================================================ --- title: Overview --- # Stashbox [![Appveyor Build Status](https://img.shields.io/appveyor/build/pcsajtai/stashbox?logo=appveyor&logoColor=white)](https://ci.appveyor.com/project/pcsajtai/stashbox/branch/master) [![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/z4kn4fein/stashbox/linux-macOS-CI.yml?logo=GitHub&branch=master)](https://github.com/z4kn4fein/stashbox/actions/workflows/linux-macOS-CI.yml) [![NuGet Downloads](https://img.shields.io/nuget/dt/Stashbox?label=nuget)](https://www.nuget.org/packages/Stashbox) [![Sonar Tests](https://img.shields.io/sonar/tests/z4kn4fein_stashbox?compact_message&logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sonar Coverage](https://img.shields.io/sonar/coverage/z4kn4fein_stashbox?logo=SonarCloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sonar Quality Gate](https://img.shields.io/sonar/quality_gate/z4kn4fein_stashbox?logo=sonarcloud&server=https%3A%2F%2Fsonarcloud.io)](https://sonarcloud.io/project/overview?id=z4kn4fein_stashbox) [![Sourcelink](https://img.shields.io/badge/sourcelink-enabled-brightgreen.svg)](https://github.com/dotnet/sourcelink) Stashbox is a lightweight, fast, and portable dependency injection framework for .NET-based solutions. It encourages the building of loosely coupled applications and simplifies the construction of hierarchical object structures. It can be integrated easily with .NET Core, Generic Host, ASP.NET, Xamarin, and many other applications. These are the latest available stable and pre-release versions: Github (stable) | NuGet (stable) | NuGet (daily) --- | --- | --- [![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://img.shields.io/nuget/v/Stashbox)](https://www.nuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/) ## Core attributes - 🚀 Fast, thread-safe, and lock-free operations. - ⚡️ Easy-to-use Fluent configuration API. - ♻️ Small memory footprint. - 🔄 Tracks the dependency tree for cycles. - 🚨 Detects and warns about misconfigurations. - 🔥 Gives fast feedback on registration/resolution issues. ## Supported platforms - .NET 5+ - .NET Standard 2.0+ - .NET Framework 4.5+ - Mono - Universal Windows Platform - Xamarin (Android/iOS/Mac) - Unity ## Contact & support - Create a [GitHub issue](https://github.com/z4kn4fein/stashbox/issues) for bug reports and feature requests. - Start a [GitHub discussion](https://github.com/z4kn4fein/stashbox/discussions) for your questions and ideas. - Add a ⭐️ [on GitHub](https://github.com/z4kn4fein/stashbox) to support the project! ## License This project is licensed under the [MIT license](https://github.com/z4kn4fein/stashbox/blob/master/LICENSE). ================================================ FILE: docs/docs/guides/advanced-registration.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Advanced registration This section is about Stashbox's further configuration options, including the registration configuration API, the registration of factory delegates, multiple implementations, batch registrations, the concept of the [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/), and many more. :::info This section won't cover all the available options of the registrations API, but you can find them [here](/docs/configuration/registration-configuration). ::: ## Factory registration
You can bind a factory delegate to a registration that the container will invoke directly to instantiate your service. You can use parameter-less and custom parameterized delegates as a factory. [Here](/docs/configuration/registration-configuration#factory) is the list of all available options. You can also get the current [dependency resolver](/docs/getting-started/glossary#dependency-resolver) as a delegate parameter to resolve any additional dependencies required for the service construction.
```cs container.Register(options => options .WithFactory(() => new ConsoleLogger()); // the container uses the factory for instantiation. IJob job = container.Resolve(); ``` ```cs container.Register(options => options .WithFactory(logger => new DbBackup(logger)); // the container uses the factory for instantiation. IJob job = container.Resolve(); ``` ```cs container.Register(options => options .WithFactory(resolver => new DbBackup(resolver.Resolve())); // the container uses the factory for instantiation. IJob job = container.Resolve(); ```
Delegate factories are useful when your service's instantiation is not straight-forward for the container, like when it depends on something that is not available at resolution time. E.g., a connection string.
```cs container.Register(options => options .WithFactory(logger => new DbBackup(Configuration["DbConnectionString"], logger)); ```
### Factories with parameter overrides Stashbox can implicitly [wrap](/docs/advanced/wrappers-resolvers#delegate) your service in a `Delegate` and lets you pass parameters that can override your service's dependencies. Moreover, you can register your own custom delegate that the container will resolve when you request your service wrapped in a `Delegate`.
```cs container.RegisterFunc((connectionString, resolver) => new DbBackup(connectionString, resolver.Resolve())); Func backupFactory = container.Resolve>(); IJob dbBackup = backupFactory(Configuration["ConnectionString"]); ``` ```cs container.RegisterFunc((connectionString, resolver) => new DbBackup(connectionString, resolver.Resolve())); Delegate backupFactory = container.ResolveFactory(typeof(IJob), parameterTypes: new[] { typeof(string) }); IJob dbBackup = backupFactory.DynamicInvoke(Configuration["ConnectionString"]); ```
If a service has multiple constructors, the container visits those first, that has matching parameters passed to the factory, with respecting the additional [constructor selection rules](/docs/configuration/registration-configuration#constructor-selection).
```cs class Service { public Service(int number) { } public Service(string text) { } } container.Register(); // create the factory with an int input parameter. var func = constainer.Resolve>(); // the constructor with the int param // is used for instantiation. var service = func(2); ```
### Consider this before using the resolver parameter inside a factory Delegate factories are a black-box for the container. It doesn't have control over what's happening inside a delegate, which means when you resolve additional dependencies with the [dependency resolver](/docs/getting-started/glossary#dependency-resolver) parameter, they could easily bypass the [lifetime](/docs/diagnostics/validation#lifetime-validation) and [circular dependency](/docs/diagnostics/validation#circular-dependency) validations. Fortunately, you have the option to keep them validated anyway with parameterized factory delegates. #### Delegates with dependencies passed as parameters Rather than using the [dependency resolver](/docs/getting-started/glossary#dependency-resolver) parameter inside the factory, let the container inject the dependencies into the delegate as parameters. This way, the [resolution tree's](/docs/getting-started/glossary#resolution-tree) integrity remains stable because no service resolution happens inside the black-box, and each parameter is validated.
```cs interface IEventProcessor { } class EventProcessor : IEventProcessor { public EventProcessor(ILogger logger, IEventValidator validator) { } } container.Register(); container.Register(); container.Register(options => options // Ilogger and IEventValidator instances are injected // by the container at resolution time, so they will be // validated against circular and captive dependencies. .WithFactory((logger, validator) => new EventProcessor(logger, validator)); // the container resolves ILogger and IEventValidator first, then // it passes them to the factory as delegate parameters. IEventProcessor processor = container.Resolve(); ```
### Accessing the currently resolving type in factories To access the currently resolving type in factory delegates, you can set the `TypeInformation` type as an input parameter of the factory. The `TypeInformation` holds every reflected context information about the currently resolving type. This can be useful when the resolution is, e.g., in an open generic context, and we want to know which closed generic variant is requested.
```cs interface IService { } class Service : IService { } container.Register(typeof(IService<>), typeof(Service<>), options => options.WithFactory(typeInfo => { // typeInfo.Type here holds the actual type like // IService based on the resolution request below. })); container.Resolve>(); ```
## Multiple implementations
As we previously saw in the [Named registration](/docs/guides/basics#named-registration) topic, Stashbox allows you to have multiple implementations bound to a particular [service type](/docs/getting-started/glossary#service-type--implementation-type). You can use names to distinguish them, but you can also access them by requesting a typed collection using the [service type](/docs/getting-started/glossary#service-type--implementation-type). :::note The returned collection is in the same order as the services were registered. Also, to request a collection, you can use any interface implemented by an array. :::
```cs container.Register(); container.Register(); container.Register(); ``` ```cs // jobs contain all three services in registration order. IEnumerable jobs = container.ResolveAll(); ``` ```cs // jobs contain all three services in registration order. IJob[] jobs = container.Resolve(); ``` ```cs // jobs contain all three services in registration order. IEnumerable jobs = container.Resolve>(); ``` ```cs // jobs contain all three services in registration order. IList jobs = container.Resolve>(); ``` ```cs // jobs contain all three services in registration order. ICollection jobs = container.Resolve>(); ```
When you have multiple implementations registered to a service, a request to the [service type](/docs/getting-started/glossary#service-type--implementation-type) without a name will return the **last registered implementation**. :::info Not only names can be used to distinguish registrations, [conditions](/docs/guides/service-resolution#conditional-resolution), [named scopes](/docs/guides/scopes#named-scopes), and [metadata](/docs/advanced/wrappers-resolvers#metadata--tuple) can also influence the results. :::
```cs container.Register(); container.Register(); container.Register(); // job will be the ImageProcess. IJob job = container.Resolve(); ```
## Binding to multiple services
When you have a service that implements multiple interfaces, you have the option to bind its registration to all or some of those additional interfaces or base types. Suppose we have the following class declaration: ```cs class DbBackup : IJob, IScheduledJob { public DbBackup() { } } ```
```cs container.Register(options => options .AsServiceAlso()); IJob job = container.Resolve(); // DbBackup IScheduledJob job = container.Resolve(); // DbBackup DbBackup job = container.Resolve(); // error, not found ``` ```cs container.Register(options => options .AsImplementedTypes()); IJob job = container.Resolve(); // DbBackup IScheduledJob job = container.Resolve(); // DbBackup DbBackup job = container.Resolve(); // DbBackup ```
## Batch registration
You have the option to register multiple services in a single registration operation. **Filters (optional):** First, the container will use the *implementation filter* action to select only those types from the collection we want to register. When we have those, the container will execute the *service filter* on their implemented interfaces and base classes to select which [service type](/docs/getting-started/glossary#service-type--implementation-type) they should be mapped to. :::note Framework types like `IDisposable` are excluded from being considered as a [service type](/docs/getting-started/glossary#service-type--implementation-type) by default. ::: :::tip You can use the registration configuration API to configure individual registrations. :::
This example will register three types to all their implemented interfaces, extended base classes, and to themselves ([self registration](/docs/getting-started/glossary#self-registration)) without any filter: ```cs container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }); IEnumerable jobs = container.ResolveAll(); // 2 items ILogger logger = container.Resolve(); // ConsoleLogger IJob job = container.Resolve(); // StorageCleanup DbBackup backup = container.Resolve(); // DbBackup ``` In this example, we assume that `DbBackup` and `StorageCleanup` are implementing `IDisposable` besides `IJob` and also extending a `JobBase` abstract class. ```cs container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }, // implementation filter, only those implementations that implements IDisposable impl => typeof(IDisposable).IsAssignableFrom(impl), // service filter, register them to base classes only (impl, service) => service.IsAbstract && !service.IsInterface); IEnumerable jobs = container.ResolveAll(); // 0 items IEnumerable jobs = container.ResolveAll(); // 2 items ILogger logger = container.Resolve(); // error, not found DbBackup backup = container.Resolve(); // DbBackup ``` This example ignores the [self registrations](/docs/getting-started/glossary#self-registration) completely: ```cs container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }, registerSelf: false); IEnumerable jobs = container.ResolveAll(); // 2 items ILogger logger = container.Resolve(); // ConsoleLogger DbBackup backup = container.Resolve(); // error, not found ConsoleLogger logger = container.Resolve(); // error, not found ``` This example will configure all registrations mapped to `ILogger` as `Singleton`: ```cs container.RegisterTypes(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }, configurator: options => { if (options.HasServiceType()) options.WithSingletonLifetime(); }); ILogger logger = container.Resolve(); // ConsoleLogger ILogger newLogger = container.Resolve(); // the same ConsoleLogger IEnumerable jobs = container.ResolveAll(); // 2 items ```
Another type of service filter is the `.RegisterTypesAs()` method, which registers only those types that implements the `T` [service type](/docs/getting-started/glossary#service-type--implementation-type). :::note This method also accepts an implementation filter and a registration configurator action like `.RegisterTypes()`. ::: :::caution `.RegisterTypesAs()` doesn't create [self registrations](/docs/getting-started/glossary#self-registration) as it only maps the implementations to the given `T` [service type](/docs/getting-started/glossary#service-type--implementation-type). :::
```cs container.RegisterTypesAs(new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }); IEnumerable jobs = container.ResolveAll(); // 2 items ILogger logger = container.Resolve(); // error, not found IJob job = container.Resolve(); // StorageCleanup DbBackup backup = container.Resolve(); // error, not found ``` ```cs container.RegisterTypesAs(typeof(IJob), new[] { typeof(DbBackup), typeof(ConsoleLogger), typeof(StorageCleanup) }); IEnumerable jobs = container.ResolveAll(); // 2 items ILogger logger = container.Resolve(); // error, not found IJob job = container.Resolve(); // StorageCleanup DbBackup backup = container.Resolve(); // error, not found ```
## Assembly registration
The batch registration API *(filters, registration configuration action, self-registration)* is also usable for registering services from given assemblies. In this example, we assume that the same three services we used in the [batch registration](#batch-registration) section are in the same assembly. :::info The container also detects and registers open-generic definitions (when applicable) from the supplied type collection. You can read about [open-generics here](/docs/advanced/generics#open-generics). :::
```cs container.RegisterAssembly(typeof(DbBackup).Assembly, // service filter, register to interfaces only serviceTypeSelector: (impl, service) => service.IsInterface, registerSelf: false, configurator: options => options.WithoutDisposalTracking() ); IEnumerable jobs = container.ResolveAll(); // 2 items IEnumerable jobs = container.ResolveAll(); // 0 items ILogger logger = container.Resolve(); // ConsoleLogger DbBackup backup = container.Resolve(); // error, not found ``` ```cs container.RegisterAssemblies(new[] { typeof(DbBackup).Assembly, typeof(JobFromAnotherAssembly).Assembly }, // service filter, register to interfaces only serviceTypeSelector: (impl, service) => service.IsInterface, registerSelf: false, configurator: options => options.WithoutDisposalTracking() ); IEnumerable jobs = container.ResolveAll(); // 2 items IEnumerable jobs = container.ResolveAll(); // 0 items ILogger logger = container.Resolve(); // ConsoleLogger DbBackup backup = container.Resolve(); // error, not found ``` ```cs container.RegisterAssemblyContaining( // service filter, register to interfaces only serviceTypeSelector: (impl, service) => service.IsInterface, registerSelf: false, configurator: options => options.WithoutDisposalTracking() ); IEnumerable jobs = container.ResolveAll(); // 2 items IEnumerable jobs = container.ResolveAll(); // 0 items ILogger logger = container.Resolve(); // ConsoleLogger DbBackup backup = container.Resolve(); // error, not found ```
## Composition root
The [Composition Root](https://blog.ploeh.dk/2011/07/28/CompositionRoot/) is an entry point where all services required to make a component functional are wired together. Stashbox provides an `ICompositionRoot` interface that can be used to define an entry point for a given component or even for an entire assembly. You can wire up your *composition root* implementation with `ComposeBy()`, or you can let the container find and execute all available *composition root* implementations within an assembly. :::note Your `ICompositionRoot` implementation also can have dependencies that the container will resolve. :::
```cs class ExampleRoot : ICompositionRoot { public ExampleRoot(IDependency rootDependency) { } public void Compose(IStashboxContainer container) { container.Register(); container.Register(); } } ``` ```cs // compose a single root. container.ComposeBy(); ``` ```cs // compose every root in the given assembly. container.ComposeAssembly(typeof(IServiceA).Assembly); ``` ```cs // compose a single root with dependency override. container.ComposeBy(new CustomRootDependency()); ```
## Injection parameters
If you have pre-evaluated dependencies you'd like to inject at resolution time, you can set them as injection parameters during registration. :::note Injection parameter names are matched to constructor arguments or field/property names. :::
```cs container.Register(options => options .WithInjectionParameter("logger", new ConsoleLogger()) .WithInjectionParameter("eventBroadcaster", new MessageBus()); // the injection parameters will be passed to DbBackup's constructor. IJob backup = container.Resolve(); ```
## Initializer / finalizer
The container provides specific extension points to let you react to lifetime events of an instantiated service. For this reason, you can specify *Initializer* and *Finalizer* delegates. The *finalizer* is called upon the service's [disposal](/docs/guides/scopes#disposal), and the *initializer* is called upon the service's construction.
```cs container.Register(options => options // delegate that called right after instantiation. .WithInitializer((logger, resolver) => logger.OpenFile()) // delegate that called right before the instance's disposal. .WithFinalizer(logger => logger.CloseFile())); ```
================================================ FILE: docs/docs/guides/basics.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Basic usage This section is about the basics of Stashbox's API. It will give you a good starting point for more advanced topics described in the following sections. Stashbox provides several methods that enable registering services, and we'll go through the most common scenarios with code examples. ## Default registration
Stashbox allows registration operations via the `Register()` methods. During registration, the container checks whether the [service type](/docs/getting-started/glossary#service-type--implementation-type) is assignable from the [implementation type](/docs/getting-started/glossary#service-type--implementation-type) and if not, the container throws an [exception](/docs/diagnostics/validation#registration-validation). Also, when the implementation is not resolvable, the container throws the same [exception](/docs/diagnostics/validation#registration-validation). The example registers `DbBackup` to be returned when `IJob` is requested.
```cs container.Register(); IJob job = container.Resolve(); // throws an exception because ConsoleLogger doesn't implement IJob. container.Register(); // throws an exception because IJob is not a valid implementation. container.Register(); ``` ```cs container.Register(typeof(IJob), typeof(DbBackup)); object job = container.Resolve(typeof(IJob)); // throws an exception because ConsoleLogger doesn't implement IJob. container.Register(typeof(IJob), typeof(ConsoleLogger)); // throws an exception because IJob is not a valid implementation. container.Register(typeof(IJob), typeof(IJob)); ```
You can register a service to itself without specifying a [service type](/docs/getting-started/glossary#service-type--implementation-type), only the implementation ([self registration](/docs/getting-started/glossary#self-registration)). In this case, the given implementation is considered the [service type](/docs/getting-started/glossary#service-type--implementation-type) and must be used to request the service (`DbBackup` in the example).
```cs container.Register(); DbBackup backup = container.Resolve(); ``` ```cs container.Register(typeof(DbBackup)); object backup = container.Resolve(typeof(DbBackup)); ```
The container's API is fluent, which means you can chain the calls on its methods after each other.
```cs var job = container.Register() .Register() .Resolve(); ```
## Named registration
The example shows how you can bind more implementations to a [service type](/docs/getting-started/glossary#service-type--implementation-type) using names for identification. The same name must be used to resolve the named service. :::note The name is an `object` type. :::
```cs container.Register("DbBackup"); container.Register("StorageCleanup"); IJob cleanup = container.Resolve("StorageCleanup"); ``` ```cs container.Register(typeof(IJob), typeof(DbBackup), "DbBackup"); container.Register(typeof(IJob), typeof(StorageCleanup), "StorageCleanup"); object cleanup = container.Resolve(typeof(IJob), "StorageCleanup"); ```
You can also get each service that share the same name by requesting an `IEnumerable<>` or using the `ResolveAll()` method with the `name` parameter.
```cs container.Register("StorageJobs"); container.Register("StorageJobs"); container.Register(); // jobs will be [DbBackup, StorageCleanup]. IEnumerable jobs = container.Resolve>("StorageJobs"); ```
## Instance registration
With instance registration, you can provide an already created external instance to use when the given [service type](/docs/getting-started/glossary#service-type--implementation-type) is requested. Stashbox automatically handles the [disposal](/docs/guides/scopes#disposal) of the registered instances, but you can turn this feature off with the `withoutDisposalTracking` parameter. When an `IJob` is requested, the container will always return the external instance.
```cs var job = new DbBackup(); container.RegisterInstance(job); // resolvedJob and job are the same. IJob resolvedJob = container.Resolve(); ``` ```cs var job = new DbBackup(); container.RegisterInstance(job, typeof(IJob)); // resolvedJob and job are the same. object resolvedJob = container.Resolve(typeof(IJob)); ``` ```cs var job = new DbBackup(); container.RegisterInstance(job, "DbBackup"); // resolvedJob and job are the same. IJob resolvedJob = container.Resolve("DbBackup"); ``` ```cs var job = new DbBackup(); container.RegisterInstance(job, withoutDisposalTracking: true); // resolvedJob and job are the same. IJob resolvedJob = container.Resolve(); ```
The instance registration API allows the batched registration of different instances.
```cs container.RegisterInstances(new DbBackup(), new StorageCleanup()); IEnumerable jobs = container.ResolveAll(); ```
## Re-mapping
With re-map, you can bind new implementations to a [service type](/docs/getting-started/glossary#service-type--implementation-type) and delete old registrations in one action. :::caution When there are multiple registrations mapped to a [service type](/docs/getting-started/glossary#service-type--implementation-type), `.ReMap()` will replace all of them with the given [implementation type](/docs/getting-started/glossary#service-type--implementation-type). If you want to replace only one specific service, use the `.ReplaceExisting()` [configuration option](/docs/configuration/registration-configuration#replace). :::
```cs container.Register(); container.Register(); // jobs contain all two jobs IEnumerable jobs = container.ResolveAll(); container.ReMap(); // jobs contains only the SlackMessageSender jobs = container.ResolveAll(); ``` ```cs container.Register(typeof(IJob), typeof(DbBackup)); container.Register(typeof(IJob), typeof(StorageCleanup)); // jobs contain all two jobs IEnumerable jobs = container.ResolveAll(typeof(IJob)); container.ReMap(typeof(IJob), typeof(SlackMessageSender)); // jobs contains only the SlackMessageSender jobs = container.ResolveAll(typeof(IJob)); ``` ## Wiring up
Wiring up is similar to [Instance registration](#instance-registration) except that the container will perform property/field injection (if configured so and applicable) on the registered instance during resolution.
```cs container.WireUp(new DbBackup()); IJob job = container.Resolve(); ``` ```cs container.WireUp(new DbBackup(), typeof(IJob)); object job = container.Resolve(typeof(IJob)); ```
## Lifetime shortcuts
A service's lifetime indicates how long its instance will live and which re-using policy should be applied when it gets injected. This example shows how you can use the registration API's shortcuts for lifetimes. These are just sugars, and there are more ways explained in the [lifetimes](/docs/guides/lifetimes) section. :::info The `DefaultLifetime` is [configurable](/docs/guides/lifetimes#default-lifetime). :::
When no lifetime is specified, the service will use the container's `DefaultLifetime`, which is `Transient` by default. ```cs container.Register(); IJob job = container.Resolve(); ``` A service with `Singleton` lifetime will be instantiated once and reused during the container's lifetime. ```cs container.RegisterSingleton(); IJob job = container.Resolve(); ``` The `Scoped` lifetime behaves like a `Singleton` within a [scope](/docs/guides/scopes). A scoped service is instantiated once and reused during the scope's whole lifetime. ```cs container.RegisterScoped(); IJob job = container.Resolve(); ```
================================================ FILE: docs/docs/guides/lifetimes.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Lifetimes Lifetime management controls how long a service's instances will live (from instantiation to [disposal](/docs/guides/scopes#disposal)) and how they will be reused between resolution requests. :::info Choosing the right lifetime helps you avoid [captive dependencies](/docs/diagnostics/validation#lifetime-validation). ::: ## Default lifetime
When you are not specifying a lifetime during registration, Stashbox will use the default lifetime. By default, it's set to [Transient](#transient-lifetime), but you can override it with the `.WithDefaultLifetime()` [container configuration option](/docs/configuration/container-configuration#default-lifetime). You can choose either from the pre-defined lifetimes defined on the `Lifetimes` static class or use a [custom lifetime](#custom-lifetime).
```cs var container = new StashboxContainer(options => options .WithDefaultLifetime(Lifetimes.Transient)); ``` ```cs var container = new StashboxContainer(options => options .WithDefaultLifetime(Lifetimes.Singleton)); ``` ```cs var container = new StashboxContainer(options => options .WithDefaultLifetime(Lifetimes.Scoped)); ```
## Transient lifetime
A new instance is created for each resolution request. If a transient is referred by multiple consumers in the same [resolution tree](/docs/getting-started/glossary#resolution-tree), each will get a new instance.
```cs container.Register(options => options .WithLifetime(Lifetimes.Transient)); ```
:::info Transient services are not tracked for disposal by default, but this feature can be turned on with the `.WithDisposableTransientTracking()` [container configuration option](/docs/configuration/container-configuration#tracking-disposable-transients). When it's enabled, the current [scope](/docs/guides/scopes) on which the resolution request was initiated takes the responsibility to track and dispose transient services. ::: ## Singleton lifetime
A single instance is created and reused for each resolution request and injected into each consumer. :::note Singleton services are disposed when the container ([root scope](/docs/getting-started/glossary#root-scope)) is being disposed. :::
```cs container.Register(options => options .WithLifetime(Lifetimes.Singleton)); ``` ```cs container.RegisterSingleton(); ```
## Scoped lifetime
A new instance is created for each [scope](/docs/guides/scopes), which will be returned for every resolution request initiated on the given scope. It's like a singleton lifetime within a scope. :::note Scoped services are disposed when their scope is being disposed. :::
```cs container.Register(options => options .WithLifetime(Lifetimes.Scoped)); using var scope = container.BeginScope(); IJob job = scope.Resolve(); ``` ```cs container.RegisterScoped(); using var scope = container.BeginScope(); IJob job = scope.Resolve(); ```
## Named scope lifetime
It is the same as scoped lifetime, except the given service will be selected only when a scope with the same name initiates the resolution request. You can also let a service [define](/docs/guides/scopes#service-as-scope) its own named scope. During registration, this scope can be referred to by its name upon using a named scope lifetime.
```cs container.Register(options => options .InNamedScope("DbScope")); using var scope = container.BeginScope("DbScope"); IJob job = scope.Resolve(); ``` ```cs container.Register(options => options .DefinesScope()); ontainer.Register(options => options .InScopeDefinedBy()); // the executor will begin a new scope within itself // when it gets resolved and DbBackup will be selected // and attached to that scope instead. using var scope = container.BeginScope(); DbJobExecutor executor = scope.Resolve(); ``` ```cs container.Register(options => options .DefinesScope("DbScope")); ontainer.Register(options => options .InNamedScope("DbScope")); // the executor will begin a new scope within itself // when it gets resolved and DbBackup will be selected // and attached to that scope instead. using var scope = container.BeginScope(); DbJobExecutor executor = scope.Resolve(); ```
:::note Services with named scope lifetime are disposed when the related named scope is being disposed. ::: ## Per-request lifetime
The requested service will be reused within the whole resolution request. A new instance is created for each individual request .
```cs container.Register(options => options .WithPerRequestLifetime()); ```
## Auto lifetime
The requested service's lifetime will align to the lifetime of its dependencies. When the requested service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary.
```cs container.Register(options => options .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); ```
If the requested service has auto lifetime with a scoped boundary and it has only transient dependencies, it'll inherit their transient lifetime.
```cs container.Register(); container.Register(options => options .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); // job has transient lifetime. var job = container.Resolve(); ```
When there's a dependency with higher lifespan than the given boundary, the requested service will get the boundary lifetime.
```cs container.RegisterSingleton(); container.Register(options => options .WithAutoLifetime(Lifetimes.Scoped /* boundary lifetime */)); // job has scoped lifetime. var job = container.Resolve(); ```
## Custom lifetime If you'd like to use a custom lifetime, you can create your implementation by inheriting either from `FactoryLifetimeDescriptor` or from `ExpressionLifetimeDescriptor`, depending on how do you want to manage the service instances. - **ExpressionLifetimeDescriptor**: With this, you can build your lifetime with the expression form of the service instantiation. ```cs class CustomLifetime : ExpressionLifetimeDescriptor { protected override Expression ApplyLifetime( Expression expression, // The expression which describes the service creation ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type requestedType) { // Lifetime managing functionality } } ``` - **FactoryLifetimeDescriptor**: With this, you can build your lifetime based on a pre-compiled factory delegate used for service instantiation. ```cs class CustomLifetime : FactoryLifetimeDescriptor { protected override Expression ApplyLifetime( Func factory, // The factory used for service creation ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Type requestedType) { // Lifetime managing functionality } } ``` Then you can use your lifetime like this: ```cs container.Register(options => options.WithLifetime(new CustomLifetime())); ``` ================================================ FILE: docs/docs/guides/scopes.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Scopes A scope is Stashbox's implementation of the unit-of-work pattern; it encapsulates a given unit used to resolve and store instances required for a given work. When a scoped service is resolved or injected, the scope ensures that it gets instantiated only once within the scope's lifetime. When the work is finished, the scope cleans up the resources by disposing each tracked disposable instance. A web application is a fair usage example for scopes as it has a well-defined execution unit that can be bound to a scope - the HTTP request. Every request could have its unique scope attached to the request's lifetime. When a request ends, the scope gets closed, and all the scoped instances will be disposed. ## Creating a scope
You can create a scope from the container by calling its `.BeginScope()` method. Scopes can be **nested**, which means you can create sub-scopes from existing ones with their `.BeginScope()` method. Scoped service instances are not shared across parent and sub-scope relations. Nested scopes can be **attached to their parent's lifetime**, which means when a parent gets disposed all child scopes attached to it will be disposed. Scopes are `IDisposable`; they track all `IDisposable` instances they resolved. Calling their `Dispose()` method or wrapping them in `using` statements is a crucial part of their service's lifetime management.
```cs container.RegisterScoped(); // create the scope with using so it'll be auto disposed. using (var scope = container.BeginScope()) { IJob job = scope.Resolve(); IJob jobAgain = scope.Resolve(); // job and jobAgain are created in the // same scope, so they are the same instance. } ``` ```cs container.RegisterScoped(); using (var parent = container.BeginScope()) { IJob job = parent.Resolve(); IJob jobAgain = parent.Resolve(); // job and jobAgain are created in the // same scope, so they are the same instance. // create a sub-scope. using var sub = parent.BeginScope(); IJob subJob = sub.Resolve(); // subJob is a new instance created in the sub-scope, // differs from either job and jobAgain. } ``` ```cs container.RegisterScoped(); var parent = container.BeginScope(); var sub = parent.BeginScope(attachToParent: true); // sub will also be disposed with the scope. scope.Dispose(); ```
## Named scopes
There might be cases where you don't want to use a service globally across every scope, only in specific ones. For this reason, you can differentiate specific scope groups from other scopes with a **name**. You can set a service's lifetime to [named scope lifetime](/docs/guides/lifetimes#named-scope-lifetime) initialized with the **scope's name** to mark it usable only for that named scope. ```cs container.Register(options => options.InNamedScope("DbScope")); container.Register(options => options.InNamedScope("DbScope")); container.Register(options => options.InNamedScope("DbSubScope")); container.Register(options => options.InNamedScope("StorageScope")); ``` :::note Services with named scope lifetime are **shared across parent and sub-scope relations**. ::: If you request a name-scoped service from an un-named scope, you'll get an error or no result (depending on the configuration) because those services are selectable only by named scopes with a matching name.
```cs using (var dbScope = container.BeginScope("DbScope")) { // DbBackup and DbCleanup will be returned. IEnumerable jobs = dbScope.ResolveAll(); // create a sub-scope of dbScope. using var sub = dbScope.BeginScope(); // DbBackup and DbCleanup will be returned from the named parent scope. IEnumerable jobs = sub.ResolveAll(); // create a named sub-scope. using var namedSub = dbScope.BeginScope("DbSubScope"); // DbIndexRebuild will be returned from the named sub-scope. IEnumerable jobs = namedSub.ResolveAll(); } using (var storageScope = container.BeginScope("StorageScope")) { // StorageCleanup will be returned. IJob job = storageScope.Resolve(); } // create a common scope without a name. using (var unNamed = container.BeginScope()) { // empty result as there's no service registered without named scope. IEnumerable jobs = unNamed.ResolveAll(); // throws an exception because there's no unnamed service registered. IJob job = unNamed.Resolve(); } ```
## Service as scope
You can configure a service to behave like a nested named scope. At the resolution of this kind of service, a new dedicated named scope is created implicitly for managing the service's dependencies. With this feature, you can organize your dependencies around logical groups (named scopes) instead of individual services. Using `InScopeDefinedBy()`, you can bind services to a defined scope without giving it a name. In this case, the defining service's [implementation type](/docs/getting-started/glossary#service-type--implementation-type) is used for naming the scope. :::note The lifetime of the defined scope is attached to the current scope that was used to create the service. :::
```cs container.Register(options => options .DefinesScope("DbBackupScope")); container.Register(options => options .InNamedScope("DbBackupScope")); container.Register(); var scope = container.BeginScope(); // DbBackup will create a named scope with the name "DbBackupScope". // the named scope will select ConsoleLogger as it's // bound to the named scope's identifier. IJob job = scope.Resolve(); // this will dispose the implicitly created named scope by DbBackup. scope.Dispose(); ``` ```cs container.Register(options => options .DefinesScope()); container.Register(options => options .InScopeDefinedBy()); container.Register(); var scope = container.BeginScope(); // DbBackup will create a named scope with the name typeof(DbBackup). // the named scope will select ConsoleLogger as it's // bound to the named scope's identifier. IJob job = scope.Resolve(); // this will dispose the implicitly created named scope by DbBackup. scope.Dispose(); ```
## Put instance to a scope
You can add an already instantiated service to a scope. The instance's lifetime will be tracked by the given scope.
```cs using var scope = container.BeginScope(); scope.PutInstanceInScope(new DbBackup()); ```
You can disable the tracking by passing `true` for the `withoutDisposalTracking` parameter. In this case, only the strong reference to the instance is dropped when the scope is disposed.
```cs using var scope = container.BeginScope(); scope.PutInstanceInScope(new DbBackup(), withoutDisposalTracking: true); ```
You can also give your instance a name to use it like a [named registration](/docs/guides/basics#named-registration):
```cs using var scope = container.BeginScope(); scope.PutInstanceInScope(new DbBackup(), false, name: "DbBackup"); ```
:::note Instances put to a scope will take precedence over existing registrations with the same [service type](/docs/getting-started/glossary#service-type--implementation-type). ::: ## Disposal
The currently resolving scope tracks services that implement either `IDisposable` or `IAsyncDisposable`. This means that when the scope is disposed, all the tracked disposable instances will be disposed with it. :::note Disposing the container will dispose all the singleton instances and their dependencies. :::
```cs using (var scope = container.BeginScope()) { var disposable = scope.Resolve(); } // 'disposable' will be disposed when // the using statement ends. ``` ```cs var scope = container.BeginScope(); var disposable = scope.Resolve(); // 'disposable' will be disposed with the scope. scope.Dispose(); ```
You can disable the disposal tracking on a [service registration](/docs/getting-started/glossary#service-registration--registered-service) with the `.WithoutDisposalTracking()` option.
```cs container.Register(options => options.WithoutDisposalTracking()); ```
### Async disposal As the container and its scopes implement the `IAsyncDisposable` interface, you can dispose them asynchronously when they are used in an `async` context. Calling `DisposeAsync` disposes both `IDisposable` and `IAsyncDisposable` instances; however, calling `Dispose` only disposes `IDisposable` instances.
```cs await using (var scope = container.BeginScope()) { var disposable = scope.Resolve(); } // 'disposable' will be disposed asynchronously // when the using statement ends. ``` ```cs var scope = container.BeginScope(); var disposable = scope.Resolve(); // 'disposable' will be disposed asynchronously with the scope. await scope.DisposeAsync(); ```
### Finalizer delegate During [service registration](/docs/getting-started/glossary#service-registration--registered-service), you can set a custom finalizer delegate that will be invoked at the service's disposal.
```cs container.Register(options => options.WithFinalizer(backup => backup.CloseDbConnection())); ```
================================================ FILE: docs/docs/guides/service-resolution.md ================================================ import CodeDescPanel from '@site/src/components/CodeDescPanel'; import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; # Service resolution When you have all your components registered and configured adequately, you can resolve them from the container or a [scope](/docs/guides/scopes) by requesting their [service type](/docs/getting-started/glossary#service-type--implementation-type). During a service's resolution, the container walks through the entire [resolution tree](/docs/getting-started/glossary#resolution-tree) and instantiates all dependencies required for the service construction. When the container encounters any violations of [these rules](/docs/diagnostics/validation#resolution-validation) *(circular dependencies, missing required services, lifetime misconfigurations)* during the walkthrough, it lets you know that something is wrong by throwing a specific exception. ## Injection patterns
**Constructor injection** is the *primary dependency injection pattern*. It encourages the organization of dependencies to a single place - the constructor. Stashbox, by default, uses the constructor that has the most parameters it knows how to resolve. This behavior is configurable through [constructor selection](/docs/configuration/registration-configuration#constructor-selection). [Property/field injection](/docs/configuration/registration-configuration#property-field-injection) is also supported in cases where constructor injection is not applicable. Members defined with C# 11's [`required`](https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/required) keyword are automatically injected by the container. This behavior can be controlled with [registration](/docs/configuration/registration-configuration#required-member-injection) or [container](/docs/configuration/container-configuration#required-member-injection) configuration options :::info [Constructor selection](/docs/configuration/container-configuration#constructor-selection) and [property/field injection](/docs/configuration/container-configuration#auto-member-injection) is also configurable container-wide. :::
```cs class DbBackup : IJob { private readonly ILogger logger; private readonly IEventBroadcaster eventBroadcaster; public DbBackup(ILogger logger, IEventBroadcaster eventBroadcaster) { this.logger = logger; this.eventBroadcaster = eventBroadcaster; } } container.Register(); container.Register(); container.Register(); // resolution using the available constructor. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public ILogger Logger { get; set; } public IEventBroadcaster EventBroadcaster { get; set; } public DbBackup() { } } container.Register(); container.Register(); // registration of service with auto member injection. container.Register(options => options.WithAutoMemberInjection()); // resolution will inject the properties. IJob job = container.Resolve(); ```
:::caution It's a common mistake to use the *property/field injection* only to disencumber the constructor from having too many parameters. That's a code smell and also violates the [Single-responsibility principle](https://en.wikipedia.org/wiki/Single-responsibility_principle). If you recognize these conditions, you should consider splitting your class into multiple smaller units rather than adding an extra property-injected dependency. ::: ## Attributes
Attributes can give you control over how Stashbox selects dependencies for a service's resolution. **Dependency attribute**: - **On a constructor/method parameter**: used with the *name* property, it works as a marker for [named resolution](/docs/getting-started/glossary#named-resolution). - **On a property/field**: first, it enables *auto-injection* on the marked property/field (even if it wasn't configured at registration explicitly), and just as with the method parameter, it allows [named resolution](/docs/getting-started/glossary#named-resolution). **DependencyName attribute**: a parameter marked with this attribute will get the related service's dependency name. **InjectionMethod attribute**: marks a method to be called when the requested service is instantiated.
```cs class DbBackup : IJob { private readonly ILogger logger; public DbBackup([Dependency("Console")]ILogger logger) { this.logger = logger; } } container.Register("Console"); container.Register("File"); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { [Dependency("Console")] public ILogger Logger { get; set; } public DbBackup() { } } container.Register("Console"); container.Register("File"); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public string Name { get; set; } public DbBackup([DependencyName] string name) { } } container.Register("Backup"); IJob job = container.Resolve(); // name is "Backup". var name = job.Name; ``` ```cs class DbBackup : IJob { [InjectionMethod] public void Initialize([Dependency("Console")]ILogger logger) { this.logger.Log("Initializing."); } } container.Register("Console"); container.Register("File"); container.Register(); // the container will call DbBackup's Initialize method. IJob job = container.Resolve(); ```
:::caution Attributes provide a more straightforward configuration, but using them also tightens the bond between your application and Stashbox. If you consider this an issue, you can use the [dependency binding](/docs/guides/service-resolution#dependency-binding) API or [your own attributes](/docs/guides/service-resolution#using-your-own-attributes). ::: ### Using your own attributes
There's an option to extend the container's dependency finding mechanism with your own attributes. - **Additional Dependency attributes**: you can use the [`.WithAdditionalDependencyAttribute()`](/docs/configuration/container-configuration#withadditionaldependencyattribute) container configuration option to let the container know that it should watch for additional attributes besides the built-in [`Dependency`](/docs/guides/service-resolution#attributes) attribute upon building up the [resolution tree](/docs/getting-started/glossary#resolution-tree). - **Additional DependencyName attributes**: you can use the [`.WithAdditionalDependencyNameAttribute()`](/docs/configuration/container-configuration#withadditionaldependencynameattribute) container configuration option to use additional dependency name indicator attributes besides the built-in [`DependencyName`](/docs/guides/service-resolution#attributes) attribute.
```cs class DbBackup : IJob { [CustomDependency("Console")] public ILogger Logger { get; set; } public DbBackup() { } } var container = new StashboxContainer(options => options .WithAdditionalDependencyAttribute()); container.Register("Console"); container.Register("File"); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public string Name { get; set; } public DbBackup([CustomName] string name) { } } var container = new StashboxContainer(options => options .WithAdditionalDependencyNameAttribute()); container.Register("Backup"); IJob job = container.Resolve(); // name is "Backup". var name = job.Name; ```
## Dependency binding
The same dependency configuration functionality as attributes, but without attributes. - **Binding to a parameter**: the same functionality as the [`Dependency`](/docs/guides/service-resolution#attributes) attribute on a constructor or method parameter, enabling [named resolution](/docs/getting-started/glossary#named-resolution). - **Binding to a property/field**: the same functionality as the [`Dependency`](/docs/guides/service-resolution#attributes) attribute, enabling the injection of the given property/field. :::info There are further dependency binding options [available](/docs/configuration/registration-configuration#dependency-configuration) on the registration configuration API. :::
```cs class DbBackup : IJob { public DbBackup(ILogger logger) { } } container.Register("Console"); container.Register("File"); // registration of service with the dependency binding. container.Register(options => options .WithDependencyBinding("logger", "Console")); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public ILogger Logger { get; set; } } container.Register("Console"); container.Register("File"); // registration of service with the member injection. container.Register(options => options .WithDependencyBinding("Logger", "Console")); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ```
## Conventional resolution
When you enable conventional resolution, the container treats member and method parameter names as their dependency identifier. It's like an implicit dependency binding on every class member. First, you have to enable conventional resolution through the configuration of the container: ```cs new StashboxContainer(options => options .TreatParameterAndMemberNameAsDependencyName()); ``` :::note The container will attempt a [named resolution](/docs/getting-started/glossary#named-resolution) on each dependency based on their parameter or property/field name. :::
```cs class DbBackup : IJob { public DbBackup( // the parameter name identifies the dependency. ILogger consoleLogger) { } } container.Register("consoleLogger"); container.Register("fileLogger"); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { // the property name identifies the dependency. public ILogger ConsoleLogger { get; set; } } container.Register("ConsoleLogger"); container.Register("FileLogger"); // registration of service with auto member injection. container.Register(options => options .WithAutoMemberInjection()); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ```
## Conditional resolution
Stashbox can resolve a particular dependency based on its context. This context is typically the reflected type of dependency, its usage, and the type it gets injected into. - **Attribute**: you can filter on constructor, method, property, or field attributes to select the desired dependency for your service. In contrast to the `Dependency` attribute, this configuration doesn't tie your application to Stashbox because you use your own attributes. - **Parent type**: you can filter on what type the given service is injected into. - **Resolution path**: similar to the parent type and attribute condition but extended with inheritance. You can set that the given service is only usable in a type's resolution path. This means that each direct and sub-dependency of the selected type must use the provided service as a dependency. - **Custom**: with this, you can build your own selection logic based on the given contextual type information.
```cs class ConsoleAttribute : Attribute { } class DbBackup : IJob { public DbBackup([Console]ILogger logger) { } } container.Register(options => options // resolve only when the injected parameter, // property or field has the 'Console' attribute .WhenHas()); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public DbBackup(ILogger logger) { } } container.Register(options => options // inject only when we are // currently resolving DbBackup OR StorageCleanup. .WhenDependantIs() .WhenDependantIs()); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public DbBackup(IStorage storage) { } } class FileStorage : IStorage { public FileStorage(ILogger logger) { } } container.Register(options => options // inject only when we are in the // resolution path of DbBackup .WhenInResolutionPathOf()); container.Register(); container.Register(); // the container will select ConsoleLogger for FileStorage // because they are injected into DbBackup. IJob job = container.Resolve(); ``` ```cs class DbBackup : IJob { public DbBackup(ILogger logger) { } } container.Register(options => options // inject only when we are // currently resolving DbBackup. .When(typeInfo => typeInfo.ParentType.Equals(typeof(DbBackup)))); container.Register(); // the container will resolve DbBackup with ConsoleLogger. IJob job = container.Resolve(); ``` ```cs class DbJobsExecutor : IJobsExecutor { public DbBackup(IEnumerable jobs) { } } container.Register(options => options .WhenDependantIs()); container.Register(options => options .WhenDependantIs()); ontainer.Register(); container.Register(); // jobsExecutor will get DbBackup and DbCleanup within a collection. IJobsExecutor jobsExecutor = container.Resolve(); ```
The specified conditions are behaving like filters when a **collection** is requested. When you use the same conditional option multiple times, the container will evaluate them **with OR** logical operator. :::tip [Here](/docs/configuration/registration-configuration#conditions) you can find each condition related registration option. ::: ## Optional resolution
In cases where it's not guaranteed that a service is resolvable, either because it's not registered or any of its dependencies are missing, you can attempt an optional resolution using the `ResolveOrDefault()` method. When the resolution attempt fails, it will return `null` (or `default` in case of value types).
```cs // returns null when the resolution fails. IJob job = container.ResolveOrDefault(); // throws ResolutionFailedException when the resolution fails. IJob job = container.Resolve(); ``` ```cs // returns null when the resolution fails. object job = container.ResolveOrDefault(typeof(IJob)); // throws ResolutionFailedException when the resolution fails. object job = container.Resolve(typeof(IJob)); ```
## Dependency overrides
At resolution time, you can override a service's dependencies by passing an `object[]` to the `Resolve()` method. ```cs class DbBackup : IJob { public DbBackup(ILogger logger) { } } ```
```cs DbBackup backup = container.Resolve( dependencyOverrides: new object[] { new ConsoleLogger() }); ``` ```cs object backup = container.Resolve(typeof(DbBackup), dependencyOverrides: new object[] { new ConsoleLogger() }); ```
To get more control over your overrides (like giving them name to allow [named resolution](/docs/getting-started/glossary#named-resolution)), you can use the `Override` built-in wrapper. ```cs class DbBackup : IJob { public DbBackup([Dependency("console")]ILogger logger) { } } ```
```cs DbBackup backup = container.Resolve( dependencyOverrides: new object[] { Override.Of(new ConsoleLogger(), "console"), }); ``` ```cs object backup = container.Resolve(typeof(DbBackup), dependencyOverrides: new object[] { Override.Of(typeof(ILogger), new ConsoleLogger(), "console"), }); ```
## Activation
You can use the container's `.Activate()` method when you only want to build up an instance from a type on the fly without registration. It allows dependency overriding with `object` arguments and performs property/field/method injection (when configured). It works like `Activator.CreateInstance()` except that Stashbox supplies the dependencies.
```cs // use dependency injected by container. DbBackup backup = container.Activate(); // override the injected dependency. DbBackup backup = container.Activate(new ConsoleLogger()); ``` ```cs // use dependency injected by container. object backup = container.Activate(typeof(DbBackup)); // override the injected dependency. object backup = container.Activate(typeof(DbBackup), new ConsoleLogger()); ```
### Build-up With the `.BuildUp()` method, you can do the same *on the fly* post-processing (property/field/method injection) on already constructed instances. :::caution `.BuildUp()` won't register the given instance into the container. :::
```cs class DbBackup : IJob { public ILogger Logger { get; set; } } DbBackup backup = new DbBackup(); // the container fills the Logger property. container.BuildUp(backup); ```
================================================ FILE: docs/docusaurus.config.js ================================================ // @ts-check // @ts-ignore import { themes } from 'prism-react-renderer'; const prismLightTheme = themes.github; /** @type {import('@docusaurus/types').Config} */ const config = { title: 'Stashbox', tagline: 'A lightweight, fast, and portable .NET DI framework.', url: 'https://z4kn4fein.github.io', baseUrl: '/stashbox', onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', favicon: 'img/favicon.ico', organizationName: 'z4kn4fein', projectName: 'stashbox', trailingSlash: false, i18n: { defaultLocale: 'en', locales: ['en'], }, presets: [ [ 'classic', /** @type {import('@docusaurus/preset-classic').Options} */ ({ docs: { sidebarPath: require.resolve('./sidebars.js'), showLastUpdateAuthor: true, showLastUpdateTime: true, sidebarCollapsible: true, editUrl: 'https://github.com/z4kn4fein/stashbox/edit/master/docs', }, theme: { customCss: require.resolve('./src/css/custom.scss'), }, gtag: { trackingID: 'G-HLNT9WV1HH' } }), ], ], plugins: [ 'docusaurus-plugin-sass', ], themeConfig: /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ ({ navbar: { hideOnScroll: true, title: 'Stashbox', logo: { alt: 'Stashbox logo', src: 'img/icon.png', }, items: [ { type: 'dropdown', position: 'left', label: 'Extensions', items: [ { type: 'html', className: 'navbar-title', value: '.NET Core', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#aspnet-core', label: 'ASP.NET Core', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#multitenant', label: 'ASP.NET Core Multitenant', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#testing', label: 'ASP.NET Core Testing', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#net-generic-host', label: '.NET Generic Host', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions-dependencyinjection#servicecollection-based-applications', label: 'ServiceCollection Extensions', }, { type: 'html', value: '', }, { type: 'html', className: 'navbar-title', value: 'ASP.NET', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-web-webapi', label: 'Web API', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-web-mvc', label: 'MVC', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-signalr', label: 'SignalR', }, { type: 'html', value: '', }, { type: 'html', className: 'navbar-title', value: 'OWIN', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-owin', label: 'OWIN', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-webapi-owin', label: 'Web API', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-signalr-owin', label: 'SignalR', }, { type: 'html', value: '', }, { type: 'html', className: 'navbar-title', value: 'Other', }, { href: 'https://github.com/z4kn4fein/stashbox-extensions/tree/main/src/stashbox-hangfire', label: 'Hangfire', }, { href: 'https://github.com/z4kn4fein/stashbox-mocking', label: 'Mocking', }, ] }, { type: 'dropdown', position: 'left', label: 'Benchmarks', items: [ { href: 'https://danielpalme.github.io/IocPerformance', label: 'Performance', }, ] }, { position: 'left', label: 'Changelog', href: 'https://github.com/z4kn4fein/stashbox/releases' }, { type: 'custom-icon', icon: 'nuget', position: 'right', className: 'nav-icon', href: 'https://www.nuget.org/packages/Stashbox' }, { type: 'custom-icon', icon: 'github', position: 'right', className: 'nav-icon', href: 'https://github.com/z4kn4fein/stashbox' }, { type: 'custom-separator', position: 'right', }, ], }, footer: { style: 'dark', links: [ { title: 'GUIDES', items: [ { label: 'Basic usage', to: 'docs/guides/basics' }, { label: 'Advanced registration', to: 'docs/guides/advanced-registration' }, { label: 'Service resolution', to: 'docs/guides/service-resolution' }, { label: 'Lifetimes', to: 'docs/guides/lifetimes' }, { label: 'Scopes', to: 'docs/guides/scopes' }, ], }, { title: 'ADVANCED', items: [ { label: 'Generics', to: 'docs/advanced/generics' }, { label: 'Decorators', to: 'docs/advanced/decorators' }, { label: 'Wrappers & resolvers', to: 'docs/advanced/wrappers-resolvers' }, { label: 'Child containers', to: 'docs/advanced/child-containers' }, { label: 'Special resolution cases', to: 'docs/advanced/special-resolution-cases' }, ], } ], copyright: `Copyright © ${new Date().getFullYear()} Peter Csajtai. Built with Docusaurus.`, }, docs: { sidebar: { hideable: true, }, }, prism: { theme: prismLightTheme, additionalLanguages: [ 'csharp', 'powershell', 'bash', ], }, colorMode: { defaultMode: 'light', disableSwitch: false, respectPrefersColorScheme: true, }, algolia: { appId: 'CYYLE77D6F', apiKey: '70fdb3ec7ec00e65922f35e5a5e35562', indexName: 'stashbox' }, }), }; async function createConfig() { const darkTheme = (await import('./src/utils/prismDark.mjs')).default; // @ts-expect-error: it exists config.themeConfig.prism.darkTheme = darkTheme; return config; } module.exports = createConfig; ================================================ FILE: docs/package.json ================================================ { "name": "website", "version": "0.0.0", "private": true, "scripts": { "docusaurus": "docusaurus", "start": "docusaurus start", "build": "docusaurus build", "swizzle": "docusaurus swizzle", "deploy": "docusaurus deploy", "clear": "docusaurus clear", "serve": "docusaurus serve", "write-translations": "docusaurus write-translations", "write-heading-ids": "docusaurus write-heading-ids" }, "dependencies": { "@docusaurus/core": "3.9.2", "@docusaurus/preset-classic": "3.9.2", "@mdx-js/react": "^3.0.0", "clsx": "^2.0.0", "docusaurus-plugin-sass": "^0.2.6", "prism-react-renderer": "^2.1.0", "react": "^18.2.0", "react-dom": "^18.2.0", "sass": "^1.69.5" }, "devDependencies": { "@docusaurus/module-type-aliases": "3.0.0" }, "browserslist": { "production": [ ">0.5%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "engines": { "node": ">=20.0" } } ================================================ FILE: docs/sidebars.js ================================================ /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ const sidebars = { docs: [ { type: 'category', label: 'Get started', collapsed: false, items: [ 'getting-started/overview', 'getting-started/introduction', 'getting-started/glossary', ], }, { type: 'category', label: 'Guides', collapsed: false, items: [ 'guides/basics', 'guides/advanced-registration', 'guides/service-resolution', 'guides/lifetimes', 'guides/scopes', ], }, { type: 'category', label: 'Configuration', collapsed: false, items: [ 'configuration/registration-configuration', 'configuration/container-configuration', ], }, { type: 'category', label: 'Advanced', collapsed: false, items: [ 'advanced/generics', 'advanced/decorators', 'advanced/wrappers-resolvers', 'advanced/child-containers', 'advanced/special-resolution-cases', ], }, { type: 'category', label: 'Diagnostics', collapsed: false, items: [ 'diagnostics/validation', 'diagnostics/utilities', ], }, ] }; module.exports = sidebars; ================================================ FILE: docs/src/components/CodeDescPanel/index.tsx ================================================ import React from 'react'; import styles from './styles.module.scss'; export default function CodeDescPanel({children}) { let notNullChildren = React.Children.toArray(children).filter(c => c); return (
{notNullChildren[0]}
{notNullChildren[1]}
); } ================================================ FILE: docs/src/components/CodeDescPanel/styles.module.scss ================================================ .codeDescContainer { width: 100%; display: inline-block; } .desc { float: left; max-width: 45%; width: 45%; padding-right: 10px; } .example { width: 55%; max-width: 55%; padding-left: 10px; display: inline-block; } @media screen and (max-width: 996px) { .desc { float: none; max-width: 100%; width: 100%; padding-right: 0px; } .example { width: 100%; max-width: 100%; padding-left: 0px; display: block; } } ================================================ FILE: docs/src/components/NavbarItems/SeparatorNavbarItem/index.tsx ================================================ import React from 'react'; import clsx from 'clsx'; import styles from './styles.module.scss'; export default function SeparatorNavbarItem({mobile, ...props}): JSX.Element { return
; } ================================================ FILE: docs/src/components/NavbarItems/SeparatorNavbarItem/styles.module.scss ================================================ .separator { height: 1.7rem; width: 1px; background-color: var(--ifm-navbar-link-color); opacity: .2; margin: 0 12px 0 8px; } @media screen and (max-width: 996px) { .separator { display: none; } } ================================================ FILE: docs/src/components/NavbarItems/SvgNavbarItem/index.tsx ================================================ import useBaseUrl from '@docusaurus/useBaseUrl'; import clsx from 'clsx'; import React from 'react'; import SvgIcon from '../../SvgIcon'; import styles from './styles.module.scss'; export default function SvgNavbarItem({icon, href, mobile, ...props}): JSX.Element { return ( ); } ================================================ FILE: docs/src/components/NavbarItems/SvgNavbarItem/styles.module.scss ================================================ .icon_container { display: flex; align-items: center; } .icon { color: var(--ifm-navbar-link-color); width: 24px; height: 24px; transition: opacity .2s; display: flex; align-items: center; } .icon:hover { opacity: .7; } .link { padding: 0 10px; } ================================================ FILE: docs/src/components/SvgIcon/index.tsx ================================================ import React from 'react'; import GitHubSvg from '@site/static/img/github.svg'; import ApiSvg from '@site/static/img/api.svg'; import GitterSvg from '@site/static/img/gitter.svg'; import SlackSvg from '@site/static/img/slack.svg'; import NuGetSvg from '@site/static/img/nuget.svg'; export default function SvgIcon({icon, ...props}): JSX.Element { return ( { 'github': , 'api': , 'gitter': , 'slack': , 'nuget': , }[icon] || null ); } ================================================ FILE: docs/src/css/custom.scss ================================================ /** * Any CSS included here will be global. The classic template * bundles Infima by default. Infima is a CSS framework designed to * work well for content-centric websites. */ @use 'sidebar'; @use 'toc'; @use 'pagination'; @use 'tab'; @use 'search'; :root { --ifm-color-primary: #3385b5; --ifm-color-primary-dark: #2e78a3; --ifm-color-primary-darker: #2b719a; --ifm-color-primary-darkest: #245d7f; --ifm-color-primary-light: #3892c7; --ifm-color-primary-lighter: #4197ca; --ifm-color-primary-lightest: #5ca6d1; --ifm-menu-color: var(--ifm-color-content-secondary); --menu-secondary-color: rgb(83, 83, 95); --base-contrast-background-color: var(--ifm-color-primary); --base-contrast-background-hover-color: var(--ifm-color-primary-dark); @media (min-width: 1840px) { --ifm-container-width-xl: 1440px; } } [data-theme='dark'] { --ifm-color-primary: #43c8ff; --ifm-color-primary-dark: #23bfff; --ifm-color-primary-darker: #13baff; --ifm-color-primary-darkest: #009fe1; --ifm-color-primary-light: #63d1ff; --ifm-color-primary-lighter: #73d6ff; --ifm-color-primary-lightest: #a4e4ff; --menu-secondary-color: rgb(192, 192, 199); --base-contrast-background-color: var(--ifm-color-primary-darkest); --base-contrast-background-hover-color: #0079ad; h2 { border-bottom: 1px solid rgba(122, 122, 122, 0.3); } } .theme-doc-sidebar-container .button--outline { border: 0 !important; background-color: var(--ifm-hover-overlay); margin-right: 1px; } .theme-doc-sidebar-container div[role='button'] { background-color: var(--ifm-hover-overlay); } .dropdown__link svg { opacity: .5; width: 10px; height: 10px; } .navbar-title { font-size: .875rem; padding: 0.2rem 0.2rem; font-weight: 500; } .dropdown-separator { margin: 0.3rem 0; opacity: .3; } h1, h2 { font-weight: 600; } h2 { border-bottom: 1px solid rgba(165, 165, 165, 0.3); padding-bottom: 5px; } .dropdown__link { color: var(--menu-secondary-color); font-weight: 400; } .button--mid { --ifm-button-size-multiplier: 1.1; } @media screen and (max-width: 460px) { .button--mid { --ifm-button-size-multiplier: 1; } } ================================================ FILE: docs/src/css/pagination.scss ================================================ .pagination-nav__link { background-color: var(--base-contrast-background-color); border: none; color: #fff; transition: background-color .2s; } .pagination-nav__link:hover { background-color: var(--base-contrast-background-hover-color); color: #fff; } .pagination-nav__sublabel { display: none; } .pagination-nav__link--next .pagination-nav__label { transition: padding-right .2s; padding-right: .25rem; } .pagination-nav__link--next:hover .pagination-nav__label { padding-right: 0; } .pagination-nav__link--prev .pagination-nav__label { transition: padding-left .2s; padding-left: .25rem; } .pagination-nav__link--prev:hover .pagination-nav__label { padding-left: 0; } ================================================ FILE: docs/src/css/search.scss ================================================ div[class^='searchBox'], div[class*=' searchBox']{ position: static; } @media (max-width: 996px) { div[class^='searchBox'], div[class*=' searchBox']{ margin-left: 8px !important; } } ================================================ FILE: docs/src/css/sidebar.scss ================================================ .menu__list-item-collapsible { font-size: 13px; font-weight: 600; text-transform: uppercase; .menu__link--active { color: var(--ifm-menu-color); } } .menu>.menu__list .menu__list { font-size: 14px; font-weight: 400; margin-bottom: 4px; padding-left: 14px; .menu__link { padding-top: 8px; padding-bottom: 8px; border-top-left-radius: 0; border-bottom-left-radius: 0; } .menu__link:not(.menu__link--active) { color: var(--menu-secondary-color); padding-left: 13px; border-left: 1px solid var(--ifm-toc-border-color); margin-left: 3px; } .menu__link--active { border-left: 3px solid var(--ifm-color-primary); margin-left: 2px; } } .menu__list-item { .dropdown-separator { display: none; } &:not(:first-child) { margin-top: 0; } } .menu__list { .nav-icon { display: none; } } ================================================ FILE: docs/src/css/tab.scss ================================================ li[role='tab'] { font-weight: 500; font-size: 14px; padding: .6rem 1rem; } li[role='tab'][aria-selected='true'] { background-color: var(--base-contrast-background-color); border: 1px solid var(--base-contrast-background-color); color: #fff; transition: border .2s; border-radius: var(--ifm-global-radius); } li[role='tab'][aria-selected='false'] { opacity: .8; transition: opacity .2s; border: 1px solid var(--ifm-color-emphasis-200) } li[role='tab'][aria-selected='false']:hover { opacity: 1; } li[role='tab']:not(:first-of-type) { margin-left: 5px; } [data-theme='light'] { li[role='tab'][aria-selected='true'] { background-color: var(--base-contrast-background-color); border: 1px solid var(--base-contrast-background-color); } } @media screen and (max-width: 996px) { li[role='tab'] { padding: .6rem .5rem; } } ================================================ FILE: docs/src/css/toc.scss ================================================ .table-of-contents { font-size: 13px; } .table-of-contents__link:not(.table-of-contents__link--active) { color: var(--menu-secondary-color); } .table-of-contents__link:hover { color: var(--ifm-color-primary); } .table-of-contents li { position: relative; } ul ul .table-of-contents__link--active:before { left: -33px; } .table-of-contents__link--active:before { background: var(--ifm-color-primary); width: 2px; position: absolute; bottom: 0; left: -17px; top: 0; content: " "; } ================================================ FILE: docs/src/pages/index.js ================================================ import Link from '@docusaurus/Link'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import useBaseUrl from '@docusaurus/useBaseUrl'; import Layout from '@theme/Layout'; import React from 'react'; import styles from './index.module.scss'; function HomepageHeader() { const {siteConfig} = useDocusaurusContext(); return (
Logo

{siteConfig.title}

{siteConfig.tagline}

🚀 Thread-safe and lock-free.
⚡️️ Easy-to-use Fluent API.
♻️ Small memory footprint.
{'$'} dotnet add package Stashbox --version 5.20.0
Get Started GitHub NuGet
); } export default function Home() { const {siteConfig} = useDocusaurusContext(); return ( ); } ================================================ FILE: docs/src/pages/index.module.scss ================================================ /** * CSS files with the .module.css suffix will be treated as CSS modules * and scoped locally. */ .heroBanner { padding: 4rem 0; text-align: center; position: relative; overflow: hidden; } .logo { margin: 15px 0 20px 0; } .attributes { font-size: 1em; margin-bottom: 30px; color: var(--ifm-color-gray-600); } .buttons { display: flex; align-items: center; justify-content: center; margin-top: 30px; } .buttons a { margin: 5px; } .title { font-size: 3em; font-weight: bold; } .subtitle { font-size: 1.2em; } .installContainer { margin: auto; max-width: 420px; } .install { margin: 30px 0; pre { font-family: monospace; font-size: 14px; display: flex; align-items: center; flex-wrap: nowrap; padding-top: 1.1rem; } } .command { color: yellow; } .command_start { -webkit-user-select: none; -ms-user-select: none; user-select: none; color: var(--ifm-color-gray-600); } .options { color: var(--ifm-color-gray-600); } .cursor { content: ""; display: inline-block; background-color: var(--ifm-font-color-secondary); width: 7px; min-width: 7px; height: 15px; margin-left: 1px; margin-bottom: 2px; -webkit-animation: blink 1s step-end infinite; animation: blink 1s step-end infinite; @-webkit-keyframes blink { 0% { opacity: 1.0; } 50% { opacity: 0.0; } 100% { opacity: 1.0; } } @keyframes blink { 0% { opacity: 1.0; } 50% { opacity: 0.0; } 100% { opacity: 1.0; } } } [data-theme='light'] { .command { color: darkgoldenrod; } } @media screen and (max-width: 996px) { .heroBanner { padding: 3rem; } } @media screen and (max-width: 460px) { .heroBanner { padding: 3rem .5rem; } } ================================================ FILE: docs/src/theme/CodeBlock/index.js ================================================ import CodeBlock from '@theme-original/CodeBlock'; import React from 'react'; export default function CodeBlockWrapper(props) { return ( <> ); } ================================================ FILE: docs/src/theme/Footer/index.js ================================================ import React from 'react'; import clsx from 'clsx'; import {useThemeConfig} from '@docusaurus/theme-common'; import LinkItem from '@theme/Footer/LinkItem'; import Link from '@docusaurus/Link'; import FooterCopyright from '@theme/Footer/Copyright'; import styles from './styles.module.scss'; import SvgIcon from '../../components/SvgIcon'; const footerLinks = [ { link: "https://github.com/z4kn4fein/stashbox", icon: "github", text: "GitHub" }, { link: "https://www.nuget.org/packages/Stashbox", icon: "nuget", text: "NuGet" }, ] function Footer() { const {footer} = useThemeConfig(); if (!footer) { return null; } const {copyright, links, style} = footer; return (
REPOSITORY
{links.map((link, i) => (
{link.title}
    {link.items.map((item, k) => ( item.html ? (
  • ) : (
  • ) ))}
))}
LINKS

{(copyright) && (
)}
); } export default React.memo(Footer); ================================================ FILE: docs/src/theme/Footer/styles.module.scss ================================================ .footer { background-color: var(--ifm-footer-background-color); padding: var(--ifm-footer-padding-vertical) 0; font-size: 14px; } .footer_title { color: #858a96; font-weight: 500; font-size: 14px; } .footer_icon { margin-right: 7px; width: 14px; height: 14px; color: var(--ifm-footer-color); } .footer_copyright { font-size: 13px; color: var(--ifm-color-gray-600); } .footer_link { display: flex; flex-direction: row; flex-wrap: nowrap; align-items: center; } .separator { opacity: .2; } .repo { min-width: 500px; } .remote_link_icon { opacity: .5; width: 10; height: 10px; } @media screen and (max-width: 996px) { .repo { min-width: 300px; } } ================================================ FILE: docs/src/theme/NavbarItem/ComponentTypes.js ================================================ import ComponentTypes from '@theme-original/NavbarItem/ComponentTypes'; import SvgNavbarItem from '@site/src/components/NavbarItems/SvgNavbarItem'; import SeparatorNavbarItem from '@site/src/components/NavbarItems/SeparatorNavbarItem'; export default { ...ComponentTypes, 'custom-icon': SvgNavbarItem, 'custom-separator': SeparatorNavbarItem, }; ================================================ FILE: docs/src/utils/prismDark.mjs ================================================ /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ import {themes} from 'prism-react-renderer'; const darkTheme = themes.vsDark; export default { plain: { color: '#D4D4D4', backgroundColor: '#272727', }, styles: [ ...darkTheme.styles, ], }; ================================================ FILE: docs/static/.nojekyll ================================================ ================================================ FILE: sandbox/stashbox.assemblyload/Program.cs ================================================ using Stashbox; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Loader; IStashboxContainer container = new StashboxContainer(); LoadAndUnload(container, out var weakContext); for (var i = 0; weakContext.TryGetTarget(out _) && i < 10; i++) { GC.Collect(); GC.WaitForPendingFinalizers(); } Console.WriteLine(weakContext.TryGetTarget(out _)); [MethodImpl(MethodImplOptions.NoInlining)] static void LoadAndUnload(IStashboxContainer container, out WeakReference weakContext) { var context = new AssemblyLoadContext(name: "context", isCollectible: true); weakContext = new WeakReference(context, trackResurrection: true); var assembly = context.LoadFromAssemblyPath(Path.Combine(Path.GetDirectoryName(container.GetType().Assembly.Location), "TestAssembly.dll")); container.RegisterAssembly(assembly); var instances = assembly.GetExportedTypes().Where(t => !t.IsGenericType).Select(exportedType => container.Resolve(exportedType)).ToList(); Console.WriteLine(instances.Count); Console.WriteLine(container.GetRegistrationDiagnostics().Count()); context.Unload(); } ================================================ FILE: sandbox/stashbox.assemblyload/stashbox.assemblyload.csproj ================================================ Exe net6.0 enable enable Stashbox.AssemblyLoad Stashbox.AssemblyLoad PreserveNewest ================================================ FILE: sandbox/stashbox.benchmarks/BeginScopeBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class BeginScopeBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.BeginScope(); } [Benchmark] public object New() { return this.newContainer.BeginScope(); } } } ================================================ FILE: sandbox/stashbox.benchmarks/ChildContainerBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class ChildContainerBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register() .Register(); this.newContainer.Register() .Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { var child = this.oldContainer.CreateChildContainer(); child.Register(); return this.oldContainer.Resolve(typeof(A)); } [Benchmark] public object New() { var child = this.newContainer.CreateChildContainer(); child.Register(); return this.newContainer.Resolve(typeof(A)); } class A { public A(B b, C c) { } } class B { public B(C c) { } } class C { } } } ================================================ FILE: sandbox/stashbox.benchmarks/DecoratorBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class DecoratorBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .RegisterDecorator(); this.newContainer.Register() .RegisterDecorator(); } [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Resolve(typeof(IA)); } [Benchmark] public object New() { return this.newContainer.Resolve(typeof(IA)); } interface IA { } class A : IA { } class ADec : IA { public ADec(IA a) { } } } } ================================================ FILE: sandbox/stashbox.benchmarks/DisposeBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; using System; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class DisposeBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(c => c.WithDisposableTransientTracking() .WithDefaultLifetime(from_nuget::Stashbox.Lifetime.Lifetimes.Scoped)); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(c => c.WithDisposableTransientTracking() .WithDefaultLifetime(from_project::Stashbox.Lifetime.Lifetimes.Scoped)); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register() .Register() .Register(); this.newContainer.Register() .Register() .Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { using var scope1 = this.oldContainer.BeginScope(); scope1.Resolve(typeof(DisposableObj2)); using var scope2 = this.oldContainer.BeginScope(); scope2.Resolve(typeof(DisposableObj2)); using var scope3 = this.oldContainer.BeginScope(); return scope3.Resolve(typeof(DisposableObj2)); } [Benchmark] public object New() { using var scope1 = this.newContainer.BeginScope(); scope1.Resolve(typeof(DisposableObj2)); using var scope2 = this.newContainer.BeginScope(); scope2.Resolve(typeof(DisposableObj2)); using var scope3 = this.newContainer.BeginScope(); return scope3.Resolve(typeof(DisposableObj2)); } private class DisposableObj1 : IDisposable { public void Dispose() { } } private class DisposableObj3 : IDisposable { private readonly DisposableObj4 obj4; private readonly DisposableObj1 obj1; public DisposableObj3(DisposableObj4 obj4, DisposableObj1 obj1) { this.obj4 = obj4; this.obj1 = obj1; } public void Dispose() { } } private class DisposableObj4 : IDisposable { private readonly DisposableObj1 obj1; public DisposableObj4(DisposableObj1 obj1) { this.obj1 = obj1; } public void Dispose() { } } private class DisposableObj2 : IDisposable { private readonly DisposableObj1 obj1; private readonly DisposableObj3 obj3; public DisposableObj2(DisposableObj1 obj1, DisposableObj3 obj3) { this.obj1 = obj1; this.obj3 = obj3; } public void Dispose() { } } } } ================================================ FILE: sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using System.Collections.Generic; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class EnumerableBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register() .Register() .Register(); this.newContainer.Register() .Register() .Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Resolve(typeof(IEnumerable)); } [Benchmark] public object New() { return this.newContainer.Resolve(typeof(IEnumerable)); } interface IA { } class A : IA { public A(B b) { } } class AA : IA { public AA(B b) { } } class AAA : IA { public AAA(B b) { } } class B { } } } ================================================ FILE: sandbox/stashbox.benchmarks/FinalizerBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class FinalizerBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register(c => c.WithFinalizer(a => a.FinalizeA()).WithScopedLifetime()); this.newContainer.Register(c => c.WithFinalizer(a => a.FinalizeA()).WithScopedLifetime()); } [Benchmark(Baseline = true)] public object Old() { using var scope = this.oldContainer.BeginScope(); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); using var scope2 = this.oldContainer.BeginScope(); scope2.Resolve(typeof(A)); scope2.Resolve(typeof(A)); scope2.Resolve(typeof(A)); return scope2.Resolve(typeof(A)); } [Benchmark] public object New() { using var scope = this.newContainer.BeginScope(); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); scope.Resolve(typeof(A)); using var scope2 = this.newContainer.BeginScope(); scope2.Resolve(typeof(A)); scope2.Resolve(typeof(A)); scope2.Resolve(typeof(A)); return scope2.Resolve(typeof(A)); } class A { private bool isFinalized; public void FinalizeA() { this.isFinalized = true; } } } } ================================================ FILE: sandbox/stashbox.benchmarks/FuncBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; using System; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class FuncBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register(); this.newContainer.Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { var factory = (Func)this.oldContainer.Resolve(typeof(Func)); return factory((B)this.oldContainer.Resolve(typeof(B))); } [Benchmark] public object New() { var factory = (Func)this.newContainer.Resolve(typeof(Func)); return factory((B)this.newContainer.Resolve(typeof(B))); } class A { public A(B b) { } } class B { } } } ================================================ FILE: sandbox/stashbox.benchmarks/NullableBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class NullableBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.ResolveOrDefault(typeof(object)); } [Benchmark] public object New() { return this.newContainer.ResolveOrDefault(typeof(object)); } } } ================================================ FILE: sandbox/stashbox.benchmarks/Program.cs ================================================ using System; using BenchmarkDotNet.Configs; using BenchmarkDotNet.Running; namespace Stashbox.Benchmarks { class Program { static void Main() { var config = ManualConfig.Create(DefaultConfig.Instance) .WithOptions(ConfigOptions.JoinSummary) .WithOptions(ConfigOptions.DisableLogFile); BenchmarkRunner.Run( new[] { // BenchmarkConverter.TypeToBenchmarks( typeof(DisposeBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(EnumerableBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(PropertyBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(RegisterBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(ResolveBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(ScopedBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(FuncBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(FinalizerBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(NullableBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(BeginScopeBenchmarks), config), BenchmarkConverter.TypeToBenchmarks( typeof(TreeBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(SingletonBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(DecoratorBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(ChildContainerBenchmarks), config), }); Console.ReadKey(); } } } ================================================ FILE: sandbox/stashbox.benchmarks/PropertyBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class PropertyBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register(c => c.WithAutoMemberInjection()) .Register(); this.newContainer.Register(c => c.WithAutoMemberInjection()) .Register(); } [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Resolve(typeof(A)); } [Benchmark] public object New() { return this.newContainer.Resolve(typeof(A)); } class A { public B B { get; set; } } class B { } } } ================================================ FILE: sandbox/stashbox.benchmarks/RegisterBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class RegisterBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Register().Register(); } [Benchmark] public object New() { return this.newContainer.Register().Register(); } class A { } class B { } } } ================================================ FILE: sandbox/stashbox.benchmarks/ResolveBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class ResolveBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register() .Register(); this.newContainer.Register() .Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Resolve(typeof(A)); } [Benchmark] public object New() { return this.newContainer.Resolve(typeof(A)); } class A { public A(B b, C c) { } } class B { public B(C c) { } } class C { } } } ================================================ FILE: sandbox/stashbox.benchmarks/ScopedBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class ScopedBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(c => c .WithDefaultLifetime(from_nuget::Stashbox.Lifetime.Lifetimes.Scoped)); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(c => c .WithDefaultLifetime(from_project::Stashbox.Lifetime.Lifetimes.Scoped)); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register(); this.newContainer.Register() .Register(); } [Benchmark(Baseline = true)] public object Old() { using var scope = this.oldContainer.BeginScope(); return scope.Resolve(typeof(A)); } [Benchmark] public object New() { using var scope = this.newContainer.BeginScope(); return scope.Resolve(typeof(A)); } class A { public A(B b) { } } class B { } } } ================================================ FILE: sandbox/stashbox.benchmarks/SingletonBenchmarks.cs ================================================ extern alias from_nuget; extern alias from_project; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks { [MemoryDiagnoser] public class SingletonBenchmarks { private readonly from_nuget::Stashbox.IStashboxContainer oldContainer = new from_nuget::Stashbox.StashboxContainer(); private readonly from_project::Stashbox.IStashboxContainer newContainer = new from_project::Stashbox.StashboxContainer(); [GlobalSetup] public void Setup() { this.oldContainer.Register() .Register() .Register(c => c.WithSingletonLifetime()); this.newContainer.Register() .Register() .Register(c => c.WithSingletonLifetime()); } [Benchmark(Baseline = true)] public object Old() { return this.oldContainer.Resolve(typeof(A)); } [Benchmark] public object New() { return this.newContainer.Resolve(typeof(A)); } class A { public A(B b, C c) { } } class B { public B(C c) { } } class C { } } } ================================================ FILE: sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj ================================================  Exe net8.0 Stashbox.Benchmarks Stashbox.Benchmarks Debug;Release;Benchmark latest true ../../sn.snk true ..\..\src\bin\Benchmark\net8.0\Stashbox.Benchmark.dll from_project true from_nuget ================================================ FILE: sandbox/stashbox.benchmarks/TreeBenchmarks.cs ================================================ extern alias from_project; using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using BenchmarkDotNet.Attributes; using from_project::Stashbox.Utils.Data.Immutable; namespace Stashbox.Benchmarks; [MemoryDiagnoser] public class TreeBenchmarks { private Type[] keys; private ImmutableDictionary imDict; private ImmutableTree imTree; [Params(10, 100, 1000)] public int Count; [GlobalSetup] public void Setup() { imDict = ImmutableDictionary.Empty; imTree = ImmutableTree.Empty; keys = typeof(Dictionary<,>).Assembly.GetTypes().Take(Count).ToArray(); foreach (var key in keys) { imDict = imDict.Add(key, "value"); imTree = imTree.AddOrUpdate(key, "value", true); } } [Benchmark] public object ImmutableDictionary_Add() { var imDictToAdd = ImmutableDictionary.Empty; foreach (var key in keys) { imDictToAdd = imDictToAdd.Add(key, "value"); } return imDictToAdd; } [Benchmark] public object ImmutableTree_AddOrUpdate() { var imTreeToAdd = ImmutableTree.Empty; foreach (var key in keys) { imTreeToAdd = imTreeToAdd.AddOrUpdate(key, "value", true); } return imTreeToAdd; } [Benchmark] public void ImmutableDictionary_TryGetValue() { foreach (var key in keys) { imDict.TryGetValue(key, out _); } } [Benchmark] public void ImmutableTree_GetOrDefault() { foreach (var key in keys) { imTree.GetOrDefaultByRef(key); } } } ================================================ FILE: sandbox/stashbox.sandbox.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32421.90 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stashbox.benchmarks", "stashbox.benchmarks\stashbox.benchmarks.csproj", "{AF205E54-7B55-436A-81DB-5B010B7E9CD6}" ProjectSection(ProjectDependencies) = postProject {FB1985B6-E9A5-4662-89D7-87BEF62D94FB} = {FB1985B6-E9A5-4662-89D7-87BEF62D94FB} EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stashbox", "..\src\stashbox.csproj", "{FB1985B6-E9A5-4662-89D7-87BEF62D94FB}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stashbox.trimmed", "stashbox.trimmed\stashbox.trimmed.csproj", "{DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "stashbox.assemblyload", "stashbox.assemblyload\stashbox.assemblyload.csproj", "{A76BE134-DE84-4CC1-87E3-B91577710249}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Benchmark|Any CPU = Benchmark|Any CPU Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Debug|Any CPU.Build.0 = Debug|Any CPU {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Release|Any CPU.ActiveCfg = Release|Any CPU {AF205E54-7B55-436A-81DB-5B010B7E9CD6}.Release|Any CPU.Build.0 = Release|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Benchmark|Any CPU.ActiveCfg = Benchmark|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Benchmark|Any CPU.Build.0 = Benchmark|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Debug|Any CPU.Build.0 = Debug|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Release|Any CPU.ActiveCfg = Release|Any CPU {FB1985B6-E9A5-4662-89D7-87BEF62D94FB}.Release|Any CPU.Build.0 = Release|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Benchmark|Any CPU.ActiveCfg = Debug|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Benchmark|Any CPU.Build.0 = Debug|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Debug|Any CPU.Build.0 = Debug|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFC8FD7D-E4DF-49D8-ACA3-517144EC1D34}.Release|Any CPU.Build.0 = Release|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Benchmark|Any CPU.ActiveCfg = Debug|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Benchmark|Any CPU.Build.0 = Debug|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Debug|Any CPU.Build.0 = Debug|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Release|Any CPU.ActiveCfg = Release|Any CPU {A76BE134-DE84-4CC1-87E3-B91577710249}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {8C4CB567-7FB6-49BA-9AE4-4FE30BF21021} EndGlobalSection EndGlobal ================================================ FILE: sandbox/stashbox.trimmed/Program.cs ================================================ using Stashbox; using System.Reflection; using System.Runtime.CompilerServices; try { new A(new B()); Console.WriteLine(Runner.Run()); } catch (TypeLoadException ex) { Console.WriteLine(ex.TypeName); Console.WriteLine(ex.Message); } class A { public A(B b) { } } class B { public B() { } } class Runner { [MethodImpl((short)MethodImplAttributes.NoInlining)] public static object? Run() { IStashboxContainer container = new StashboxContainer(); container.RegisterScoped() .RegisterScoped("A") .Register("B") .Register() .RegisterSingleton() .RegisterSingleton("BB") .Register(typeof(A)) .Register(typeof(A), c => { }) .Register(typeof(A)) .Register(typeof(A), c => { }) .Register() .Register(c => { }) .Register(c => { }) .RegisterDecorator() .RegisterDecorator(c => { }); container.Resolve(); container.Resolve("A"); container.Resolve(typeof(A)); container.Resolve(typeof(A), "A"); container.ResolveOrDefault(); container.ResolveOrDefault("A"); container.ResolveOrDefault(typeof(A)); container.ResolveOrDefault(typeof(A), "A"); container.ResolveAll(); container.ResolveAll("A"); container.ResolveAll(typeof(A)); container.ResolveAll(typeof(A), "A"); IDependencyResolver scope = container.BeginScope(); scope.Resolve(); scope.Resolve("A"); scope.Resolve(typeof(A)); scope.Resolve(typeof(A), "A"); scope.ResolveOrDefault(); scope.ResolveOrDefault("A"); scope.ResolveOrDefault(typeof(A)); scope.ResolveOrDefault(typeof(A), "A"); scope.ResolveAll(); scope.ResolveAll("A"); scope.ResolveAll(typeof(A)); scope.ResolveAll(typeof(A), "A"); return scope.Resolve(); } } ================================================ FILE: sandbox/stashbox.trimmed/stashbox.trimmed.csproj ================================================  Exe net6.0 enable enable true link true Stashbox.Trimmed Stashbox.Trimmed ================================================ FILE: src/Attributes/DependencyAttribute.cs ================================================ using System; namespace Stashbox.Attributes; /// /// Represents an attribute for tracking dependencies. /// [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Field)] public class DependencyAttribute : Attribute { /// /// The name of the dependency. /// public object? Name { get; set; } /// /// Constructs a /// /// The name of the dependency. public DependencyAttribute(object? name = null) { this.Name = name; } } ================================================ FILE: src/Attributes/DependencyNameAttribute.cs ================================================ using System; namespace Stashbox.Attributes; /// /// When a parameter is marked with this attribute, the container will pass the given dependency's name to it. /// [AttributeUsage(AttributeTargets.Parameter)] public class DependencyNameAttribute : Attribute; ================================================ FILE: src/Attributes/InjectionMethodAttribute.cs ================================================ using System; namespace Stashbox.Attributes; /// /// Represents an attribute for tracking injection methods. /// [AttributeUsage(AttributeTargets.Method)] public sealed class InjectionMethodAttribute : Attribute; ================================================ FILE: src/Configuration/ContainerConfiguration.cs ================================================ using Stashbox.Attributes; using Stashbox.Lifetime; using Stashbox.Registration.Fluent; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using Stashbox.Exceptions; using Stashbox.Utils; using Stashbox.Utils.Data; namespace Stashbox.Configuration; /// /// Represents a container configuration /// public class ContainerConfiguration { /// /// If it's set to true the container will track transient objects for disposal. /// public bool TrackTransientsForDisposalEnabled { get; internal set; } /// /// The actual behavior used when a new service is going to be registered into the container. See the enum for available options. /// public Rules.RegistrationBehavior RegistrationBehavior { get; internal set; } /// /// If it's set to true, the container will inject optional and default values for missing dependencies and primitive types. /// public bool DefaultValueInjectionEnabled { get; internal set; } /// /// If it's set to true the container will try to register the unknown type during the activation. /// public bool UnknownTypeResolutionEnabled { get; internal set; } /// /// If it's set to true, the container will inject members even without . /// public bool AutoMemberInjectionEnabled { get; internal set; } /// /// If it's set to true, the container will inject required fields and properties. /// public bool RequiredMemberInjectionEnabled { get; internal set; } = true; /// /// If it's set to true, the container will treat the name of a constructor/method parameter or member name as a dependency name used by named resolution. /// public bool TreatingParameterAndMemberNameAsDependencyNameEnabled { get; internal set; } /// /// If it's set to true, the container will use an unnamed registration when a named one not found for a request with a dependency name. /// public bool NamedDependencyResolutionForUnNamedRequestsEnabled { get; internal set; } /// /// If it's set to true, the container will use an unnamed registration when a named one not found for a request with a dependency name. /// public bool NamedDependencyResolutionForUnNamedCollectionRequestsEnabled { get; internal set; } = true; /// /// If it's set to true, the container won't select services with for a universal name resolution request. /// public bool IgnoreServicesWithUniversalNameForUniversalNamedRequests { get; internal set; } /// /// If it's set to true, the container will throw a when a named dependency is not resolvable even for requests initiated by . /// public bool ForceThrowWhenNamedDependencyIsNotResolvable { get; internal set; } /// /// If it's set to true, in a child-parent container case singletons will be rebuilt with the dependencies overridden in the child, not affecting the already built instance in the parent. /// public bool ReBuildSingletonsInChildContainerEnabled { get; internal set; } /// /// The member injection rule. /// public Rules.AutoMemberInjectionRules AutoMemberInjectionRule { get; internal set; } /// /// The constructor selection rule. /// public Func, IEnumerable> ConstructorSelectionRule { get; internal set; } = Rules.ConstructorSelection.PreferMostParameters; /// /// Represents the configuration which will be invoked when an unknown type being registered. /// public Action? UnknownTypeConfigurator { get; internal set; } /// /// The action which will be invoked when the container configuration changes. /// public Action? ConfigurationChangedEvent { get; internal set; } /// /// A filter delegate used to determine which members should be auto-injected and which are not. /// public Func? AutoMemberInjectionFilter { get; internal set; } /// /// The default lifetime, used when a service isn't configured with a lifetime. /// public LifetimeDescriptor DefaultLifetime { get; internal set; } = Lifetimes.Transient; /// /// When it's true, the container validates the lifetime configuration of the resolution /// graph via the value, /// and checks that scoped services are not resolved from the root scope. /// public bool LifetimeValidationEnabled { get; internal set; } /// /// When it's true, the container checks for generic covariance and contravariance during the resolution of generic type collections. /// public bool VariantGenericTypesEnabled { get; internal set; } = true; /// /// When it's true, the container throws a when no services are found for a collection resolution request. /// public bool ExceptionOverEmptyCollectionEnabled { get; internal set; } internal object? UniversalName { get; set; } internal Type? ExternalResolutionFailedExceptionType { get; set; } internal ExpandableArray? AdditionalDependencyNameAttributeTypes { get; set; } internal ExpandableArray? AdditionalDependencyAttributeTypes { get; set; } /// /// A delegate to use external expression compilers. /// public Func? ExternalExpressionCompiler { get; internal set; } internal ContainerConfiguration() { } private ContainerConfiguration(bool trackTransientsForDisposalEnabled, Rules.RegistrationBehavior registrationBehavior, bool defaultValueInjectionEnabled, bool unknownTypeResolutionEnabled, bool autoMemberInjectionEnabled, bool treatingParameterAndMemberNameAsDependencyNameEnabled, bool namedDependencyResolutionForUnNamedRequestsEnabled, bool namedDependencyResolutionForUnNamedCollectionRequestsEnabled, bool reBuildSingletonsInChildContainerEnabled, bool variantGenericTypesEnabled, bool exceptionOverEmptyCollectionEnabled, Rules.AutoMemberInjectionRules autoMemberInjectionRule, Func, IEnumerable> constructorSelectionRule, Action? unknownTypeConfigurator, Action? configurationChangedEvent, Func? autoMemberInjectionFilter, LifetimeDescriptor defaultLifetime, bool lifetimeValidationEnabled, Func? externalExpressionCompiler, object? universalName, Type? externalResolutionFailedExceptionType, ExpandableArray? additionalDependencyNameAttributeTypes, ExpandableArray? additionalDependencyAttributeTypes) { this.TrackTransientsForDisposalEnabled = trackTransientsForDisposalEnabled; this.RegistrationBehavior = registrationBehavior; this.DefaultValueInjectionEnabled = defaultValueInjectionEnabled; this.UnknownTypeResolutionEnabled = unknownTypeResolutionEnabled; this.AutoMemberInjectionEnabled = autoMemberInjectionEnabled; this.TreatingParameterAndMemberNameAsDependencyNameEnabled = treatingParameterAndMemberNameAsDependencyNameEnabled; this.NamedDependencyResolutionForUnNamedRequestsEnabled = namedDependencyResolutionForUnNamedRequestsEnabled; this.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled = namedDependencyResolutionForUnNamedCollectionRequestsEnabled; this.ReBuildSingletonsInChildContainerEnabled = reBuildSingletonsInChildContainerEnabled; this.VariantGenericTypesEnabled = variantGenericTypesEnabled; this.ExceptionOverEmptyCollectionEnabled = exceptionOverEmptyCollectionEnabled; this.AutoMemberInjectionRule = autoMemberInjectionRule; this.ConstructorSelectionRule = constructorSelectionRule; this.UnknownTypeConfigurator = unknownTypeConfigurator; this.ConfigurationChangedEvent = configurationChangedEvent; this.AutoMemberInjectionFilter = autoMemberInjectionFilter; this.DefaultLifetime = defaultLifetime; this.LifetimeValidationEnabled = lifetimeValidationEnabled; this.ExternalExpressionCompiler = externalExpressionCompiler; this.UniversalName = universalName; this.AdditionalDependencyNameAttributeTypes = additionalDependencyNameAttributeTypes; this.AdditionalDependencyAttributeTypes = additionalDependencyAttributeTypes; this.ExternalResolutionFailedExceptionType = externalResolutionFailedExceptionType; } internal ContainerConfiguration Clone() => new(this.TrackTransientsForDisposalEnabled, this.RegistrationBehavior, this.DefaultValueInjectionEnabled, this.UnknownTypeResolutionEnabled, this.AutoMemberInjectionEnabled, this.TreatingParameterAndMemberNameAsDependencyNameEnabled, this.NamedDependencyResolutionForUnNamedRequestsEnabled, this.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled, this.ReBuildSingletonsInChildContainerEnabled, this.VariantGenericTypesEnabled, this.ExceptionOverEmptyCollectionEnabled, this.AutoMemberInjectionRule, this.ConstructorSelectionRule, this.UnknownTypeConfigurator, this.ConfigurationChangedEvent, this.AutoMemberInjectionFilter, this.DefaultLifetime, this.LifetimeValidationEnabled, this.ExternalExpressionCompiler, this.UniversalName, this.ExternalResolutionFailedExceptionType, this.AdditionalDependencyNameAttributeTypes, this.AdditionalDependencyAttributeTypes); } ================================================ FILE: src/Configuration/ContainerConfigurator.cs ================================================ using Stashbox.Lifetime; using Stashbox.Registration.Fluent; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; using Stashbox.Attributes; using Stashbox.Exceptions; using Stashbox.Utils.Data; namespace Stashbox.Configuration; /// /// Represents a container configurator. /// public class ContainerConfigurator { /// /// The container configuration. /// public ContainerConfiguration ContainerConfiguration { get; } internal ContainerConfigurator(ContainerConfiguration? containerConfiguration = null) { this.ContainerConfiguration = containerConfiguration ?? new ContainerConfiguration(); } /// /// Enables or disables the tracking of disposable transient objects. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithDisposableTransientTracking(bool enabled = true) { this.ContainerConfiguration.TrackTransientsForDisposalEnabled = enabled; return this; } /// /// Sets the actual behavior used when a new service is registered into the container. These options do not affect named registrations. See the enum for available options. /// /// The actual registration behavior. /// The container configurator. public ContainerConfigurator WithRegistrationBehavior(Rules.RegistrationBehavior registrationBehavior) { this.ContainerConfiguration.RegistrationBehavior = registrationBehavior; return this; } /// /// Enables or disables the default value injection. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithDefaultValueInjection(bool enabled = true) { this.ContainerConfiguration.DefaultValueInjectionEnabled = enabled; return this; } /// /// Enables or disables the unknown type resolution. /// It's disabled by default. /// /// An optional configuration action used during the registration of the unknown type. /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithUnknownTypeResolution(Action? configurator = null, bool enabled = true) { this.ContainerConfiguration.UnknownTypeResolutionEnabled = enabled; this.ContainerConfiguration.UnknownTypeConfigurator = configurator; return this; } /// /// Enables or disables the auto member-injection without annotation. /// /// The rule used to determine what kind of members (properties / fields) should be auto injected. /// An optional filter predicate used to select which properties or fields of a type should be auto injected. /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithAutoMemberInjection(Rules.AutoMemberInjectionRules rule = Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, Func? filter = null, bool enabled = true) { this.ContainerConfiguration.AutoMemberInjectionEnabled = enabled; this.ContainerConfiguration.AutoMemberInjectionRule = rule; this.ContainerConfiguration.AutoMemberInjectionFilter = filter; return this; } /// /// Enables or disables required member injection. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithRequiredMemberInjection(bool enabled = true) { this.ContainerConfiguration.RequiredMemberInjectionEnabled = enabled; return this; } /// /// Sets the constructor selection rule used to determine which constructor the container should use for instantiation /// /// The container configurator. public ContainerConfigurator WithConstructorSelectionRule(Func, IEnumerable> selectionRule) { this.ContainerConfiguration.ConstructorSelectionRule = selectionRule; return this; } /// /// Sets a callback delegate to call when the container configuration changes. /// /// The container configurator. public ContainerConfigurator OnContainerConfigurationChanged(Action configurationChanged) { this.ContainerConfiguration.ConfigurationChangedEvent = configurationChanged; return this; } /// /// Enables or disables conventional resolution, which means the container treats the constructor/method parameter or member names as dependency names used by named resolution. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator TreatParameterAndMemberNameAsDependencyName(bool enabled = true) { this.ContainerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled = enabled; return this; } /// /// Enables or disables the selection of named registrations when the resolution request is unnamed. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// Enables or disables the selection of named registrations when a collection resolution request is unnamed. It's enabled by default. /// The container configurator. public ContainerConfigurator WithNamedDependencyResolutionForUnNamedRequests(bool enabled = true, bool enabledForCollectionRequests = true) { this.ContainerConfiguration.NamedDependencyResolutionForUnNamedRequestsEnabled = enabled; this.ContainerConfiguration.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled = enabledForCollectionRequests; return this; } /// /// Enables or disables the selection of services with for a universal named resolution request. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithIgnoreServicesWithUniversalNameForUniversalNamedRequests(bool enabled = true) { this.ContainerConfiguration.IgnoreServicesWithUniversalNameForUniversalNamedRequests = enabled; return this; } /// /// Enables or disables throwing a when a named dependency is not resolvable even for requests initiated by . /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithForceThrowWhenNamedDependencyIsNotResolvable(bool enabled = true) { this.ContainerConfiguration.ForceThrowWhenNamedDependencyIsNotResolvable = enabled; return this; } /// /// Sets the default lifetime used when a service doesn't have a configured one. /// /// The default lifetime. /// The container configurator. public ContainerConfigurator WithDefaultLifetime(LifetimeDescriptor lifetime) { this.ContainerConfiguration.DefaultLifetime = lifetime; return this; } /// /// Enables or disables the life-span and root resolution validation on the dependency tree. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithLifetimeValidation(bool enabled = true) { this.ContainerConfiguration.LifetimeValidationEnabled = enabled; return this; } /// /// Enables or disables the re-building of singletons in child containers. /// It allows the child containers to effectively override singleton dependencies in the parent. /// This feature is not affecting the already built singleton instances in the parent. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithReBuildSingletonsInChildContainer(bool enabled = true) { this.ContainerConfiguration.ReBuildSingletonsInChildContainerEnabled = enabled; return this; } /// /// Enables or disables the check for generic covariance and contravariance during the resolution of generic type collections. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithVariantGenericTypes(bool enabled = true) { this.ContainerConfiguration.VariantGenericTypesEnabled = enabled; return this; } /// /// Enables or disables the throwing of a when no services are found for a collection resolution request. /// When this feature is disabled (default), the container returns an empty array for the collection resolution request. /// It's disabled by default. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public ContainerConfigurator WithExceptionOverEmptyCollection(bool enabled = true) { this.ContainerConfiguration.ExceptionOverEmptyCollectionEnabled = enabled; return this; } /// /// Sets an external expression tree compiler used by the container to compile the generated expressions. /// /// The compiler delegate used to compile expression trees. /// The container configurator. public ContainerConfigurator WithExpressionCompiler(Func compilerDelegate) { this.ContainerConfiguration.ExternalExpressionCompiler = compilerDelegate; return this; } /// /// Sets the universal name that represents a special name which allows named resolution work for any given name. /// /// The universal name. /// The container configurator. public ContainerConfigurator WithUniversalName(object name) { this.ContainerConfiguration.UniversalName = name; return this; } /// /// Adds an attribute type that is considered a dependency name indicator just like . /// /// The attribute type. /// The container configurator. public ContainerConfigurator WithAdditionalDependencyNameAttribute() where TAttribute : Attribute { this.ContainerConfiguration.AdditionalDependencyNameAttributeTypes ??= []; this.ContainerConfiguration.AdditionalDependencyNameAttributeTypes.Add(typeof(TAttribute)); return this; } /// /// Adds an attribute type that is considered a dependency indicator just like . /// /// The attribute type. /// The container configurator. public ContainerConfigurator WithAdditionalDependencyAttribute() where TAttribute : Attribute { this.ContainerConfiguration.AdditionalDependencyAttributeTypes ??= []; this.ContainerConfiguration.AdditionalDependencyAttributeTypes.Add(typeof(TAttribute)); return this; } /// /// Wraps each with the given exception type. /// /// The exception type. /// The container configurator. public ContainerConfigurator OverrideResolutionFailedExceptionWith() where TException : Exception { this.ContainerConfiguration.ExternalResolutionFailedExceptionType = typeof(TException); return this; } } ================================================ FILE: src/Configuration/Rules.cs ================================================ using Stashbox.Exceptions; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Stashbox.Configuration; /// /// Represents the predefined configuration rules of the . /// public static class Rules { /// /// Represents the rules related to registration filters used in . /// public static class ServiceRegistrationFilters { /// /// Includes only interface types. /// public static readonly Func Interfaces = (_, t) => t.IsInterface; /// /// Includes only abstract types. /// public static readonly Func AbstractClasses = (_, t) => t.IsAbstract && !t.IsInterface; } /// /// Represents the actual behavior used when a new service is going to be registered into the container. These options does not affect named registrations. /// public enum RegistrationBehavior { /// /// The container will skip new registrations when the given implementation type is already registered. /// SkipDuplications, /// /// The container will throw a when the given implementation type is already registered. /// ThrowException, /// /// The container will replace the already registered service with the given one when they have the same implementation type. /// ReplaceExisting, /// /// The container will keep registering the new services with the same implementation type. /// PreserveDuplications } /// /// Represents the rules for auto-injecting members. /// [Flags] public enum AutoMemberInjectionRules { /// /// None will be injected. /// None = 1 << 1, /// /// With this flag the container will perform auto-injection on properties which has a public setter. /// PropertiesWithPublicSetter = 1 << 2, /// /// With this flag the container will perform auto-injection on properties which has a non-public setter as well. /// PropertiesWithLimitedAccess = 1 << 3, /// /// With this flag the container will perform auto-injection on private fields too. /// PrivateFields = 1 << 4 } /// /// Represents the constructor selection rules. /// public static class ConstructorSelection { /// /// Prefers the constructor which has the longest parameter list. /// public static readonly Func, IEnumerable> PreferMostParameters = constructors => constructors.OrderByDescending(constructor => constructor.GetParameters().Length); /// /// Prefers the constructor which has the shortest parameter list. /// public static readonly Func, IEnumerable> PreferLeastParameters = constructors => constructors.OrderBy(constructor => constructor.GetParameters().Length); } /// /// Pre-defined expression compiler delegates. /// public static class ExpressionCompilers { /// /// The standard Microsoft expression compiler. /// public static readonly Func MicrosoftExpressionCompiler = lambda => lambda.Compile(); /// /// The built-in Stashbox expression compiler. /// public static readonly Func StashboxExpressionCompiler = lambda => lambda.CompileDelegate(); } } ================================================ FILE: src/ContainerContext.cs ================================================ using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Resolution; namespace Stashbox; internal class ContainerContext : IContainerContext { public ContainerContext(IContainerContext? parentContext, IResolutionStrategy resolutionStrategy, ContainerConfiguration containerConfiguration) { this.ContainerConfiguration = containerConfiguration; this.ParentContext = parentContext; this.RootScope = new ResolutionScope(this); this.RegistrationRepository = new RegistrationRepository(containerConfiguration); this.DecoratorRepository = new DecoratorRepository(containerConfiguration); this.ResolutionStrategy = resolutionStrategy; } public IRegistrationRepository RegistrationRepository { get; } public IDecoratorRepository DecoratorRepository { get; } public IContainerContext? ParentContext { get; } public IResolutionScope RootScope { get; } public IResolutionStrategy ResolutionStrategy { get; } public ContainerConfiguration ContainerConfiguration { get; } } ================================================ FILE: src/Exceptions/CompositionRootNotFoundException.cs ================================================ using System; using System.Reflection; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Occurs when composing requested but no is present in the given assembly. /// [Serializable] public class CompositionRootNotFoundException : Exception { /// /// Constructs a . /// /// The scanned assembly. /// The inner exception. public CompositionRootNotFoundException(Assembly assembly, Exception? innerException = null) : base($"No ICompositionRoot found in the given assembly: {assembly.FullName}.", innerException) { } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected CompositionRootNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Exceptions/ConstructorNotFoundException.cs ================================================ using System; using System.Linq; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Represents a constructor not found exception. /// [Serializable] public class ConstructorNotFoundException : Exception { /// /// Constructs a . /// /// The type on the constructor was not found. /// The arguments. /// The inner exception public ConstructorNotFoundException(Type type, Type[] argumentTypes, Exception? innerException = null) : base($"Constructor not found for '{type.FullName}' with the given argument types: {argumentTypes.Select(t => t.FullName).Aggregate((t1, t2) => $"{t1}, {t2}")}.", innerException) { } /// /// Constructs a . /// /// The type on the constructor was not found. /// The inner exception public ConstructorNotFoundException(Type type, Exception? innerException = null) : base($"Constructor not found for '{type.FullName}' with no arguments.", innerException) { } /// /// Constructs a . /// /// The type on the constructor was not found. /// The argument type. /// The inner exception public ConstructorNotFoundException(Type type, Type argument, Exception? innerException = null) : base($"Constructor not found for '{type.FullName}' with the argument type: {argument.FullName}.", innerException) { } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected ConstructorNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Exceptions/InvalidRegistrationException.cs ================================================ using System; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Represents an exception the container throws when it detects an invalid registration. /// [Serializable] public class InvalidRegistrationException : Exception { /// /// The type the container is trying to register. /// public Type? Type { get; } /// /// Constructs a . /// /// The type of the service. /// The exception message. /// The inner exception. public InvalidRegistrationException(Type? type, string message, Exception? innerException = null) : base($"Invalid registration with type '{type?.FullName}'. Details: {message}", innerException) { this.Type = type; } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected InvalidRegistrationException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Exceptions/LifetimeValidationFailedException.cs ================================================ using System; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Represents the exception the container throws when the lifetime validation is failed. /// [Serializable] public class LifetimeValidationFailedException : Exception { /// /// The type the container is currently resolving. /// public Type? Type { get; } /// /// Constructs a . /// /// The type of the service. /// The exception message. public LifetimeValidationFailedException(Type? type, string message) : base(message) { this.Type = type; } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected LifetimeValidationFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Exceptions/ResolutionFailedException.cs ================================================ using System; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Represents the exception the container throws when a service resolution is failed. /// [Serializable] public class ResolutionFailedException : Exception { private const string DefaultMessage = "Service is not registered properly or unresolvable type requested."; /// /// The type the container is currently resolving. /// public Type? Type { get; } /// /// Constructs a . /// /// The type of the service. /// The name of the service. /// The exception message. /// The inner exception. public ResolutionFailedException(Type? type, object? name = null, string? message = null, Exception? innerException = null) : base(ConstructMessage(type, name, message), innerException) { this.Type = type; } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected ResolutionFailedException(SerializationInfo info, StreamingContext context) : base(info, context) { } private static string ConstructMessage(Type? serviceType, object? name = null, string? message = null) => $"Unable to resolve type '{serviceType?.FullName}'{(name != null ? " with name \'" + name + "\'" : "")}.{Environment.NewLine}{message ?? DefaultMessage}"; internal static Exception CreateWithDesiredExceptionType(Type? serviceType, object? name = null, string? message = null, Exception? innerException = null, Type? externalExceptionType = null) { if (externalExceptionType == null) return new ResolutionFailedException(serviceType, name, message, innerException); return (Exception)(Activator.CreateInstance(externalExceptionType, ConstructMessage(serviceType, name, message), innerException) ?? new ResolutionFailedException(serviceType, name, message, innerException)); } } ================================================ FILE: src/Exceptions/ServiceAlreadyRegisteredException.cs ================================================ using Stashbox.Configuration; using System; using System.Runtime.Serialization; namespace Stashbox.Exceptions; /// /// Represents the exception the container throws when a registration process fails due to service duplication. /// Occurs when the container is configured with . /// [Serializable] public class ServiceAlreadyRegisteredException : Exception { /// /// The type the container is trying to register. /// public Type? Type { get; } /// /// Constructs a . /// /// The type of the service. /// The inner exception. public ServiceAlreadyRegisteredException(Type? type, Exception? innerException = null) : base($"The type '{type?.FullName}' is already registered.", innerException) { this.Type = type; } /// #if NET8_0_OR_GREATER [Obsolete(DiagnosticId = "SYSLIB0051")] // add this attribute to the serialization ctor #endif protected ServiceAlreadyRegisteredException(SerializationInfo info, StreamingContext context) : base(info, context) { } } ================================================ FILE: src/Expressions/Compile/Closure.cs ================================================ using System.Reflection; using Stashbox.Utils; namespace Stashbox.Expressions.Compile; internal class Closure { public static readonly FieldInfo ConstantsField = TypeCache.Type.GetField(nameof(Constants))!; public readonly object[] Constants; public Closure(object[] constants) { this.Constants = constants; } } ================================================ FILE: src/Expressions/Compile/CompilerContext.cs ================================================ using Stashbox.Utils.Data; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile; internal class CompilerContext { public readonly ExpandableArray DefinedVariables; public readonly bool HasCapturedVariablesArgument; public readonly bool IsNestedLambda; public readonly ExpandableArray Constants; public readonly ExpandableArray CapturedArguments; public readonly Closure? Target; public LocalBuilder[]? LocalBuilders; public LocalBuilder? CapturedArgumentsHolderVariable; public readonly ExpandableArray NestedLambdas; public readonly bool HasClosure; public CompilerContext(Closure? target, ExpandableArray constants, ExpandableArray definedVariables, ExpandableArray capturedArguments, ExpandableArray nestedLambdas) { this.Target = target; this.Constants = constants; this.DefinedVariables = definedVariables; this.CapturedArguments = capturedArguments; this.NestedLambdas = nestedLambdas; this.HasClosure = target != null; this.HasCapturedVariablesArgument = capturedArguments.Length > 0; } private CompilerContext(ExpandableArray definedVariables, bool hasCapturedVariablesArgument, bool isNestedLambda, ExpandableArray constants, ExpandableArray capturedArguments, Closure? target, LocalBuilder[]? localBuilders, LocalBuilder? capturedArgumentsHolderVariable, ExpandableArray nestedLambdas, bool hasClosure) { DefinedVariables = definedVariables; HasCapturedVariablesArgument = hasCapturedVariablesArgument; IsNestedLambda = isNestedLambda; Constants = constants; CapturedArguments = capturedArguments; Target = target; LocalBuilders = localBuilders; CapturedArgumentsHolderVariable = capturedArgumentsHolderVariable; NestedLambdas = nestedLambdas; HasClosure = hasClosure; } public CompilerContext Clone(ExpandableArray definedVariables, bool isNestedLambda, bool hasCapturedArgument) => new(definedVariables, hasCapturedArgument, isNestedLambda, this.Constants, this.CapturedArguments, this.Target, this.LocalBuilders, this.CapturedArgumentsHolderVariable, this.NestedLambdas, this.HasClosure); } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Assign.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this BinaryExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { switch (expression.Left.NodeType) { case ExpressionType.Parameter: var localIndex = context.DefinedVariables.IndexOf(expression.Left); if (localIndex == -1 || context.LocalBuilders == null) return false; if (!expression.Right.TryEmit(generator, context, parameters)) return false; generator.Emit(OpCodes.Stloc, context.LocalBuilders[localIndex]); if (!context.HasCapturedVariablesArgument) return true; var paramIndex = context.CapturedArguments.IndexOf(expression.Left); if (paramIndex == -1) return true; generator.LoadCapturedArgumentHolder(context); generator.EmitInteger(paramIndex); generator.Emit(OpCodes.Ldloc, context.LocalBuilders[localIndex]); if (expression.Type.IsValueType) generator.Emit(OpCodes.Box, expression.Type); generator.Emit(OpCodes.Stelem_Ref); return true; case ExpressionType.MemberAccess: var memberExpression = (MemberExpression)expression.Left; if (memberExpression.Expression != null && !memberExpression.Expression.TryEmit(generator, context, parameters)) return false; return expression.Right.TryEmit(generator, context, parameters) && memberExpression.Member.EmitMemberAssign(generator); default: return false; } } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Call.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this MethodCallExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { if (expression.Object != null && !expression.Object.TryEmit(generator, context, parameters)) return false; return expression.Arguments.TryEmit(generator, context, parameters) && generator.EmitMethod(expression.Method); } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Constant.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System; using System.Linq.Expressions; using System.Reflection.Emit; using Stashbox.Utils; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this ConstantExpression expression, ILGenerator generator, CompilerContext context) { var value = expression.Value; var type = expression.Type; if (value == null) { if (expression.Type.IsValueType) generator.InitValueType(expression.Type); else generator.Emit(OpCodes.Ldnull); return true; } if (context.HasClosure && !Utils.IsInPlaceEmittableConstant(type, value)) { var constantIndex = context.Constants.IndexOf(value); if (constantIndex == -1) return false; generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, Closure.ConstantsField); generator.EmitInteger(constantIndex); generator.Emit(OpCodes.Ldelem_Ref); if (type.IsValueType) generator.Emit(OpCodes.Unbox_Any, type); return true; } if (generator.TryEmitNumberConstant(type, value)) return true; if (type.IsEnum) return generator.TryEmitNumberConstant(Enum.GetUnderlyingType(type), value); switch (value) { case string stringValue: generator.Emit(OpCodes.Ldstr, stringValue); break; case Type typeValue: generator.Emit(OpCodes.Ldtoken, typeValue); generator.Emit(OpCodes.Call, TypeCache.Type.GetMethod("GetTypeFromHandle")!); break; default: return false; } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Convert.cs ================================================ using System; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using Stashbox.Utils; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this UnaryExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { var typeFrom = expression.Operand.Type; var typeTo = expression.Type; if (!expression.Operand.TryEmit(generator, context, parameters)) return false; if (typeFrom == typeTo) return true; var typeToIsNullable = typeTo.IsNullableType(); var typeToUnderlyingType = Nullable.GetUnderlyingType(typeTo); ConstructorInfo? constructor; if (typeToIsNullable && typeFrom == typeToUnderlyingType && (constructor = typeTo.GetFirstConstructor()) != null) { generator.Emit(OpCodes.Newobj, constructor); return true; } switch (typeFrom.IsValueType) { case false when typeTo.IsValueType: generator.Emit(OpCodes.Unbox_Any, typeTo); break; case true when typeTo == TypeCache.Type: generator.Emit(OpCodes.Box, typeFrom); break; default: generator.Emit(OpCodes.Castclass, typeTo); break; } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Default.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using Stashbox.Utils; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this DefaultExpression expression, ILGenerator generator) { var type = expression.Type; if (type == TypeCache.VoidType) return true; if (type == TypeCache.Type) generator.Emit(OpCodes.Ldnull); else if (type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type || type == TypeCache.Type) generator.Emit(OpCodes.Ldc_I4_0); else if (type == TypeCache.Type || type == TypeCache.Type) { generator.Emit(OpCodes.Ldc_I4_0); generator.Emit(OpCodes.Conv_I8); } else if (type == TypeCache.Type) generator.Emit(OpCodes.Ldc_R4, default(float)); else if (type == TypeCache.Type) generator.Emit(OpCodes.Ldc_R8, default(double)); else if (type.IsValueType) generator.InitValueType(type); else generator.Emit(OpCodes.Ldnull); return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Invoke.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this InvocationExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { if (!expression.Expression.TryEmit(generator, context, parameters) || !expression.Arguments.TryEmit(generator, context, parameters)) return false; var invokeMethod = expression.Expression.Type.GetMethod("Invoke")!; generator.EmitMethod(invokeMethod); return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Lambda.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; using Stashbox.Utils; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this LambdaExpression expression, ILGenerator generator, CompilerContext context) { var lambdaIndex = context.NestedLambdas.IndexAndValueOf(expression, out var lambda); if (lambdaIndex == -1 || context.Target == null || lambda == null) return false; var lambdaClosureIndex = lambdaIndex + (context.Target.Constants.Length - context.NestedLambdas.Length); generator.Emit(OpCodes.Ldarg_0); generator.Emit(OpCodes.Ldfld, Closure.ConstantsField); generator.EmitInteger(lambdaClosureIndex); generator.Emit(OpCodes.Ldelem_Ref); if (lambda.UsesCapturedArgument) { if (context is { IsNestedLambda: false, CapturedArgumentsHolderVariable: { } }) generator.Emit(OpCodes.Ldloc, context.CapturedArgumentsHolderVariable); else generator.Emit(OpCodes.Ldarg_1); } var variables = lambda.ParameterExpressions; var nestedParameters = expression.Parameters.CastToArray(); var nestedContext = context.Clone(variables, true, lambda.UsesCapturedArgument); if (nestedContext.Target == null) return false; var method = new DynamicMethod(string.Empty, expression.ReturnType, nestedContext.HasCapturedVariablesArgument ? new[] { TypeCache.Type, TypeCache.Type }.Append(nestedParameters.GetTypes()) : TypeCache.Type.Append(nestedParameters.GetTypes()), TypeCache.Type, true); var nestedGenerator = method.GetILGenerator(); if (variables.Length > 0) nestedContext.LocalBuilders = variables.BuildLocals(nestedGenerator); if (nestedContext.HasCapturedVariablesArgument) nestedGenerator.CopyParametersToCapturedArgumentsIfAny(nestedContext, nestedParameters); if (!expression.Body.TryEmit(nestedGenerator, nestedContext, nestedParameters)) return false; nestedGenerator.Emit(OpCodes.Ret); if (nestedContext.HasCapturedVariablesArgument) { var delegateArgs = TypeCache.Type .Append(nestedParameters.GetTypes()) .Append(expression.ReturnType); var resultDelegate = method.CreateDelegate(Utils.MapDelegateType(delegateArgs), nestedContext.Target); nestedContext.Target.Constants[lambdaClosureIndex] = resultDelegate; generator.EmitMethod(Utils.GetPartialApplicationMethodInfo(delegateArgs)); } else { var resultDelegate = method.CreateDelegate(expression.Type, nestedContext.Target); nestedContext.Target.Constants[lambdaClosureIndex] = resultDelegate; } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.MemberAccess.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this MemberExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { if (expression.Expression == null) return false; return expression.Expression.TryEmit(generator, context, parameters) && expression.Member.EmitMemberAccess(generator); } private static bool EmitMemberAssign(this MemberInfo member, ILGenerator generator) { switch (member) { case PropertyInfo property: { var setMethod = property.GetSetMethod(true); if (setMethod == null) return false; generator.EmitMethod(setMethod); break; } case FieldInfo field: generator.Emit(field.IsStatic ? OpCodes.Stsfld : OpCodes.Stfld, field); break; } return true; } private static bool EmitMemberAccess(this MemberInfo member, ILGenerator generator) { switch (member) { case PropertyInfo property: { var getMethod = property.GetGetMethod(true); if (getMethod == null) return false; generator.EmitMethod(getMethod); break; } case FieldInfo field: generator.Emit(field.IsStatic ? OpCodes.Ldsfld : OpCodes.Ldfld, field); break; } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.MemberInit.cs ================================================ using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this MemberInitExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { if (!expression.NewExpression.TryEmit(generator, context, parameters)) return false; var length = expression.Bindings.Count; for (var i = 0; i < length; i++) { var binding = expression.Bindings[i]; if (binding.BindingType != MemberBindingType.Assignment) return false; generator.Emit(OpCodes.Dup); if (!((MemberAssignment)binding).Expression.TryEmit(generator, context, parameters)) return false; if (!binding.Member.EmitMemberAssign(generator)) return false; } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.New.cs ================================================ using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this NewExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { if (!expression.Arguments.TryEmit(generator, context, parameters)) return false; generator.Emit(OpCodes.Newobj, expression.Constructor!); return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.NewArrayInit.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this NewArrayExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { var type = expression.Type; var itemType = type.GetEnumerableType(); if (itemType == null) return false; var length = expression.Expressions.Count; generator.EmitInteger(length); generator.Emit(OpCodes.Newarr, itemType); for (var i = 0; i < length; i++) { generator.Emit(OpCodes.Dup); generator.EmitInteger(i); if (!expression.Expressions[i].TryEmit(generator, context, parameters)) return false; if (itemType.IsValueType) generator.Emit(OpCodes.Stelem, itemType); else generator.Emit(OpCodes.Stelem_Ref); } return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.Parameter.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { private static bool TryEmit(this ParameterExpression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { var index = parameters.GetReferenceIndex(expression); if (index != -1) { if (context is { HasClosure: true, HasCapturedVariablesArgument: true }) index += 2; else if (context.HasClosure || context.HasCapturedVariablesArgument) index++; if (context is { IsNestedLambda: false, HasCapturedVariablesArgument: true }) index--; generator.LoadParameter(index); return true; } var definedVariableIndex = context.DefinedVariables.IndexOf(expression); if (definedVariableIndex != -1 && context.LocalBuilders != null) { generator.Emit(OpCodes.Ldloc, context.LocalBuilders[definedVariableIndex]); return true; } var capturedVariableIndex = context.CapturedArguments.IndexOf(expression); if (capturedVariableIndex == -1) return true; generator.Emit(OpCodes.Ldarg_1); generator.EmitInteger(capturedVariableIndex); generator.Emit(OpCodes.Ldelem_Ref); if (expression.Type.IsValueType) generator.Emit(OpCodes.Unbox_Any, expression.Type); return true; } } ================================================ FILE: src/Expressions/Compile/Emitters/Emitter.cs ================================================ using Stashbox.Expressions.Compile.Extensions; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection.Emit; using Stashbox.Utils; namespace Stashbox.Expressions.Compile.Emitters; internal static partial class Emitter { public static bool TryEmit(this Expression expression, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { return expression.NodeType switch { ExpressionType.Call => ((MethodCallExpression)expression).TryEmit(generator, context, parameters), ExpressionType.MemberAccess => ((MemberExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Invoke => ((InvocationExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Parameter => ((ParameterExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Lambda => ((LambdaExpression)expression).TryEmit(generator, context), ExpressionType.Convert => ((UnaryExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Constant => ((ConstantExpression)expression).TryEmit(generator, context), ExpressionType.MemberInit => ((MemberInitExpression)expression).TryEmit(generator, context, parameters), ExpressionType.New => ((NewExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Block => ((BlockExpression)expression).Expressions.TryEmit(generator, context, parameters), ExpressionType.NewArrayInit => ((NewArrayExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Assign => ((BinaryExpression)expression).TryEmit(generator, context, parameters), ExpressionType.Default => ((DefaultExpression)expression).TryEmit(generator), _ => false }; } public static DynamicMethod CreateDynamicMethod(CompilerContext context, Type returnType, params ParameterExpression[] parameters) { return !context.HasClosure ? new DynamicMethod(string.Empty, returnType, parameters.GetTypes(), TypeCache.ExpressionEmitterType, true) : new DynamicMethod(string.Empty, returnType, TypeCache.Type.Append(parameters.GetTypes()), TypeCache.Type, true); } private static bool TryEmit(this IList expressions, ILGenerator generator, CompilerContext context, params ParameterExpression[] parameters) { var length = expressions.Count; for (var i = 0; i < length; i++) if (!expressions[i].TryEmit(generator, context, parameters)) return false; return true; } internal static LocalBuilder[] BuildLocals(this ExpandableArray variables, ILGenerator ilGenerator) { var length = variables.Length; var locals = new LocalBuilder[length]; for (var i = 0; i < length; i++) locals[i] = ilGenerator.DeclareLocal(variables[i].Type); return locals; } } ================================================ FILE: src/Expressions/Compile/ExpressionEmitter.cs ================================================ using Stashbox.Expressions.Compile.Emitters; using Stashbox.Expressions.Compile.Extensions; using System; using System.Linq; using System.Linq.Expressions; using System.Reflection.Emit; namespace Stashbox.Expressions.Compile; internal static class ExpressionEmitter { public static bool TryEmit(this LambdaExpression expression, out Delegate? resultDelegate) => TryEmit(expression.Body, out resultDelegate, expression.Type, expression.ReturnType, expression.Parameters.ToArray()); public static bool TryEmit(this Expression expression, out Delegate? resultDelegate, Type delegateType, Type returnType, params ParameterExpression[] parameters) { resultDelegate = null; var analyzer = new TreeAnalyzer(); if (!analyzer.Analyze(expression, parameters)) return false; var storedObjects = analyzer.Constants.AsArray(); if (analyzer.NestedLambdas.Length > 0) storedObjects = storedObjects.Append(new object[analyzer.NestedLambdas.Length]); var closure = storedObjects.Length == 0 ? null : new Closure(storedObjects); var context = new CompilerContext(closure, analyzer.Constants, analyzer.DefinedVariables, analyzer.CapturedParameters, analyzer.NestedLambdas); var method = Emitter.CreateDynamicMethod(context, returnType, parameters); var generator = method.GetILGenerator(); if (context.HasCapturedVariablesArgument) { context.CapturedArgumentsHolderVariable = generator.PrepareCapturedArgumentsHolderVariable(analyzer.CapturedParameters.Length); generator.CopyParametersToCapturedArgumentsIfAny(context, parameters); } if (analyzer.DefinedVariables.Length > 0) context.LocalBuilders = analyzer.DefinedVariables.BuildLocals(generator); if (!expression.TryEmit(generator, context, parameters)) return false; generator.Emit(OpCodes.Ret); resultDelegate = context.HasClosure ? method.CreateDelegate(delegateType, context.Target) : method.CreateDelegate(delegateType); return true; } } ================================================ FILE: src/Expressions/Compile/Extensions/CollectionExtensions.cs ================================================ using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq.Expressions; namespace Stashbox.Expressions.Compile.Extensions; internal static class CollectionExtensions { public static Type[] GetTypes(this IList parameters) { var count = parameters.Count; switch (count) { case 0: return TypeCache.EmptyTypes; case 1: return [parameters[0].Type]; } var types = new Type[count]; for (var i = 0; i < count; i++) types[i] = parameters[i].Type; return types; } public static Type[] Append(this Type type, Type[] types) { var count = types.Length; if (count == 0) return [type]; var arr = new Type[count + 1]; arr[0] = type; Array.Copy(types, 0, arr, 1, count); return arr; } public static Type[] Append(this Type[] types, Type type) { var count = types.Length; if (count == 0) return [type]; var arr = new Type[count + 1]; Array.Copy(types, 0, arr, 0, count); arr[count] = type; return arr; } public static TItem[] Append(this TItem[] types, TItem[] others) { if (others.Length == 0) return types; if (types.Length == 0) return others; var length = others.Length + types.Length; var arr = new TItem[length]; Array.Copy(types, 0, arr, 0, types.Length); Array.Copy(others, 0, arr, types.Length, others.Length); return arr; } } ================================================ FILE: src/Expressions/Compile/Extensions/ILGeneratorExtensions.cs ================================================ using System; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using Stashbox.Utils; namespace Stashbox.Expressions.Compile.Extensions; internal static class ILGeneratorExtensions { public static LocalBuilder PrepareCapturedArgumentsHolderVariable(this ILGenerator generator, int capturedArgumentsCount) { var local = generator.DeclareLocal(TypeCache.Type); generator.EmitInteger(capturedArgumentsCount); generator.Emit(OpCodes.Newarr, TypeCache.Type); generator.Emit(OpCodes.Stloc, local); return local; } public static void CopyParametersToCapturedArgumentsIfAny(this ILGenerator generator, CompilerContext context, Expression[] parameters) { var length = context.CapturedArguments.Length; for (var i = 0; i < length; i++) { var arg = context.CapturedArguments[i]; var paramIndex = parameters.GetReferenceIndex(arg); if (paramIndex == -1) continue; generator.LoadCapturedArgumentHolder(context); generator.EmitInteger(i); generator.LoadParameter(paramIndex + (context.IsNestedLambda ? 2 : 1)); if (arg.Type.IsValueType) generator.Emit(OpCodes.Box, arg.Type); generator.Emit(OpCodes.Stelem_Ref); } } public static bool EmitMethod(this ILGenerator generator, MethodInfo info) { generator.Emit(info.IsVirtual ? OpCodes.Callvirt : OpCodes.Call, info); return true; } public static void LoadCapturedArgumentHolder(this ILGenerator generator, CompilerContext context) { if (context is { IsNestedLambda: false, CapturedArgumentsHolderVariable: { } }) generator.Emit(OpCodes.Ldloc, context.CapturedArgumentsHolderVariable); else generator.Emit(context.HasClosure ? OpCodes.Ldarg_1 : OpCodes.Ldarg_0); } public static void EmitInteger(this ILGenerator generator, int intValue) { switch (intValue) { case 0: generator.Emit(OpCodes.Ldc_I4_0); break; case 1: generator.Emit(OpCodes.Ldc_I4_1); break; case 2: generator.Emit(OpCodes.Ldc_I4_2); break; case 3: generator.Emit(OpCodes.Ldc_I4_3); break; case 4: generator.Emit(OpCodes.Ldc_I4_4); break; case 5: generator.Emit(OpCodes.Ldc_I4_5); break; case 6: generator.Emit(OpCodes.Ldc_I4_6); break; case 7: generator.Emit(OpCodes.Ldc_I4_7); break; case 8: generator.Emit(OpCodes.Ldc_I4_8); break; default: if (intValue is >= sbyte.MinValue and <= sbyte.MaxValue) generator.Emit(OpCodes.Ldc_I4_S, (sbyte)intValue); else generator.Emit(OpCodes.Ldc_I4, intValue); break; } } public static void LoadParameter(this ILGenerator generator, int index) { switch (index) { case 0: generator.Emit(OpCodes.Ldarg_0); break; case 1: generator.Emit(OpCodes.Ldarg_1); break; case 2: generator.Emit(OpCodes.Ldarg_2); break; case 3: generator.Emit(OpCodes.Ldarg_3); break; default: if (index <= byte.MaxValue) generator.Emit(OpCodes.Ldarg_S, (byte)index); else generator.Emit(OpCodes.Ldarg, index); break; } } public static void InitValueType(this ILGenerator generator, Type type) { var lb = generator.DeclareLocal(type); generator.Emit(OpCodes.Ldloca, lb); generator.Emit(OpCodes.Initobj, type); generator.Emit(OpCodes.Ldloc, lb); } public static bool TryEmitNumberConstant(this ILGenerator generator, Type type, object value) { if (type == TypeCache.Type) generator.EmitInteger((int)value); else if (type == TypeCache.Type) unchecked { generator.EmitInteger((int)(uint)value); } else if (type == TypeCache.Type) generator.EmitInteger((char)value); else if (type == TypeCache.Type) generator.EmitInteger((short)value); else if (type == TypeCache.Type) generator.EmitInteger((byte)value); else if (type == TypeCache.Type) generator.EmitInteger((ushort)value); else if (type == TypeCache.Type) generator.EmitInteger((sbyte)value); else if (type == TypeCache.Type) generator.Emit(OpCodes.Ldc_I8, (long)value); else if (type == TypeCache.Type) unchecked { generator.Emit(OpCodes.Ldc_I8, (long)(ulong)value); } else if (type == TypeCache.Type) generator.Emit(OpCodes.Ldc_R4, (float)value); else if (type == TypeCache.Type) generator.Emit((bool)value ? OpCodes.Ldc_I4_1 : OpCodes.Ldc_I4_0); else if (type == TypeCache.Type) generator.Emit(OpCodes.Ldc_R8, (double)value); else return false; return true; } } ================================================ FILE: src/Expressions/Compile/NestedLambda.cs ================================================ using Stashbox.Utils.Data; using System.Linq.Expressions; namespace Stashbox.Expressions.Compile; internal class NestedLambda { public readonly ExpandableArray ParameterExpressions; public readonly bool UsesCapturedArgument; public NestedLambda(ExpandableArray parameterExpressions, bool usesCapturedArgument) { this.ParameterExpressions = parameterExpressions; this.UsesCapturedArgument = usesCapturedArgument; } } ================================================ FILE: src/Expressions/Compile/TreeAnalyzer.cs ================================================ using Stashbox.Utils.Data; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Stashbox.Expressions.Compile; internal class TreeAnalyzer { private readonly bool isNestedLambda; public readonly ExpandableArray CapturedParameters; public readonly ExpandableArray DefinedVariables; public readonly ExpandableArray NestedLambdas; public readonly ExpandableArray Constants; public TreeAnalyzer() { this.CapturedParameters = []; this.DefinedVariables = []; this.NestedLambdas = []; this.Constants = []; } private TreeAnalyzer(bool isNestedLambda, ExpandableArray capturedParameters, ExpandableArray definedVariables, ExpandableArray nestedLambdas, ExpandableArray constants) { this.isNestedLambda = isNestedLambda; CapturedParameters = capturedParameters; DefinedVariables = definedVariables; NestedLambdas = nestedLambdas; Constants = constants; } public bool Analyze(Expression expression, params ParameterExpression[] parameters) { switch (expression.NodeType) { case ExpressionType.Parameter: if (parameters.ContainsReference(expression) || this.DefinedVariables.IndexOf(expression) != -1) return true; if (!this.isNestedLambda) return false; this.CapturedParameters.AddOrKeep((ParameterExpression)expression); return true; case ExpressionType.Lambda: var lambda = (LambdaExpression)expression; var analyzer = this.Clone(true); if (!analyzer.Analyze(lambda.Body, lambda.Parameters.CastToArray())) return false; this.NestedLambdas.AddOrKeep(lambda, new NestedLambda(analyzer.DefinedVariables, analyzer.CapturedParameters.Length > 0)); return true; case ExpressionType.MemberAccess when expression is MemberExpression memberExpression && memberExpression.Expression != null: return this.Analyze(memberExpression.Expression, parameters); case ExpressionType.Constant: var constant = (ConstantExpression)expression; if (constant.Value == null || Utils.IsInPlaceEmittableConstant(constant.Type, constant.Value)) return true; this.Constants.AddOrKeep(constant.Value); return true; case ExpressionType.New: return this.Analyze(((NewExpression)expression).Arguments, parameters); case ExpressionType.MemberInit: var memberInit = (MemberInitExpression)expression; return this.Analyze(memberInit.NewExpression, parameters) && this.Analyze(memberInit.Bindings, parameters); case ExpressionType.Block: var block = (BlockExpression)expression; var blockVarLength = block.Variables.Count; for (var i = 0; i < blockVarLength; i++) this.DefinedVariables.AddOrKeep(block.Variables[i]); return this.Analyze(block.Expressions, parameters); case ExpressionType.Conditional: var condition = (ConditionalExpression)expression; return this.Analyze(condition.Test, parameters) && this.Analyze(condition.IfTrue, parameters) && this.Analyze(condition.IfFalse, parameters); case ExpressionType.Default: return true; case ExpressionType.Call: var call = (MethodCallExpression)expression; return (call.Object == null || this.Analyze(call.Object, parameters)) && this.Analyze(call.Arguments, parameters); case ExpressionType.Invoke: var invoke = (InvocationExpression)expression; return this.Analyze(invoke.Expression, parameters) && this.Analyze(invoke.Arguments, parameters); case ExpressionType.NewArrayInit: return this.Analyze(((NewArrayExpression)expression).Expressions, parameters); default: switch (expression) { case UnaryExpression unaryExpression: return this.Analyze(unaryExpression.Operand, parameters); case BinaryExpression binaryExpression: return this.Analyze(binaryExpression.Left, parameters) && this.Analyze(binaryExpression.Right, parameters); } break; } return false; } private TreeAnalyzer Clone(bool isLambda = false) => new(isLambda, this.CapturedParameters, [], this.NestedLambdas, this.Constants); private bool Analyze(IList expressions, params ParameterExpression[] parameters) { var length = expressions.Count; for (var i = 0; i < length; i++) if (!this.Analyze(expressions[i], parameters)) return false; return true; } private bool Analyze(IList bindings, params ParameterExpression[] parameters) { var length = bindings.Count; for (var i = 0; i < length; i++) { var binding = bindings[i]; if (binding.BindingType != MemberBindingType.Assignment || !this.Analyze(((MemberAssignment)binding).Expression, parameters)) return false; } return true; } } ================================================ FILE: src/Expressions/Compile/Utils.cs ================================================ using System; using System.Linq; using System.Reflection; using Stashbox.Utils; namespace Stashbox.Expressions.Compile; internal static class Utils { public static bool IsInPlaceEmittableConstant(Type type, object value) => type.IsPrimitive || type.IsEnum || value is string or Type; public static Type MapDelegateType(Type[] paramTypes) { return paramTypes.Length switch { 1 => typeof(Func<>).MakeGenericType(paramTypes), 2 => typeof(Func<,>).MakeGenericType(paramTypes), 3 => typeof(Func<,,>).MakeGenericType(paramTypes), 4 => typeof(Func<,,,>).MakeGenericType(paramTypes), 5 => typeof(Func<,,,,>).MakeGenericType(paramTypes), 6 => typeof(Func<,,,,,>).MakeGenericType(paramTypes), 7 => typeof(Func<,,,,,,>).MakeGenericType(paramTypes), 8 => typeof(Func<,,,,,,,>).MakeGenericType(paramTypes), _ => throw new NotSupportedException($"Too many func parameters {paramTypes.Length}.") }; } internal static MethodInfo GetPartialApplicationMethodInfo(Type[] types) => PartialApplication.Methods[types.Length - 2].MakeGenericMethod(types); } internal static class PartialApplication { public static readonly MethodInfo[] Methods = typeof(PartialApplication) .GetMethods().CastToArray(); public static Func ApplyPartial(Func f, TV v) => () => f(v); public static Func ApplyPartial(Func f, TV v) => t1 => f(v, t1); public static Func ApplyPartial(Func f, TV v) => (t1, t2) => f(v, t1, t2); public static Func ApplyPartial(Func f, TV v) => (t1, t2, t3) => f(v, t1, t2, t3); public static Func ApplyPartial(Func f, TV v) => (t1, t2, t3, t4) => f(v, t1, t2, t3, t4); public static Func ApplyPartial(Func f, TV v) => (t1, t2, t3, t4, t5) => f(v, t1, t2, t3, t4, t5); public static Func ApplyPartial(Func f, TV v) => (t1, t2, t3, t4, t5, t6) => f(v, t1, t2, t3, t4, t5, t6); } ================================================ FILE: src/Expressions/ExpressionBuilder.Default.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System.Collections.Generic; using System.Linq.Expressions; namespace Stashbox.Expressions; internal static partial class ExpressionBuilder { private static Expression? GetExpressionForDefault(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (resolutionContext.CircularDependencyBarrier.Contains(serviceRegistration.RegistrationId)) throw ResolutionFailedException.CreateWithDesiredExceptionType(serviceRegistration.ImplementationType, serviceRegistration.Name, $"Circular dependency was detected while resolving '{serviceRegistration.ImplementationType.FullName}'.", externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); resolutionContext.CircularDependencyBarrier.Add(serviceRegistration.RegistrationId); var result = PrepareDefaultExpression(serviceRegistration, resolutionContext, typeInformation); resolutionContext.CircularDependencyBarrier.Pop(); return result; } private static Expression? PrepareDefaultExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var definedScopeName = serviceRegistration.Options.GetOrDefault(RegistrationOption.DefinedScopeName); if (definedScopeName != null) { var variable = TypeCache.Type.AsVariable(); var newScope = resolutionContext.CurrentScopeParameter .CallMethod(Constants.BeginScopeMethod, definedScopeName.AsConstant(), true.AsConstant()); var newScopeContext = resolutionContext.BeginNewScopeContext(new ReadOnlyKeyValue(definedScopeName, variable)); resolutionContext.AddDefinedVariable(variable); resolutionContext.AddInstruction(variable.AssignTo(newScope.ConvertTo(TypeCache.Type))); var expression = ExpressionFactory.ConstructExpression(serviceRegistration, newScopeContext, typeInformation); foreach (var definedVariable in newScopeContext.DefinedVariables.Walk()) resolutionContext.AddDefinedVariable(definedVariable); foreach (var instruction in newScopeContext.SingleInstructions) resolutionContext.AddInstruction(instruction); return expression; } return ExpressionFactory.ConstructExpression(serviceRegistration, resolutionContext, typeInformation); } } ================================================ FILE: src/Expressions/ExpressionBuilder.Factory.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Linq.Expressions; using Stashbox.Utils; namespace Stashbox.Expressions; internal static partial class ExpressionBuilder { private static Expression GetExpressionForFactory(ServiceRegistration serviceRegistration, FactoryOptions factoryOptions, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (resolutionContext.CircularDependencyBarrier.Contains(serviceRegistration.RegistrationId)) throw ResolutionFailedException.CreateWithDesiredExceptionType(serviceRegistration.ImplementationType, serviceRegistration.Name, $"Circular dependency was detected while resolving '{serviceRegistration.ImplementationType.FullName}'.", externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); resolutionContext.CircularDependencyBarrier.Add(serviceRegistration.RegistrationId); var parameters = GetFactoryParameters(factoryOptions, resolutionContext, typeInformation); var expression = ConstructFactoryExpression(factoryOptions, parameters); var result = ExpressionFactory.ConstructBuildUpExpression(serviceRegistration, resolutionContext, expression, typeInformation); resolutionContext.CircularDependencyBarrier.Pop(); return result; } private static Expression ConstructFactoryExpression(FactoryOptions factoryOptions, IEnumerable parameters) { if (factoryOptions.IsFactoryDelegateACompiledLambda || factoryOptions.Factory.IsCompiledLambda()) return factoryOptions.Factory.InvokeDelegate(parameters); var method = factoryOptions.Factory.GetMethod(); return method.IsStatic ? method.CallStaticMethod(parameters) : method.CallMethod(factoryOptions.Factory.Target.AsConstant(), parameters); } private static IEnumerable GetFactoryParameters(FactoryOptions factoryOptions, ResolutionContext resolutionContext, TypeInformation typeInformation) { var length = factoryOptions.FactoryParameters.Length; for (var i = 0; i < length - 1; i++) { var type = factoryOptions.FactoryParameters[i]; if (type == TypeCache.Type) { yield return typeInformation.AsConstant(); } else { var typeInfo = new TypeInformation(factoryOptions.FactoryParameters[i], null); yield return resolutionContext.CurrentContainerContext.ResolutionStrategy.BuildExpressionForType(resolutionContext, typeInfo).ServiceExpression; } } } } ================================================ FILE: src/Expressions/ExpressionBuilder.Func.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Stashbox.Expressions; internal static partial class ExpressionBuilder { private static Expression GetExpressionForFunc(ServiceRegistration serviceRegistration, Delegate func, ResolutionContext resolutionContext) { var internalMethodInfo = func.GetMethod(); var parameters = GetFuncParametersWithScope(serviceRegistration.ImplementationType.GetMethod("Invoke")!.GetParameters(), resolutionContext); if (func.IsCompiledLambda()) return func.InvokeDelegate(parameters) .AsLambda(parameters.Take(parameters.Length - 1).Cast()); var expr = internalMethodInfo.IsStatic ? internalMethodInfo.CallStaticMethod(parameters) : func.Target.AsConstant().CallMethod(internalMethodInfo, parameters); return expr.AsLambda(parameters.Take(parameters.Length - 1).Cast()); } private static Expression[] GetFuncParametersWithScope(IList parameterInfos, ResolutionContext resolutionContext) { var length = parameterInfos.Count; var expressions = new Expression[length + 1]; for (var i = 0; i < length; i++) { expressions[i] = parameterInfos[i].ParameterType.AsParameter(); } #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER expressions[^1] = resolutionContext.CurrentScopeParameter; #else expressions[expressions.Length - 1] = resolutionContext.CurrentScopeParameter; #endif return expressions; } } ================================================ FILE: src/Expressions/ExpressionBuilder.cs ================================================ using Stashbox.Lifetime; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System; using System.Linq.Expressions; using System.Collections.Generic; namespace Stashbox.Expressions; internal static partial class ExpressionBuilder { internal static Expression? BuildExpressionForRegistration(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var expression = BuildExpressionByRegistrationType(serviceRegistration, resolutionContext, typeInformation); if (expression == null) return null; if (serviceRegistration.Options != null && !serviceRegistration.IsInstance()) { if (serviceRegistration.Options.TryGetValue(RegistrationOption.AsyncInitializer, out var asyncInitializer)) expression = resolutionContext.CurrentScopeParameter.CallMethod(Constants.AddWithAsyncInitializerMethod, expression, asyncInitializer.AsConstant()); if (serviceRegistration.Options.TryGetValue(RegistrationOption.Finalizer, out var finalizer)) expression = resolutionContext.CurrentScopeParameter.CallMethod(Constants.AddWithFinalizerMethod, expression, finalizer.AsConstant()); } if (!ShouldHandleDisposal(resolutionContext.CurrentContainerContext, serviceRegistration) || !expression.Type.IsDisposable()) return expression; return resolutionContext.RequestConfiguration.RequiresRequestContext ? resolutionContext.CurrentScopeParameter.CallMethod(Constants.AddRequestContextAwareDisposalMethod, expression, resolutionContext.RequestContextParameter) : resolutionContext.CurrentScopeParameter.CallMethod(Constants.AddDisposalMethod, expression); } private static Expression? BuildExpressionByRegistrationType(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { resolutionContext = resolutionContext.FallBackToRequestInitiatorIfNeeded(); var options = serviceRegistration.Options?.GetOrDefault(RegistrationOption.RegistrationTypeOptions); return options switch { FactoryOptions factoryOptions => GetExpressionForFactory(serviceRegistration, factoryOptions, resolutionContext, typeInformation), InstanceOptions instanceRegistration => instanceRegistration.IsWireUp ? ExpressionFactory.ConstructBuildUpExpression(serviceRegistration, resolutionContext, instanceRegistration.ExistingInstance.AsConstant(), typeInformation) : instanceRegistration.ExistingInstance.AsConstant(), Delegate func => GetExpressionForFunc(serviceRegistration, func, resolutionContext), _ => GetExpressionForDefault(serviceRegistration, resolutionContext, typeInformation) }; } private static bool ShouldHandleDisposal(IContainerContext containerContext, ServiceRegistration serviceRegistration) { if (serviceRegistration.Options.IsOn(RegistrationOption.IsLifetimeExternallyOwned) || serviceRegistration.IsInstance()) return false; return containerContext.ContainerConfiguration.TrackTransientsForDisposalEnabled || serviceRegistration.Lifetime is not TransientLifetime; } } ================================================ FILE: src/Expressions/ExpressionFactory.Member.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Resolution.Extensions; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Stashbox.Expressions; internal static partial class ExpressionFactory { private static IEnumerable GetMemberExpressions( IEnumerable members, ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, Expression instance, TypeInformation typeInformation) => from member in members let expression = GetMemberExpression(member, serviceRegistration, resolutionContext, typeInformation) where expression != null select instance.Member(member).AssignTo(expression); private static IEnumerable GetMemberBindings( IEnumerable members, ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => from member in members let expression = GetMemberExpression(member, serviceRegistration, resolutionContext, typeInformation) where expression != null select member.AssignTo(expression); private static Expression GetMemberExpression( MemberInfo member, ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var memberTypeInfo = member.AsTypeInformation(serviceRegistration, typeInformation, resolutionContext.CurrentContainerContext.ContainerConfiguration); var injectionParameter = serviceRegistration?.Options.GetOrDefault>>(RegistrationOption.InjectionParameters)?.SelectInjectionParameterOrDefault(memberTypeInfo); if (injectionParameter != null) return injectionParameter; var serviceContext = resolutionContext.CurrentContainerContext .ResolutionStrategy.BuildExpressionForType(resolutionContext, memberTypeInfo); if (!serviceContext.IsEmpty() || resolutionContext.NullResultAllowed) return serviceContext.ServiceExpression; var memberType = member is PropertyInfo ? "property" : "field"; throw ResolutionFailedException.CreateWithDesiredExceptionType(memberTypeInfo.ParentType, null, $"Unresolvable {memberType}: ({memberTypeInfo.Type.FullName}){memberTypeInfo.ParameterOrMemberName}{(memberTypeInfo.DependencyName != null ? $" with name '{memberTypeInfo.DependencyName}'" : string.Empty)}.", externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); } } ================================================ FILE: src/Expressions/ExpressionFactory.Method.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Resolution.Extensions; using Stashbox.Utils; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Text; namespace Stashbox.Expressions; internal static partial class ExpressionFactory { private static IEnumerable CreateParameterExpressionsForMethod( ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, MethodBase method, TypeInformation typeInformation) { var parameters = method.GetParameters(); var paramLength = parameters.Length; for (var i = 0; i < paramLength; i++) { var parameter = parameters[i].AsTypeInformation(method.DeclaringType, typeInformation, serviceRegistration, resolutionContext.CurrentContainerContext.ContainerConfiguration); var injectionParameter = serviceRegistration?.Options.GetOrDefault>>(RegistrationOption.InjectionParameters)?.SelectInjectionParameterOrDefault(parameter); if (injectionParameter != null) yield return injectionParameter; yield return resolutionContext.CurrentContainerContext.ResolutionStrategy.BuildExpressionForType( resolutionContext, parameter).ServiceExpression ?? throw ResolutionFailedException.CreateWithDesiredExceptionType(method.DeclaringType, serviceRegistration?.Name, $"Method {method} found with unresolvable parameter: ({parameter.Type}){parameter.ParameterOrMemberName}", externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); } } private static ConstructorInfo? SelectConstructor( Type typeToConstruct, ServiceRegistration? serviceRegistration, TypeInformation typeInformation, ResolutionContext resolutionContext, IEnumerable constructorsEnumerable, out Expression[] parameterExpressions) { ConstructorInfo[]? resultConstructors; parameterExpressions = TypeCache.EmptyArray(); if (resolutionContext.ParameterExpressions.Length > 0) { var constructors = constructorsEnumerable.CastToArray(); var containingFactoryParameter = constructors .Where(c => Array.Exists(c.GetParameters(),p => resolutionContext.ParameterExpressions .Any(pe => Array.Exists(pe, item => item.I2.Type == p.ParameterType || item.I2.Type.Implements(p.ParameterType))))) .CastToArray(); var everythingElse = constructors.Except(containingFactoryParameter); resultConstructors = containingFactoryParameter.Concat(everythingElse).CastToArray(); } else resultConstructors = constructorsEnumerable.CastToArray(); if (resultConstructors.Length == 0) throw ResolutionFailedException.CreateWithDesiredExceptionType(typeToConstruct, serviceRegistration?.Name, "No public constructor found. Make sure there is at least one public constructor on the type.", externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); var checkedConstructors = new Dictionary(); var unknownTypeCheckDisabledContext = resolutionContext.CurrentContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled ? resolutionContext.BeginUnknownTypeCheckDisabledContext() : resolutionContext; var length = resultConstructors.Length; for (var i = 0; i < length; i++) { var constructor = resultConstructors[i]; if (TryBuildMethod(constructor, serviceRegistration, unknownTypeCheckDisabledContext, typeInformation, out var failedParameter, out parameterExpressions)) return constructor; checkedConstructors.Add(constructor, failedParameter); } if (resolutionContext.CurrentContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled) { for (var i = 0; i < length; i++) { var constructor = resultConstructors[i]; if (TryBuildMethod(constructor, serviceRegistration, resolutionContext, typeInformation, out _, out parameterExpressions)) return constructor; } } var mustThrowBecauseOfUnresolvableNamedDependency = resolutionContext.CurrentContainerContext.ContainerConfiguration.ForceThrowWhenNamedDependencyIsNotResolvable && checkedConstructors.Any(ctor => ctor.Value.DependencyName != null || ctor.Value.HasDependencyNameAttribute); if (!mustThrowBecauseOfUnresolvableNamedDependency && resolutionContext.NullResultAllowed) return null; var stringBuilder = new StringBuilder(); foreach (var checkedConstructor in checkedConstructors) stringBuilder.AppendLine($"Constructor {checkedConstructor.Key} found with unresolvable parameter: ({checkedConstructor.Value.Type.FullName}){checkedConstructor.Value.ParameterOrMemberName}."); throw ResolutionFailedException.CreateWithDesiredExceptionType(typeToConstruct, serviceRegistration?.Name, stringBuilder.ToString(), externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); } private static IEnumerable CreateMethodExpressions( IEnumerable methods, ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, Expression instance, TypeInformation typeInformation) { foreach (var method in methods) { var parameters = method.GetParameters(); if (parameters.Length == 0) yield return instance.CallMethod(method); else yield return instance.CallMethod(method, CreateParameterExpressionsForMethod( serviceRegistration, resolutionContext, method, typeInformation)); } } private static bool TryBuildMethod( MethodBase method, ServiceRegistration? serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation, out TypeInformation failedParameter, out Expression[] parameterExpressions) { var parameters = method.GetParameters(); var paramLength = parameters.Length; parameterExpressions = new Expression[paramLength]; failedParameter = TypeInformation.Empty; for (var i = 0; i < paramLength; i++) { var parameter = parameters[i].AsTypeInformation(method.DeclaringType, typeInformation, serviceRegistration, resolutionContext.CurrentContainerContext.ContainerConfiguration); var injectionParameter = serviceRegistration?.Options.GetOrDefault>>(RegistrationOption.InjectionParameters)?.SelectInjectionParameterOrDefault(parameter); if (injectionParameter != null) { parameterExpressions[i] = injectionParameter; continue; } var serviceContext = resolutionContext.CurrentContainerContext.ResolutionStrategy .BuildExpressionForType(resolutionContext, parameter); if (!serviceContext.IsEmpty()) { parameterExpressions[i] = serviceContext.ServiceExpression; continue; } failedParameter = parameter; return false; } return true; } } ================================================ FILE: src/Expressions/ExpressionFactory.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Reflection; namespace Stashbox.Expressions; internal static partial class ExpressionFactory { public static Expression ConstructBuildUpExpression( ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, Expression instance, TypeInformation typeInformation) { if (instance.Type != serviceRegistration.ImplementationType) instance = instance.ConvertTo(serviceRegistration.ImplementationType); var methods = serviceRegistration.ImplementationType.GetUsableMethods(); var members = serviceRegistration.ImplementationType.GetUsableMembers(serviceRegistration, resolutionContext.CurrentContainerContext.ContainerConfiguration); var initializer = serviceRegistration.Options.GetOrDefault(RegistrationOption.Initializer); if (members.Length == 0 && methods.Length == 0 && initializer == null) return instance; var variable = instance.Type.AsVariable(); var assign = variable.AssignTo(instance); var lines = new ExpandableArray { assign }; lines.AddRange(GetMemberExpressions(members, serviceRegistration, resolutionContext, variable, typeInformation)); lines.AddRange(CreateMethodExpressions(methods, serviceRegistration, resolutionContext, variable, typeInformation)); if (initializer != null) lines.Add(initializer.AsConstant() .CallMethod(initializer.GetType().GetMethod("Invoke")!, variable, resolutionContext.CurrentScopeParameter)); lines.Add(variable); return lines.AsBlock(variable); } public static Expression ConstructBuildUpExpression( ResolutionContext resolutionContext, Expression instance, TypeInformation typeInformation) { var type = instance.Type; var methods = type.GetUsableMethods(); var members = type.GetUsableMembers(null, resolutionContext.CurrentContainerContext.ContainerConfiguration); if (members.Length == 0 && methods.Length == 0) return instance; var variable = type.AsVariable(); var assign = variable.AssignTo(instance); var lines = new ExpandableArray { assign }; lines.AddRange(GetMemberExpressions(members, null, resolutionContext, variable, typeInformation)); lines.AddRange(CreateMethodExpressions(methods, null, resolutionContext, instance, typeInformation)); lines.Add(variable); return lines.AsBlock(variable); } public static Expression? ConstructExpression( ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var constructors = serviceRegistration.ImplementationType.GetConstructors(); var initExpression = CreateInitExpression( serviceRegistration.ImplementationType, serviceRegistration, typeInformation, resolutionContext, constructors); if (initExpression == null) return null; var methods = serviceRegistration.ImplementationType.GetUsableMethods(); var members = serviceRegistration.ImplementationType.GetUsableMembers(serviceRegistration, resolutionContext.CurrentContainerContext.ContainerConfiguration); if (members.Length > 0) initExpression = initExpression.InitMembers(GetMemberBindings(members, serviceRegistration, resolutionContext, typeInformation)); var initializer = serviceRegistration.Options.GetOrDefault(RegistrationOption.Initializer); if (methods.Length == 0 && initializer == null) return initExpression; var variable = initExpression.Type.AsVariable(); var assign = variable.AssignTo(initExpression); var lines = new ExpandableArray { assign }; lines.AddRange(CreateMethodExpressions(methods, serviceRegistration, resolutionContext, variable, typeInformation)); if (initializer != null) lines.Add(initializer.AsConstant() .CallMethod(initializer.GetType().GetMethod("Invoke")!, variable, resolutionContext.CurrentScopeParameter)); lines.Add(variable); return lines.AsBlock(variable); } public static Expression? ConstructExpression( ResolutionContext resolutionContext, TypeInformation typeInformation) { var methods = typeInformation.Type.GetUsableMethods(); var members = typeInformation.Type.GetUsableMembers(null, resolutionContext.CurrentContainerContext.ContainerConfiguration); if (SelectConstructor( typeInformation.Type, null, typeInformation, resolutionContext, typeInformation.Type.GetConstructors(), out var parameters)?.MakeNew(parameters) is not Expression initExpression) return null; if (members.Length > 0) initExpression = initExpression.InitMembers(GetMemberBindings(members, null, resolutionContext, typeInformation)); if (methods.Length == 0) return initExpression; var variable = initExpression.Type.AsVariable(); var assign = variable.AssignTo(initExpression); var lines = new ExpandableArray { assign }; lines.AddRange(CreateMethodExpressions(methods, null, resolutionContext, variable, typeInformation)); lines.Add(variable); return lines.AsBlock(variable); } private static Expression? CreateInitExpression( Type typeToConstruct, ServiceRegistration serviceRegistration, TypeInformation typeInformation, ResolutionContext resolutionContext, IEnumerable constructors) { var rule = resolutionContext.CurrentContainerContext.ContainerConfiguration.ConstructorSelectionRule; var constructorOptions = serviceRegistration.Options.GetOrDefault(RegistrationOption.ConstructorOptions); if (constructorOptions != null) { if (constructorOptions.ConstructorArguments != null) return constructorOptions.SelectedConstructor .MakeNew(constructorOptions.ConstructorArguments.Select(Expression.Constant)); return constructorOptions.SelectedConstructor.MakeNew( CreateParameterExpressionsForMethod( serviceRegistration, resolutionContext, constructorOptions.SelectedConstructor, typeInformation)); } var constructorSelectionRule = serviceRegistration.Options.GetOrDefault, IEnumerable>>(RegistrationOption.ConstructorSelectionRule); if (constructorSelectionRule != null) rule = constructorSelectionRule; constructors = rule(constructors); return SelectConstructor(typeToConstruct, serviceRegistration, typeInformation, resolutionContext, constructors, out var parameters)?.MakeNew(parameters); } } ================================================ FILE: src/Extensions/AssemblyExtensions.cs ================================================ using System.Collections.Generic; using System.Linq; namespace System.Reflection; internal static class AssemblyExtensions { public static IEnumerable CollectTypes(this Assembly assembly) => assembly.ExportedTypes.Concat(assembly.DefinedTypes.Select(typeInfo => typeInfo.AsType())).Distinct(); } ================================================ FILE: src/Extensions/CollectionExtensions.cs ================================================ using Stashbox.Registration; namespace System.Collections.Generic; internal static class CollectionExtensions { public static TResult? GetOrDefault(this Dictionary? dict, RegistrationOption key) { if ((dict?.TryGetValue(key, out var value) ?? false) && value is TResult result) return result; return default; } public static object? GetOrDefault(this Dictionary? dict, RegistrationOption key) { if ((dict?.TryGetValue(key, out var value) ?? false)) return value; return default; } public static bool TryGet(this Dictionary? dict, RegistrationOption key, out object? value) { if (dict?.TryGetValue(key, out var objValue) ?? false) { value = objValue; return true; } value = default; return false; } public static bool IsOn(this Dictionary? dict, RegistrationOption key) => (dict?.ContainsKey(key) ?? false) && dict[key] is true; } ================================================ FILE: src/Extensions/EnumerableExtensions.cs ================================================ using Stashbox.Utils.Data; using System.Collections.Generic; using System.Linq.Expressions; namespace System.Linq; internal static class EnumerableExtensions { public static TEnumerable[] CastToArray(this IEnumerable enumerable) => enumerable is TEnumerable[] array ? array : enumerable.ToArray(); public static T[] Append(this T[] array, T item) { var count = array.Length; if (count == 0) return [item]; var arr = new T[count + 1]; Array.Copy(array, arr, count); arr[count] = item; return arr; } public static bool ContainsReference(this TElement[] array, TElement element) where TElement : class => array.GetReferenceIndex(element) != -1; public static int GetReferenceIndex(this TElement[] array, TElement element) where TElement : class { if (array.Length == 0) return -1; var length = array.Length; if (length == 1) return ReferenceEquals(array[0], element) ? 0 : -1; for (var i = 0; i < length; i++) if (ReferenceEquals(array[i], element)) return i; return -1; } public static TResult LastElement(this TResult[] source) => #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER source[^1]; #else source[source.Length - 1]; #endif public static ParameterExpression[] AsParameters(this Type[] source) { var length = source.Length; var result = new ParameterExpression[length]; for (var i = 0; i < length; i++) result[i] = source[i].AsParameter(); return result; } public static Pair[] AsParameterPairs(this ParameterExpression[] source) { var length = source.Length; var result = new Pair[length]; for (var i = 0; i < length; i++) result[i] = new Pair(false, source[i]); return result; } public static Stashbox.Utils.Data.Stack AsStack(this IEnumerable enumerable) => Stashbox.Utils.Data.Stack.FromEnumerable(enumerable); } ================================================ FILE: src/Extensions/ExpressionExtensions.cs ================================================ using Stashbox; using Stashbox.Configuration; using Stashbox.Expressions.Compile; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data; using System.Collections.Generic; using System.Reflection; namespace System.Linq.Expressions; /// /// Holds the extension methods. /// public static class ExpressionExtensions { private static Expression PostProcess(this Expression expression) { if (expression.NodeType == ExpressionType.Convert && expression is UnaryExpression unaryExpression && unaryExpression.Operand.Type == TypeCache.Type) return unaryExpression; return expression.Type.IsValueType ? expression.ConvertTo(TypeCache.Type) : expression; } /// /// Compiles an to a of , , and . /// /// The expression. /// The resolution context. /// The container configuration. /// The compiled delegate. public static Func CompileDelegate(this Expression expression, ResolutionContext resolutionContext, ContainerConfiguration containerConfiguration) { expression = expression.PostProcess(); if (expression is ConstantExpression { Value: { } } constantExpression) { var instance = constantExpression.Value; return (_, _) => instance; } if (!resolutionContext.DefinedVariables.IsEmpty) { resolutionContext.SingleInstructions.Add(expression); expression = resolutionContext.SingleInstructions.AsBlock(resolutionContext.DefinedVariables.Walk()); } if (containerConfiguration.ExternalExpressionCompiler != null) return (Func)containerConfiguration.ExternalExpressionCompiler( expression.AsLambda(resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter)); if (!expression.TryEmit(out var factory, TypeCache>.Type, TypeCache.Type, resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter)) factory = expression.AsLambda(resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter).Compile(); return (Func)factory!; } /// /// Compiles a to a . For testing purposes. /// /// The expression. /// The delegate. public static Delegate CompileDelegate(this LambdaExpression expression) { if (!expression.TryEmit(out var result)) throw new InvalidOperationException("Could not compile the given expression!"); return result!; } /// /// Compiles an to a of , , and . /// /// The expression. /// The resolution context. /// The container configuration. /// The compiled delegate. public static Func CompileDynamicDelegate(this Expression expression, ResolutionContext resolutionContext, ContainerConfiguration containerConfiguration) { expression = expression.PostProcess(); if (!resolutionContext.DefinedVariables.IsEmpty) { resolutionContext.SingleInstructions.Add(expression); expression = resolutionContext.SingleInstructions.AsBlock(resolutionContext.DefinedVariables.Walk()); } if (containerConfiguration.ExternalExpressionCompiler != null) return (Func)containerConfiguration.ExternalExpressionCompiler( expression.AsLambda(resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter)); if (!expression.TryEmit(out var factory, TypeCache>.Type, TypeCache.Type, resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter)) factory = expression.AsLambda>(resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter).Compile(); return (Func)factory!; } /// /// Creates a from the given . /// /// The expression to wrap within the context. /// Optional service registration when it's available. /// The service context. public static ServiceContext AsServiceContext(this Expression? expression, ServiceRegistration? serviceRegistration = null) => expression == null ? ServiceContext.Empty : new ServiceContext(expression, serviceRegistration); /// /// Compiles a lambda expression into a Func delegate. /// /// The result type. /// The expression /// The delegate. public static Func CompileFunc(this Expression> expression) => (Func)expression.CompileDelegate(); /// /// Compiles a lambda expression into a Func delegate. /// /// First parameter type. /// The result type. /// The expression /// The delegate. public static Func CompileFunc(this Expression> expression) => (Func)expression.CompileDelegate(); /// /// Constructs an assigment expression, => Expression.Assign(left, right) /// /// The left part. /// The right part. /// The assignment expression. public static BinaryExpression AssignTo(this Expression left, Expression right) => Expression.Assign(left, right); /// /// Constructs an assigment expression, => Expression.Bind(member, expression) /// /// The member info. /// The right part. /// The assignment expression. public static MemberAssignment AssignTo(this MemberInfo memberInfo, Expression expression) => Expression.Bind(memberInfo, expression); /// /// Constructs a constant expression from an object, => Expression.Constant(obj) /// /// The object. /// The constant expression. public static ConstantExpression AsConstant(this object? obj) => Expression.Constant(obj); /// /// Constructs a constant expression from an object and a type, => Expression.Constant(obj, type) /// /// The object. /// The type. /// The constant expression. public static ConstantExpression AsConstant(this object? obj, Type type) => Expression.Constant(obj, type); /// /// Constructs a default expression from a type, => Expression.Default(type) /// /// The type. /// The default expression. public static DefaultExpression AsDefault(this Type type) => Expression.Default(type); /// /// Constructs a block expression from an expression collection and variables, => Expression.Block(variables, expressions) /// /// The expressions. /// The variables. /// The block expression. public static BlockExpression AsBlock(this IEnumerable expressions, params ParameterExpression[] variables) => Expression.Block(variables, expressions); internal static BlockExpression AsBlock(this ExpandableArray expressions, params ParameterExpression[] variables) => Expression.Block(variables, expressions); private static BlockExpression AsBlock(this ExpandableArray expressions, IEnumerable variables) => Expression.Block(variables, expressions); /// /// Constructs a lambda expression from an expression and parameters, => Expression.Lambda(expression, parameters) /// /// The expression. /// The parameters. /// The lambda expression. public static LambdaExpression AsLambda(this Expression expression, params ParameterExpression[] parameters) => Expression.Lambda(expression, parameters); /// /// Constructs a lambda expression from an expression and parameters, => Expression.Lambda(expression, parameters) /// /// The expression. /// The type of the delegate. /// The parameters. /// The lambda expression. public static LambdaExpression AsLambda(this Expression expression, Type delegateType, IEnumerable parameters) => Expression.Lambda(delegateType, expression, parameters); /// /// Constructs a lambda expression from an expression and parameters, => Expression.Lambda(expression, parameters) /// /// The expression. /// The type of the delegate. /// The parameters. /// The lambda expression. public static LambdaExpression AsLambda(this Expression expression, Type delegateType, params ParameterExpression[] parameters) => Expression.Lambda(delegateType, expression, parameters); /// /// Constructs a lambda expression from an expression and parameters, => Expression.Lambda(expression, parameters) /// /// The expression. /// The parameters. /// The lambda expression. public static LambdaExpression AsLambda(this Expression expression, IEnumerable parameters) => Expression.Lambda(expression, parameters); /// /// Constructs a lambda expression from an expression and parameters, => Expression.Lambda{TDelegate}(expression, parameters) /// /// The expression. /// The parameters. /// The lambda expression. public static Expression AsLambda(this Expression expression, params ParameterExpression[] parameters) => Expression.Lambda(expression, parameters); /// /// Constructs a variable expression from a type, => Expression.Variable(type, name) /// /// The type. /// The name. /// The variable expression. public static ParameterExpression AsVariable(this Type type, string? name = null) => Expression.Variable(type, name); /// /// Constructs a parameter expression from a type, => Expression.Parameter(type, name) /// /// The type. /// The name. /// The parameter expression. public static ParameterExpression AsParameter(this Type type, string? name = null) => Expression.Parameter(type, name); /// /// Constructs a static method call expression from a and its parameters, => Expression.Call(methodInfo, parameters) /// /// The static method info. /// The parameters. /// The call expression. public static MethodCallExpression CallStaticMethod(this MethodInfo methodInfo, params Expression[] parameters) => Expression.Call(methodInfo, parameters); /// /// Constructs a static method call expression from a and its parameters, => Expression.Call(methodInfo, parameters) /// /// The static method info. /// The parameters. /// The call expression. public static MethodCallExpression CallStaticMethod(this MethodInfo methodInfo, IEnumerable parameters) => Expression.Call(methodInfo, parameters); /// /// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters) /// /// The target expression. /// The method info. /// The parameters. /// The call expression. public static MethodCallExpression CallMethod(this Expression target, MethodInfo methodInfo, params Expression[] parameters) => Expression.Call(target, methodInfo, parameters); /// /// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters) /// /// The target expression. /// The method info. /// The parameters. /// The call expression. public static MethodCallExpression CallMethod(this Expression target, MethodInfo methodInfo, IEnumerable parameters) => Expression.Call(target, methodInfo, parameters); /// /// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters) /// /// The target expression. /// The method info. /// The parameters. /// The call expression. public static MethodCallExpression CallMethod(this MethodInfo methodInfo, Expression target, params Expression[] parameters) => target.CallMethod(methodInfo, parameters); /// /// Constructs a method call expression from a target expression, method info and parameters, => Expression.Call(target, methodInfo, parameters) /// /// The target expression. /// The method info. /// The parameters. /// The call expression. public static MethodCallExpression CallMethod(this MethodInfo methodInfo, Expression target, IEnumerable parameters) => target.CallMethod(methodInfo, parameters); /// /// Constructs a convert expression, => Expression.Convert(expression, type) /// /// The expression. /// The type. /// The convert expression. public static Expression ConvertTo(this Expression expression, Type type) => Expression.Convert(expression, type); /// /// Constructs an invocation expression, => Expression.Invoke(expression, parameters) /// /// The expression. /// The parameters. /// The invocation expression. public static InvocationExpression InvokeLambda(this LambdaExpression expression, params Expression[] parameters) => Expression.Invoke(expression, parameters); /// /// Constructs an invocation expression, => Expression.Invoke(delegate.AsConstant(), parameters) /// /// The delegate to invoke. /// The delegate parameters. /// The invocation expression. public static InvocationExpression InvokeDelegate(this Delegate @delegate, params Expression[] parameters) => Expression.Invoke(@delegate.AsConstant(), parameters); /// /// Constructs an invocation expression, => Expression.Invoke(delegate.AsConstant(), parameters) /// /// The delegate to invoke. /// The delegate parameters. /// The invocation expression. public static InvocationExpression InvokeDelegate(this Delegate @delegate, IEnumerable parameters) => Expression.Invoke(@delegate.AsConstant(), parameters); /// /// Constructs an new expression, => Expression.New(constructor, arguments) /// /// The constructor info. /// The arguments. /// The new expression. public static NewExpression MakeNew(this ConstructorInfo constructor, IEnumerable arguments) => Expression.New(constructor, arguments); /// /// Constructs an new expression, => Expression.New(constructor, arguments) /// /// The constructor info. /// The arguments. /// The new expression. public static NewExpression MakeNew(this ConstructorInfo constructor, params Expression[] arguments) => Expression.New(constructor, arguments); /// /// Constructs a member access expression, => Expression.Property(expression, prop) or Expression.Field(expression, field) /// /// The target expression. /// The property or field info. /// The member access expression. public static MemberExpression Member(this Expression expression, MemberInfo memberInfo) => memberInfo is PropertyInfo prop ? Expression.Property(expression, prop) : Expression.Field(expression, (FieldInfo)memberInfo); /// /// Constructs a property access expression, => Expression.Property(expression, prop) /// /// The target expression. /// The property info. /// The property access expression. public static MemberExpression Prop(this Expression expression, PropertyInfo propertyInfo) => Expression.Property(expression, propertyInfo); /// /// Constructs a property access expression, => Expression.Property(expression, prop) /// /// The property info. /// The target expression. /// The property access expression. public static MemberExpression Access(this PropertyInfo propertyInfo, Expression expression) => Expression.Property(expression, propertyInfo); /// /// Constructs a member init expression, => Expression.MemberInit(expression, bindings) /// /// The expression. /// The member bindings. /// The member init expression. public static MemberInitExpression InitMembers(this Expression expression, IEnumerable bindings) => Expression.MemberInit((NewExpression)expression, bindings); /// /// Constructs a member init expression, => Expression.MemberInit(expression, bindings) /// /// The expression. /// The member bindings. /// The member init expression. public static MemberInitExpression InitMembers(this Expression expression, params MemberBinding[] bindings) => Expression.MemberInit((NewExpression)expression, bindings); /// /// Constructs a new array expression, => Expression.NewArrayInit(type, initializerExpressions) /// /// The type. /// The element initializer expressions. /// The new array expression. public static NewArrayExpression InitNewArray(this Type type, params Expression[] initializerExpressions) => Expression.NewArrayInit(type, initializerExpressions); /// /// Constructs a new array expression, => Expression.NewArrayInit(type, initializerExpressions) /// /// The type. /// The element initializer expressions. /// The new array expression. public static NewArrayExpression InitNewArray(this Type type, IEnumerable initializerExpressions) => Expression.NewArrayInit(type, initializerExpressions); } ================================================ FILE: src/Extensions/TypeExtensions.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using Stashbox; using DependencyAttribute = Stashbox.Attributes.DependencyAttribute; namespace System; internal static class TypeExtensions { public static Type? GetEnumerableType(this Type type) { if (type.IsArray) return type.GetElementType(); if (type.ImplementsGenericType(TypeCache.EnumerableType) && type != TypeCache.Type && type.GenericTypeArguments.Length == 1) return type.GenericTypeArguments[0]; return null; } public static bool IsClosedGenericType(this Type type) => type is { IsGenericType: true, IsGenericTypeDefinition: false }; public static bool IsOpenGenericType(this Type type) => type is { IsGenericType: true, ContainsGenericParameters: true }; public static ConstructorInfo? GetConstructor(this Type type, params Type[] args) => Array.Find(type.GetConstructors(), c => c.GetParameters().Select(p => p.ParameterType).SequenceEqual(args)); public static bool IsDisposable(this Type type) => type.Implements(TypeCache.Type) #if HAS_ASYNC_DISPOSABLE || type.Implements(TypeCache.Type) #endif ; public static bool IsCompositionRoot(this Type type) => type.Implements(TypeCache.Type); public static bool IsResolvableType(this Type type) => type is { IsAbstract: false, IsInterface: false, IsClass: true } && type != TypeCache.Type && type.GetCustomAttribute() == null; public static IEnumerable GetRegisterableInterfaceTypes(this Type type) => type.GetInterfaces().Where(t => t != TypeCache.Type #if HAS_ASYNC_DISPOSABLE && t != TypeCache.Type #endif ); public static IEnumerable GetRegisterableBaseTypes(this Type type) { var baseType = type.BaseType; while (baseType != null && !baseType.IsObjectType()) { yield return baseType; baseType = baseType.BaseType; } } public static IEnumerable GetPossibleDependencyTypes(this Type type) { var constructorParameterTypes = type.GetConstructors() .SelectMany(c => c.GetParameters().Select(p => p.ParameterType)); var memberTypes = type.GetProperties().Select(p => p.PropertyType) .Concat(type.GetFields().Select(f => f.FieldType)); return constructorParameterTypes.Concat(memberTypes).Distinct(); } public static bool Implements(this Type type, Type interfaceType) => type == interfaceType || (interfaceType.IsOpenGenericType() ? type.ImplementsGenericType(interfaceType.GetGenericTypeDefinition()) : interfaceType.IsAssignableFrom(type)); public static bool ImplementsWithoutGenericCheck(this Type type, Type interfaceType) => interfaceType.IsAssignableFrom(type); private static bool ImplementsGenericType(this Type type, Type genericType) => type.MapsToGenericTypeDefinition(genericType) || type.HasInterfaceThatMapsToGenericTypeDefinition(genericType) || type.BaseType != null && type.BaseType.ImplementsGenericType(genericType); private static bool HasInterfaceThatMapsToGenericTypeDefinition(this Type type, Type genericType) => type.GetInterfaces() .Where(it => it.IsGenericType) .Any(it => it.GetGenericTypeDefinition() == genericType); public static bool MapsToGenericTypeDefinition(this Type type, Type genericType) => genericType.IsGenericTypeDefinition && type.IsGenericType && type.GetGenericTypeDefinition() == genericType; public static ConstructorInfo? GetFirstConstructor(this Type type) => type.GetConstructors().FirstOrDefault(); public static TypeInformation AsTypeInformation(this ParameterInfo parameter, Type? declaringType, TypeInformation parent, ServiceRegistration? serviceRegistration, ContainerConfiguration containerConfiguration) { var customAttributes = parameter.GetCustomAttributes(); var dependencyName = parameter.GetNameFromDependencyAttribute(containerConfiguration); if (serviceRegistration != null) { var dependencyBindings = serviceRegistration?.Options.GetOrDefault>(RegistrationOption.DependencyBindings); if (dependencyBindings != null || containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled) { if (dependencyBindings != null && parameter.Name != null && dependencyBindings.TryGetValue(parameter.Name, out var foundNamedDependencyName)) dependencyName = foundNamedDependencyName; else if (dependencyBindings != null && dependencyBindings.TryGetValue(parameter.ParameterType, out var foundTypedDependencyName)) dependencyName = foundTypedDependencyName; else if (dependencyName == null && containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled) dependencyName = parameter.Name; } } return new TypeInformation( parameter.ParameterType, declaringType, parent, dependencyName, customAttributes, parameter.Name, parameter.GetOptionalDefaultValue(), parameter.HasDependencyNameAttribute(containerConfiguration), null); } public static TypeInformation AsTypeInformation(this MemberInfo member, ServiceRegistration? serviceRegistration, TypeInformation parent, ContainerConfiguration containerConfiguration) { var customAttributes = member.GetCustomAttributes(); var dependencyName = member.GetNameFromDependencyAttribute(containerConfiguration); if (serviceRegistration != null) { var dependencyBindings = serviceRegistration?.Options.GetOrDefault>(RegistrationOption.DependencyBindings); if (dependencyBindings != null || containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled) { if (dependencyBindings != null && dependencyBindings.TryGetValue(member.Name, out var foundNamedDependencyName)) dependencyName = foundNamedDependencyName; else if (dependencyName == null && containerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled) dependencyName = member.Name; } } var type = member is PropertyInfo prop ? prop.PropertyType : ((FieldInfo)member).FieldType; return new TypeInformation( type, member.DeclaringType, parent, dependencyName, customAttributes, member.Name, null, member.HasDependencyNameAttribute(containerConfiguration), null); } public static MethodInfo[] GetUsableMethods(this Type type) { var methods = type.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(method => method.GetInjectionAttribute() != null); var baseType = type.BaseType; while (baseType != null && !baseType.IsObjectType()) { methods = methods.Concat(baseType.GetMethods(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance).Where(method => method.GetInjectionAttribute() != null)); baseType = baseType.BaseType; } return methods.CastToArray(); } public static MemberInfo[] GetUsableMembers(this Type type, ServiceRegistration? serviceRegistration, ContainerConfiguration containerConfiguration) { var autoMemberOptions = serviceRegistration?.Options.GetOrDefault(RegistrationOption.AutoMemberOptions); var autoMemberInjectionEnabled = containerConfiguration.AutoMemberInjectionEnabled || autoMemberOptions != null; var autoMemberInjectionRule = autoMemberOptions?.AutoMemberInjectionRule ?? containerConfiguration.AutoMemberInjectionRule; var publicPropsEnabled = autoMemberInjectionEnabled && (autoMemberInjectionRule & Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter) == Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter; var limitedPropsEnabled = autoMemberInjectionEnabled && (autoMemberInjectionRule & Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess) == Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess; var fieldsEnabled = autoMemberInjectionEnabled && (autoMemberInjectionRule & Rules.AutoMemberInjectionRules.PrivateFields) == Rules.AutoMemberInjectionRules.PrivateFields; var dependencyBindings = serviceRegistration?.Options.GetOrDefault>(RegistrationOption.DependencyBindings); var globalRequiredInjectionEnabled = containerConfiguration.RequiredMemberInjectionEnabled; var regRequiredInjectionEnabled = serviceRegistration?.Options.GetOrDefault(RegistrationOption.RequiredMemberInjectionEnabled); var requiredInjectionEnabled = regRequiredInjectionEnabled ?? globalRequiredInjectionEnabled; IEnumerable properties = type.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(member => member.FilterProperty(dependencyBindings, autoMemberOptions, containerConfiguration, publicPropsEnabled, limitedPropsEnabled, requiredInjectionEnabled)); IEnumerable fields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(member => member.FilterField(dependencyBindings, autoMemberOptions, containerConfiguration, fieldsEnabled, requiredInjectionEnabled)); var baseType = type.BaseType; while (baseType != null && !baseType.IsObjectType()) { properties = properties.Concat(baseType.GetProperties(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(member => member.FilterProperty(dependencyBindings, autoMemberOptions, containerConfiguration, publicPropsEnabled, limitedPropsEnabled, requiredInjectionEnabled))); fields = fields.Concat(baseType.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance) .Where(member => member.FilterField(dependencyBindings, autoMemberOptions, containerConfiguration, fieldsEnabled, requiredInjectionEnabled))); baseType = baseType.BaseType; } return properties.Concat(fields).CastToArray(); } public static bool SatisfiesGenericConstraintsOf(this Type implementationType, Type serviceType) { if (!implementationType.IsGenericTypeDefinition) return true; var serviceParameters = serviceType.GetGenericArguments(); var serviceParametersLength = serviceParameters.Length; var implementationParameters = implementationType.GetGenericArguments(); var implementationParametersLength = implementationParameters.Length; for (var i = 0; i < implementationParametersLength; i++) { var implementationParameter = implementationParameters[i]; var parameterPosition = implementationParameter.GenericParameterPosition; if (parameterPosition >= serviceParametersLength) return false; var argumentToValidate = serviceParameters[parameterPosition]; var parameterAttributes = implementationParameter.GenericParameterAttributes; if (parameterAttributes.HasDefaultConstructorConstraint() && !parameterAttributes.HasNotNullableValueTypeConstraint() && !argumentToValidate.IsPrimitive && !argumentToValidate.HasPublicParameterlessConstructor()) return false; if (parameterAttributes.HasReferenceTypeConstraint() && argumentToValidate is { IsClass: false, IsInterface: false }) return false; if (parameterAttributes.HasNotNullableValueTypeConstraint() && argumentToValidate is { IsValueType: false, IsPrimitive: false, IsEnum: false }) return false; var constraints = implementationParameter.GetGenericParameterConstraints(); var constraintsLength = constraints.Length; if (constraints.Length <= 0) continue; var found = false; for (var j = 0; !found && j < constraintsLength; j++) { var constraintForCheck = constraints[j]; if (argumentToValidate.Implements(constraintForCheck)) found = true; } return found; } return true; } private static bool HasDependencyNameAttribute(this ParameterInfo parameterInfo, ContainerConfiguration containerConfiguration) => parameterInfo.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault() != null || parameterInfo.CustomAttributes.Any(ca => containerConfiguration.AdditionalDependencyNameAttributeTypes != null && containerConfiguration.AdditionalDependencyNameAttributeTypes.Contains(ca.AttributeType)); private static bool HasDependencyNameAttribute(this MemberInfo memberInfo, ContainerConfiguration containerConfiguration) => memberInfo.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault() != null || memberInfo.CustomAttributes.Any(ca => containerConfiguration.AdditionalDependencyNameAttributeTypes != null && containerConfiguration.AdditionalDependencyNameAttributeTypes.Contains(ca.AttributeType)); public static bool IsNullableType(this Type type) => type.IsGenericType && type.GetGenericTypeDefinition() == TypeCache.NullableType; public static MethodInfo GetMethod(this Delegate @delegate) => @delegate.GetMethodInfo(); public static bool IsCompiledLambda(this Delegate @delegate) => @delegate.Target != null && @delegate.Target.GetType().FullName == "System.Runtime.CompilerServices.Closure"; public static string GetDiagnosticsView(this Type type) { if (!type.IsGenericType) return type.Name; var typeName = type.Name; var i = typeName.IndexOf('`'); #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER typeName = i != -1 ? typeName[..i] : typeName; #else typeName = i != -1 ? typeName.Substring(0, i) : typeName; #endif typeName += "<"; if (type.IsGenericTypeDefinition) typeName += new string(Enumerable.Repeat(',', type.GetGenericArguments().Length - 1).ToArray()); else typeName += string.Join(",", type.GetGenericArguments().Select(a => a.GetDiagnosticsView())); typeName += ">"; return typeName; } private static bool IsObjectType(this Type type) => type == TypeCache.Type; private static bool HasDefaultConstructorConstraint(this GenericParameterAttributes attributes) => (attributes & GenericParameterAttributes.DefaultConstructorConstraint) == GenericParameterAttributes.DefaultConstructorConstraint; private static bool HasReferenceTypeConstraint(this GenericParameterAttributes attributes) => (attributes & GenericParameterAttributes.ReferenceTypeConstraint) == GenericParameterAttributes.ReferenceTypeConstraint; private static bool HasNotNullableValueTypeConstraint(this GenericParameterAttributes attributes) => (attributes & GenericParameterAttributes.NotNullableValueTypeConstraint) == GenericParameterAttributes.NotNullableValueTypeConstraint; private static bool FilterProperty(this PropertyInfo prop, Dictionary? dependencyBindings, AutoMemberOptions? autoMemberOptions, ContainerConfiguration containerConfiguration, bool publicPropsEnabled, bool limitedPropsEnabled, bool requiredInjectionEnabled) { var valid = prop.CanWrite && !prop.IsIndexer() && (prop.HasDependencyAttribute(containerConfiguration) || #if HAS_REQUIRED (prop.HasRequiredAttribute() && requiredInjectionEnabled) || #endif publicPropsEnabled && prop.GetSetMethod() != null || limitedPropsEnabled || (dependencyBindings != null && dependencyBindings.ContainsKey(prop.Name))); valid = valid && (containerConfiguration.AutoMemberInjectionFilter == null || containerConfiguration.AutoMemberInjectionFilter(prop)); valid = valid && (autoMemberOptions?.AutoMemberInjectionFilter == null || autoMemberOptions.AutoMemberInjectionFilter(prop)); return valid; } private static bool FilterField(this FieldInfo field, Dictionary? dependencyBindings, AutoMemberOptions? autoMemberOptions, ContainerConfiguration containerConfiguration, bool fieldsEnabled, bool requiredInjectionEnabled) { var valid = !field.IsInitOnly && !field.IsBackingField() && (field.HasDependencyAttribute(containerConfiguration) || #if HAS_REQUIRED (field.HasRequiredAttribute() && requiredInjectionEnabled) || #endif fieldsEnabled || (dependencyBindings != null && dependencyBindings.ContainsKey(field.Name))); valid = valid && (containerConfiguration.AutoMemberInjectionFilter == null || containerConfiguration.AutoMemberInjectionFilter(field)); valid = valid && (autoMemberOptions?.AutoMemberInjectionFilter == null || autoMemberOptions.AutoMemberInjectionFilter(field)); return valid; } private static bool IsBackingField(this MemberInfo field) => field.Name[0] == '<'; private static bool IsIndexer(this PropertyInfo property) => property.GetIndexParameters().Length != 0; private static TypeInformation.DefaultValueHolder? GetOptionalDefaultValue(this ParameterInfo parameter) { if (!parameter.IsOptional) return null; if (parameter.DefaultValue != null && parameter.DefaultValue.GetType() == TypeCache.Type) return parameter.IsNullableMember() ? new TypeInformation.DefaultValueHolder(null) : null; return new TypeInformation.DefaultValueHolder(parameter.DefaultValue); } private static bool IsNullableMember(this ParameterInfo parameter) => IsNullableMember(parameter.Member, parameter.ParameterType); private static bool IsNullableMember(MemberInfo member, Type memberType) { if (memberType.IsValueType) return Nullable.GetUnderlyingType(memberType) != null; var nullable = member.CustomAttributes .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableAttribute"); if (nullable is { ConstructorArguments.Count: 1 }) { var attributeArgument = nullable.ConstructorArguments[0]; if (attributeArgument.ArgumentType == TypeCache.Type) { var args = (ReadOnlyCollection)attributeArgument.Value!; if (args.Count > 0 && args[0].ArgumentType == TypeCache.Type) return (byte)args[0].Value! == 2; } else if (attributeArgument.ArgumentType == TypeCache.Type) return (byte)attributeArgument.Value! == 2; } for (var type = member; type != null; type = type.DeclaringType) { var context = type.CustomAttributes .FirstOrDefault(x => x.AttributeType.FullName == "System.Runtime.CompilerServices.NullableContextAttribute"); if (context is { ConstructorArguments.Count: 1 } && context.ConstructorArguments[0].ArgumentType == TypeCache.Type) return (byte)context.ConstructorArguments[0].Value! == 2; } return false; } private static bool HasPublicParameterlessConstructor(this Type type) => Array.Find(type.GetConstructors(), c => c.GetParameters().Length == 0) != null; private static object? GetNameFromDependencyAttribute(this MemberInfo property, ContainerConfiguration containerConfiguration) { var attr = property.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault(); if (attr != null) return (attr as DependencyAttribute)?.Name; var msAttr = property.CustomAttributes.FirstOrDefault(a => containerConfiguration.AdditionalDependencyAttributeTypes != null && containerConfiguration.AdditionalDependencyAttributeTypes.Contains(a.AttributeType)); return msAttr?.ConstructorArguments.FirstOrDefault().Value; } private static object? GetNameFromDependencyAttribute(this ParameterInfo parameter, ContainerConfiguration containerConfiguration) { var attr = parameter.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault(); if (attr != null) return (attr as DependencyAttribute)?.Name; var msAttr = parameter.CustomAttributes.FirstOrDefault(a => containerConfiguration.AdditionalDependencyAttributeTypes != null && containerConfiguration.AdditionalDependencyAttributeTypes.Contains(a.AttributeType)); return msAttr?.ConstructorArguments.FirstOrDefault().Value; } private static bool HasDependencyAttribute(this MemberInfo property, ContainerConfiguration containerConfiguration) { return property.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault() != null || property.CustomAttributes.Any(a => containerConfiguration.AdditionalDependencyAttributeTypes != null && containerConfiguration.AdditionalDependencyAttributeTypes.Contains(a.AttributeType)); } #if HAS_REQUIRED private static bool HasRequiredAttribute(this MemberInfo memberInfo) => memberInfo.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault() != null; #endif private static InjectionMethodAttribute? GetInjectionAttribute(this MemberInfo method) { var attr = method.GetCustomAttributes(TypeCache.Type, false).FirstOrDefault(); return attr as InjectionMethodAttribute; } } ================================================ FILE: src/ICompositionRoot.cs ================================================ namespace Stashbox; /// /// Represents a composition root used by the , , , and methods. /// public interface ICompositionRoot { /// /// Composes services through the , , , and methods. /// /// The . void Compose(IStashboxContainer container); } ================================================ FILE: src/IContainerContext.cs ================================================ using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Resolution; namespace Stashbox; /// /// Represents the container context. /// public interface IContainerContext { /// /// The service registration repository. /// IRegistrationRepository RegistrationRepository { get; } /// /// The service decorator registration repository. /// IDecoratorRepository DecoratorRepository { get; } /// /// The parent container context. /// IContainerContext? ParentContext { get; } /// /// The resolution strategy. /// IResolutionStrategy ResolutionStrategy { get; } /// /// The parent container context. /// IResolutionScope RootScope { get; } /// /// The container configuration. /// ContainerConfiguration ContainerConfiguration { get; } } ================================================ FILE: src/IDecoratorRegistrator.cs ================================================ using Stashbox.Registration.Fluent; using System; namespace Stashbox; /// /// Represents a decorator registrator. /// public interface IDecoratorRegistrator { /// /// Registers a decorator service into the container. /// /// The service type. /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterDecorator(Type typeFrom, Type typeTo, Action? configurator = null); /// /// Registers a decorator service into the container. /// /// The service type. /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom; /// /// Registers a decorator service into the container. /// This function configures the registration with the option. /// /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterDecorator(Type typeTo, Action? configurator = null); /// /// Registers a decorator service into the container. /// This function configures the registration with the option. /// /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterDecorator(Action>? configurator = null) where TTo : class; /// /// Registers a decorator service into the container. /// /// The service type. /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterDecorator(Type typeTo, Action>? configurator = null) where TFrom : class; } ================================================ FILE: src/IDependencyCollectionRegistrator.cs ================================================ using Stashbox.Registration.Fluent; using System; using System.Collections.Generic; namespace Stashbox; /// /// Represents a dependency collection registrator. /// public interface IDependencyCollectionRegistrator { /// /// Registers a collection of types mapped to a service type. /// /// The service type. /// Types to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterTypesAs(Type typeFrom, IEnumerable types, Func? selector = null, Action? configurator = null); /// /// Registers a collection of types into the container. /// /// Types to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The service type selector. Used to filter which interface or base types the implementation should be mapped to. /// If it's true, the types will be registered to their own type too. /// The configurator for the registered types. /// The instance. IStashboxContainer RegisterTypes(IEnumerable types, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null); /// /// Composes services by calling the method of the given root. /// /// The type of an implementation. /// Optional composition root constructor arguments. /// The instance. IStashboxContainer ComposeBy(Type compositionRootType, params object[] compositionRootArguments); /// /// Composes services by calling the method of the given root. /// /// The composition root instance. /// The instance. IStashboxContainer ComposeBy(ICompositionRoot compositionRoot); } ================================================ FILE: src/IDependencyReMapper.cs ================================================ using Stashbox.Registration.Fluent; using System; namespace Stashbox; /// /// Represents a dependency remapper. /// public interface IDependencyReMapper { /// /// Replaces an existing registration mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMap(Action>? configurator = null) where TFrom : class where TTo : class, TFrom; /// /// Replaces an existing registration mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMap(Type typeTo, Action>? configurator = null) where TFrom : class; /// /// Replaces an existing registration mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMap(Type typeFrom, Type typeTo, Action? configurator = null); /// /// Replaces an existing registration mapping. /// /// The service/implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMap(Action>? configurator = null) where TTo : class; /// /// Replaces an existing decorator mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMapDecorator(Type typeFrom, Type typeTo, Action? configurator = null); /// /// Replaces an existing decorator mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMapDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom; /// /// Replaces an existing decorator mapping. /// /// The service type to re-map. /// The implementation type to re-map. /// The configurator for the registered types. /// The instance. IStashboxContainer ReMapDecorator(Type typeTo, Action>? configurator = null) where TFrom : class; } ================================================ FILE: src/IDependencyRegistrator.cs ================================================ using Stashbox.Registration.Fluent; using System; namespace Stashbox; /// /// Represents a dependency registrator. /// public interface IDependencyRegistrator { /// /// Registers a service into the container. /// /// The service type. /// The implementation type. /// The instance. IStashboxContainer Register(Action> configurator) where TFrom : class where TTo : class, TFrom; /// /// Registers a service into the container with a name. /// /// The service type. /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer Register(object? name = null) where TFrom : class where TTo : class, TFrom; /// /// Registers a service into the container. /// /// The service type. /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer Register(Type typeTo, Action>? configurator = null) where TFrom : class; /// /// Registers a service into the container. /// /// The service type. /// The implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer Register(Type typeFrom, Type typeTo, Action? configurator = null); /// /// Registers a service into the container. /// /// The service/implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer Register(Action> configurator) where TTo : class; /// /// Registers a service into the container. /// /// The service/implementation type. /// The name of the registration. /// The instance. IStashboxContainer Register(object? name = null) where TTo : class; /// /// Registers a service into the container. /// /// The service/implementation type. /// The configurator for the registered types. /// The instance. IStashboxContainer Register(Type typeTo, Action? configurator = null); /// /// Registers a named service with singleton lifetime. /// /// The service type. /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterSingleton(object? name = null) where TFrom : class where TTo : class, TFrom; /// /// Registers a named service with singleton lifetime. /// /// The service/implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterSingleton(object? name = null) where TTo : class; /// /// Registers a named service with singleton lifetime. /// /// The service type. /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterSingleton(Type typeFrom, Type typeTo, object? name = null); /// /// Registers a named service with scoped lifetime. /// /// The service type. /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterScoped(object? name = null) where TFrom : class where TTo : class, TFrom; /// /// Registers a named service with scoped lifetime. /// /// The service type. /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterScoped(Type typeFrom, Type typeTo, object? name = null); /// /// Registers a named service with scoped lifetime. /// /// The implementation type. /// The name of the registration. /// The instance. IStashboxContainer RegisterScoped(object? name = null) where TTo : class; /// /// Registers an already constructed instance into the container. /// /// The service type. /// The constructed instance. /// The name of the registration. /// If it's set to true the container will exclude the instance from disposal tracking. /// The cleanup delegate to call before dispose. /// The instance. IStashboxContainer RegisterInstance(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class; /// /// Registers an already constructed instance into the container. /// /// The constructed instance. /// The service type. /// The name of the registration. /// If it's set to true the container will exclude the instance from disposal tracking. /// The instance. IStashboxContainer RegisterInstance(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false); /// /// Registers an already constructed instance, but the container will perform injections and extensions on it. /// /// The service type. /// The constructed instance. /// The name of the registration. /// If it's set to true the container will exclude the instance from disposal tracking. /// The cleanup delegate to call before dispose. /// The instance. IStashboxContainer WireUp(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class; /// /// Registers an already constructed instance, but the container will perform injections and extensions on it. /// /// The constructed instance. /// The service type. /// The name of the registration. /// If it's set to true the container will exclude the instance from disposal tracking. /// The instance. IStashboxContainer WireUp(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false); } ================================================ FILE: src/IDependencyResolver.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Stashbox; /// /// Represents a dependency resolver. /// public interface IDependencyResolver : IServiceProvider, #if HAS_ASYNC_DISPOSABLE IAsyncDisposable, #endif IDisposable { /// /// Resolves an instance from the container. /// /// The type of the requested service. /// The resolved object. object Resolve(Type typeFrom); /// /// Resolves a named instance from the container with dependency overrides. /// /// The type of the requested service. /// The name of the requested service. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. object Resolve(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default); /// /// Resolves an instance from the container or returns default if the type is not resolvable. /// /// The type of the requested service. /// The resolved object. object? ResolveOrDefault(Type typeFrom); /// /// Resolves an instance from the container with dependency overrides or returns default if the type is not resolvable. /// /// The type of the requested service. /// The name of the requested service. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. object? ResolveOrDefault(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default); /// /// Returns a factory delegate that can be used to activate the service. /// /// The type of the requested instances. /// The name of the requested registration. /// The resolution behavior. /// The parameter type. /// The factory delegate. Delegate ResolveFactory(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes); /// /// Returns a factory delegate that can be used to activate the service or returns default if the type is not resolvable. /// /// The type of the requested instances. /// The name of the requested registration. /// The resolution behavior. /// The parameter type. /// The factory delegate. Delegate? ResolveFactoryOrDefault(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes); /// /// Creates a new scope. /// /// The name of the scope. /// If true, the new scope will be attached to the lifecycle of its parent scope. When the parent is being disposed, the new scope will be disposed with it. /// The created scope. IDependencyResolver BeginScope(object? name = null, bool attachToParent = false); /// /// Puts an instance into the scope. The instance will be disposed along with the scope disposal. /// /// The service type. /// The instance. /// If it's set to true the container will exclude the instance from disposal tracking. /// The dependency name of the instance. /// The scope. void PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false, object? name = null); /// /// Builds up an existing instance. This means the container performs member and method injections on it without registering it into the container. /// /// The type of the requested instance. /// The instance to build up. /// The resolution behavior. /// The built object. TTo BuildUp(TTo instance, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) where TTo : class; /// /// Activates an object without registering it into the container. If you want to resolve a /// registered service use the method instead. /// /// The type to activate. /// The resolution behavior. /// Optional dependency overrides. /// The built object. object Activate(Type type, ResolutionBehavior resolutionBehavior, params object[] arguments); /// /// Calls the registered asynchronous initializers of all resolved objects. /// /// The cancellation token. /// The initializer task. ValueTask InvokeAsyncInitializers(CancellationToken token = default); /// /// Checks whether a type can be resolved by the container, or not. /// /// The service type. /// The registration name. /// The resolution behavior. /// True if the service can be resolved, otherwise false. bool CanResolve(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default); /// /// Returns all cached service resolution delegates. /// /// The service resolution delegates. IEnumerable GetDelegateCacheEntries(); } ================================================ FILE: src/IFuncRegistrator.cs ================================================ using System; namespace Stashbox; /// /// Represents a factory registrator. /// public interface IFuncRegistrator { /// /// Registers a service with a factory resolver. /// /// The service type. /// The factory delegate. /// The name of the factory registration. /// The instance. IStashboxContainer RegisterFunc(Func factory, object? name = null); /// /// Registers a service with a factory resolver. /// /// The first parameter of the factory. /// The service type. /// The factory delegate. /// The name of the factory registration. /// The instance. IStashboxContainer RegisterFunc(Func factory, object? name = null); /// /// Registers a service with a factory resolver. /// /// The first parameter of the factory. /// The second parameter of the factory. /// The service type. /// The factory delegate. /// The name of the factory registration. /// The instance. IStashboxContainer RegisterFunc(Func factory, object? name = null); /// /// Registers a service with a factory resolver. /// /// The first parameter of the factory. /// The second parameter of the factory. /// The third parameter of the factory. /// The service type. /// The factory delegate. /// The name of the factory registration. /// The instance. IStashboxContainer RegisterFunc(Func factory, object? name = null); /// /// Registers a service with a factory resolver. /// /// The first parameter of the factory. /// The second parameter of the factory. /// The third parameter of the factory. /// The fourth parameter of the factory. /// The service type. /// The factory delegate. /// The name of the factory registration. /// The instance. IStashboxContainer RegisterFunc(Func factory, object? name = null); } ================================================ FILE: src/IResolutionScope.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Stashbox; /// /// Represents a resolution scope. /// public interface IResolutionScope : IDependencyResolver { /// /// The parent scope. /// IResolutionScope? ParentScope { get; } /// /// The name of the scope, if it's null then it's a regular nameless scope. /// object? Name { get; } /// /// Adds a service to dispose tracking. /// /// The object. /// The object. object AddDisposableTracking(object disposable); /// /// Adds a service to dispose tracking. /// /// The object. /// The request context. /// The object. object AddRequestContextAwareDisposableTracking(object disposable, IRequestContext requestContext); /// /// Adds a service with a cleanup delegate. /// /// The object to cleanup. /// The cleanup delegate. /// The object to cleanup. object AddWithFinalizer(object finalizable, Action finalizer); /// /// Adds a service with an async initializer delegate. /// /// The object to initialize. /// The async initializer delegate. /// The object to initialize. object AddWithAsyncInitializer(object initializable, Func initializer); /// /// Returns an existing scoped object or adds it into the scope if it doesn't exist. /// /// The key. /// The value factory used to create the object if it doesn't exist yet. /// The request context. /// The type of the service. /// The scoped object. object GetOrAddScopedObject(int key, Func factory, IRequestContext requestContext, Type serviceType); /// /// Invalidates the delegate cache. /// void InvalidateDelegateCache(); /// /// Gets the names of the already opened scopes. /// /// The scope names. IEnumerable GetActiveScopeNames(); } ================================================ FILE: src/IStashboxContainer.cs ================================================ using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Collections.Generic; namespace Stashbox; /// /// Represents a dependency injection container. /// public interface IStashboxContainer : IDependencyRegistrator, IDependencyResolver, IDependencyReMapper, IDependencyCollectionRegistrator, IDecoratorRegistrator, IFuncRegistrator { /// /// The container context. /// IContainerContext ContainerContext { get; } /// /// Child containers created by this container. /// IEnumerable> ChildContainers { get; } /// /// Registers an . /// /// The resolver implementation. /// The container itself. IStashboxContainer RegisterResolver(IResolver resolver); /// /// Creates a child container. /// /// The action delegate which will configure the child container. /// If true, the new child container will be attached to the lifecycle of its parent. When the parent is being disposed, the child will be disposed with it. IStashboxContainer CreateChildContainer(Action? config = null, bool attachToParent = true); /// /// Creates a child container. /// /// The identifier of the child container. /// The action delegate which will configure the child container. /// If true, the new child container will be attached to the lifecycle of its parent. When the parent is being disposed, the child will be disposed with it. IStashboxContainer CreateChildContainer(object identifier, Action? config = null, bool attachToParent = true); /// /// Returns the child container identified by . /// /// The identifier of the child container. /// The child container if it's exist, otherwise null. IStashboxContainer? GetChildContainer(object identifier); /// /// Checks whether a type is registered in the container. /// /// The service type. /// The registration name. /// True if the service is registered, otherwise false. bool IsRegistered(object? name = null); /// /// Checks whether a type is registered in the container. /// /// The service type. /// The registration name. /// True if the service is registered, otherwise false. bool IsRegistered(Type typeFrom, object? name = null); /// /// Configures the container. /// /// The action delegate which will configure the container. /// The container itself. IStashboxContainer Configure(Action config); /// /// Validates the current state of the container. /// void Validate(); /// /// Returns all registration mappings. /// /// The registration mappings. IEnumerable> GetRegistrationMappings(); /// /// Returns the details about the registrations. /// /// The detailed string representation of the registration. IEnumerable GetRegistrationDiagnostics(); } ================================================ FILE: src/Lifetime/AutoLifetime.cs ================================================ using System.Linq.Expressions; using Stashbox.Registration; using Stashbox.Resolution; namespace Stashbox.Lifetime; internal class AutoLifetime(LifetimeDescriptor boundaryLifetime) : LifetimeDescriptor { private LifetimeDescriptor selectedLifetime = boundaryLifetime; protected internal override int LifeSpan => this.selectedLifetime.LifeSpan; private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var tracker = new ResolutionContext.AutoLifetimeTracker(); var context = resolutionContext.BeginAutoLifetimeTrackingContext(tracker); var expression = GetExpressionForRegistration(serviceRegistration, context, typeInformation); this.selectedLifetime = tracker.HighestRankingLifetime.LifeSpan <= this.selectedLifetime.LifeSpan ? tracker.HighestRankingLifetime : this.selectedLifetime; var func = expression?.CompileDelegate(context, context.CurrentContainerContext.ContainerConfiguration); if (func == null) return null; var final = Expression.Invoke(func.AsConstant(), context.CurrentScopeParameter, context.RequestContextParameter); return this.selectedLifetime.ApplyLifetimeToExpression(final, serviceRegistration, resolutionContext, typeInformation); } internal override Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => this.selectedLifetime.ApplyLifetimeToExpression(expression, serviceRegistration, resolutionContext, typeInformation); } ================================================ FILE: src/Lifetime/EmptyLifetime.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using System.Linq.Expressions; namespace Stashbox.Lifetime; internal class EmptyLifetime : LifetimeDescriptor { private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => null; internal override Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => null; } ================================================ FILE: src/Lifetime/ExpressionLifetimeDescriptor.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a lifetime descriptor which applies to expressions. /// public abstract class ExpressionLifetimeDescriptor : LifetimeDescriptor { private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var expression = GetExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation); return this.ApplyLifetimeToExpression(expression, serviceRegistration, resolutionContext, typeInformation); } internal override Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => expression == null ? null : this.ApplyLifetime(expression, serviceRegistration, resolutionContext, typeInformation); /// /// Derived types are using this method to apply their lifetime to the instance creation. /// /// The expression the lifetime should apply to. /// The service registration. /// The info about the actual resolution. /// The type information of the resolved service. /// The lifetime managed expression. protected abstract Expression ApplyLifetime(Expression expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation); } ================================================ FILE: src/Lifetime/FactoryLifetimeDescriptor.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a lifetime descriptor which applies to factory delegates. /// public abstract class FactoryLifetimeDescriptor : LifetimeDescriptor { private protected override Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var factory = GetFactoryDelegateForRegistration(serviceRegistration, resolutionContext, typeInformation); return factory == null ? null : this.ApplyLifetime(factory, serviceRegistration, resolutionContext, typeInformation); } internal override Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => expression == null ? null : this.ApplyLifetime(expression.CompileDelegate(resolutionContext, resolutionContext.CurrentContainerContext.ContainerConfiguration), serviceRegistration, resolutionContext, typeInformation); private static Func? GetFactoryDelegateForRegistration(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (!IsRegistrationOutputCacheable(serviceRegistration, resolutionContext)) return GetNewFactoryDelegate(serviceRegistration, resolutionContext.BeginSubGraph(), typeInformation); var factory = resolutionContext.FactoryCache.GetOrDefault(serviceRegistration.RegistrationId); if (factory != null) return factory; factory = GetNewFactoryDelegate(serviceRegistration, resolutionContext.BeginSubGraph(), typeInformation); if (factory == null) return null; resolutionContext.FactoryCache.Add(serviceRegistration.RegistrationId, factory); return factory; } private static Func? GetNewFactoryDelegate(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => GetExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation) ?.CompileDelegate(resolutionContext, resolutionContext.CurrentContainerContext.ContainerConfiguration); /// /// Derived types are using this method to apply their lifetime to the instance creation. /// /// The factory which can be used to instantiate the service. /// The service registration. /// The info about the actual resolution. /// The type information of the resolved service. /// The lifetime managed expression. protected abstract Expression ApplyLifetime(Func factory, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation); } ================================================ FILE: src/Lifetime/LifetimeDescriptor.cs ================================================ using Stashbox.Exceptions; using Stashbox.Expressions; using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a lifetime descriptor. /// public abstract class LifetimeDescriptor { /// /// An indicator used to validate the lifetime configuration of the resolution tree. /// Services with longer life-span shouldn't contain dependencies with shorter ones. /// protected internal virtual int LifeSpan => 0; /// /// The name of the lifetime, used only for diagnostic reasons. /// protected string Name { get; } /// /// Constructs the lifetime descriptor. /// protected LifetimeDescriptor() { this.Name = this.GetType().Name; } internal Expression? ApplyLifetime(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (resolutionContext.CurrentContainerContext.ContainerConfiguration.LifetimeValidationEnabled && this.LifeSpan > 0) { if (resolutionContext.CurrentLifeSpan > this.LifeSpan) throw new LifetimeValidationFailedException(serviceRegistration.ImplementationType, $"The life-span of {serviceRegistration.ImplementationType} ({this.Name}|{this.LifeSpan}) " + $"is shorter than its direct or indirect parent's {resolutionContext.NameOfServiceLifeSpanValidatingAgainst}." + Environment.NewLine + "This could lead to incidental lifetime promotions with longer life-span, it's recommended to double check your lifetime configurations."); resolutionContext = resolutionContext.BeginLifetimeValidationContext(this.LifeSpan, $"{serviceRegistration.ImplementationType} ({this.Name}|{this.LifeSpan})"); } if (resolutionContext.AutoLifetimeTracking != null && this.LifeSpan > resolutionContext.AutoLifetimeTracking.HighestRankingLifetime.LifeSpan) { resolutionContext.AutoLifetimeTracking.HighestRankingLifetime = this; } return this.BuildLifetimeAppliedExpression(serviceRegistration, resolutionContext, typeInformation); } private protected abstract Expression? BuildLifetimeAppliedExpression(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation); internal abstract Expression? ApplyLifetimeToExpression(Expression? expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation); private protected static Expression? GetExpressionForRegistration(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (!IsRegistrationOutputCacheable(serviceRegistration, resolutionContext)) return ExpressionBuilder.BuildExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation); var expression = resolutionContext.ExpressionCache.GetOrDefault(serviceRegistration.RegistrationId); if (expression != null) return expression; expression = ExpressionBuilder.BuildExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation); if (expression == null) return null; resolutionContext.CacheExpression(serviceRegistration.RegistrationId, expression); return expression; } private protected static bool IsRegistrationOutputCacheable(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext) => !serviceRegistration.IsDecorator && resolutionContext.RequestConfiguration.FactoryDelegateCacheEnabled && resolutionContext.PerResolutionRequestCacheEnabled && serviceRegistration is not OpenGenericRegistration; } ================================================ FILE: src/Lifetime/Lifetimes.cs ================================================ namespace Stashbox.Lifetime; /// /// Contains all the built-in lifetime managers. /// public static class Lifetimes { /// /// Transient lifetime. /// public static readonly LifetimeDescriptor Transient = new TransientLifetime(); /// /// Singleton lifetime. /// public static readonly LifetimeDescriptor Singleton = new SingletonLifetime(); /// /// Scoped lifetime. /// public static readonly LifetimeDescriptor Scoped = new ScopedLifetime(); /// /// Per resolution request lifetime. /// public static readonly LifetimeDescriptor PerRequest = new PerRequestLifetime(); /// /// Produces a NamedScope lifetime. /// /// The name of the scope. /// A named-scope lifetime. public static LifetimeDescriptor NamedScope(object name) => new NamedScopeLifetime(name); /// /// Produces a lifetime that aligns to the lifetime of the resolved service's dependencies. /// When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary. /// /// The lifetime that represents a boundary which the derived lifetime must not exceed. /// An auto lifetime. public static LifetimeDescriptor Auto(LifetimeDescriptor boundaryLifetime) => new AutoLifetime(boundaryLifetime); internal static readonly LifetimeDescriptor Empty = new EmptyLifetime(); } ================================================ FILE: src/Lifetime/NamedScopeLifetime.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Linq.Expressions; using System.Reflection; using Stashbox.Utils; namespace Stashbox.Lifetime; /// /// Represents a named scope lifetime. /// public class NamedScopeLifetime : FactoryLifetimeDescriptor { private static readonly MethodInfo GetScopeValueMethod = TypeCache.Type.GetMethod(nameof(GetScopedValue), BindingFlags.Static | BindingFlags.NonPublic)!; /// /// The name of the scope where this lifetime activates. /// public readonly object ScopeName; /// protected internal override int LifeSpan => 10; /// /// Constructs a . /// /// public NamedScopeLifetime(object scopeName) { this.ScopeName = scopeName; } /// protected override Expression ApplyLifetime(Func factory, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => GetScopeValueMethod.CallStaticMethod(resolutionContext.CurrentScopeParameter, resolutionContext.RequestContextParameter, factory.AsConstant(), serviceRegistration.ImplementationType.AsConstant(), serviceRegistration.GetDiscriminator(typeInformation, resolutionContext.CurrentContainerContext.ContainerConfiguration).AsConstant(), this.ScopeName.AsConstant(), resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType .AsConstant().ConvertTo(TypeCache.Type)); private static object GetScopedValue(IResolutionScope currentScope, IRequestContext requestContext, Func factory, Type serviceType, int scopeId, object scopeName, Type? externalExceptionType) { var scope = currentScope; while (scope != null && !scopeName.Equals(scope.Name)) scope = scope.ParentScope; if (scope == null) throw ResolutionFailedException.CreateWithDesiredExceptionType(serviceType, message: $"The scope '{scopeName}' was not found to resolve {serviceType.FullName} with named scope lifetime.", externalExceptionType: externalExceptionType); return scope.GetOrAddScopedObject(scopeId, factory, requestContext, serviceType); } } ================================================ FILE: src/Lifetime/PerRequestLifetime.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a per-request lifetime. /// public class PerRequestLifetime : FactoryLifetimeDescriptor { /// protected override Expression ApplyLifetime(Func factory, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { resolutionContext.RequestConfiguration.RequiresRequestContext = true; return resolutionContext.RequestContextParameter .ConvertTo(TypeCache.Type) .CallMethod(Constants.GetOrAddInstanceMethod, serviceRegistration.GetDiscriminator(typeInformation, resolutionContext.CurrentContainerContext.ContainerConfiguration).AsConstant(), factory.AsConstant(), resolutionContext.CurrentScopeParameter); } } ================================================ FILE: src/Lifetime/ScopedLifetime.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a scoped lifetime. /// public class ScopedLifetime : FactoryLifetimeDescriptor { /// protected internal override int LifeSpan => 10; /// protected override Expression ApplyLifetime(Func factory, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (resolutionContext.CurrentContainerContext.ContainerConfiguration.LifetimeValidationEnabled && resolutionContext.IsRequestedFromRoot) throw new LifetimeValidationFailedException(serviceRegistration.ImplementationType, $"Resolution of {serviceRegistration.ImplementationType} ({this.Name}) from the root scope is not allowed, " + $"that would promote the service's lifetime to singleton."); return resolutionContext.CurrentScopeParameter.CallMethod(Constants.GetOrAddScopedObjectMethod, serviceRegistration.GetDiscriminator(typeInformation, resolutionContext.CurrentContainerContext.ContainerConfiguration).AsConstant(), factory.AsConstant(), resolutionContext.RequestContextParameter, serviceRegistration.ImplementationType.AsConstant()); } } ================================================ FILE: src/Lifetime/SingletonLifetime.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a singleton lifetime. /// public class SingletonLifetime : FactoryLifetimeDescriptor { /// protected internal override int LifeSpan => 20; /// protected override Expression ApplyLifetime(Func factory, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { var rootScope = resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.ReBuildSingletonsInChildContainerEnabled ? resolutionContext.RequestInitiatorContainerContext.RootScope : resolutionContext.CurrentContainerContext.RootScope; return rootScope.AsConstant().CallMethod(Constants.GetOrAddScopedObjectMethod, serviceRegistration.GetDiscriminator(typeInformation, resolutionContext.CurrentContainerContext.ContainerConfiguration).AsConstant(), factory.AsConstant(), resolutionContext.RequestContextParameter, serviceRegistration.ImplementationType.AsConstant()); } } ================================================ FILE: src/Lifetime/TransientLifetime.cs ================================================ using Stashbox.Registration; using Stashbox.Resolution; using System; using System.Linq.Expressions; namespace Stashbox.Lifetime; /// /// Represents a transient lifetime. /// public class TransientLifetime : ExpressionLifetimeDescriptor { /// protected override Expression ApplyLifetime(Expression expression, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) => expression; } ================================================ FILE: src/Metadata.cs ================================================ namespace Stashbox; /// /// Describes a wrapper for services with additional metadata. /// /// The service type. /// The additional metadata type. public sealed class Metadata { /// /// The service. /// public readonly TService Service; /// /// The additional metadata. /// public readonly TMeta Data; /// /// Constructs a . /// /// The service. /// The additional metadata. public Metadata(TService service, TMeta data) { this.Service = service; this.Data = data; } } ================================================ FILE: src/Multitenant/ITenantDistributor.cs ================================================ using System; namespace Stashbox.Multitenant; /// /// Represents a tenant distributor that manages tenants in a multi-tenant environment. /// [Obsolete("The functionality of this interface was moved to IStashboxContainer. Please use IStashboxContainer.CreateChildContainer() and IStashboxContainer.GetChildContainer() instead.")] public interface ITenantDistributor : IStashboxContainer { /// /// Adds a tenant with a specified service configuration to the distributor. /// /// The identifier of the tenant. /// The service configuration of the tenant. /// If true, the new tenant will be attached to the lifecycle of the root container. When the root is being disposed, the tenant will be disposed with it. void ConfigureTenant(object tenantId, Action tenantConfig, bool attachTenantToRoot = true); /// /// Gets a pre-configured from the distributor which represents a tenant identified by the given id. /// When the requested tenant doesn't exist a null value will be returned. /// /// The identifier of the tenant. /// The pre-configured tenant container if it's exist, otherwise null. IDependencyResolver? GetTenant(object tenantId); } ================================================ FILE: src/Multitenant/TenantDistributor.cs ================================================ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Registration.Fluent; using Stashbox.Resolution; namespace Stashbox.Multitenant; /// /// Represents a tenant distributor that manages tenants in a multi-tenant environment. /// [Obsolete("The functionality of this class was moved to StashboxContainer. Please use StashboxContainer.CreateChildContainer() and StashboxContainer.GetChildContainer() instead.")] public sealed class TenantDistributor : ITenantDistributor { private int disposed; private readonly IStashboxContainer rootContainer; /// /// Constructs a . /// /// A pre-configured root container, used to create child tenant containers. If not set, a new will be created. public TenantDistributor(IStashboxContainer? rootContainer = null) { this.rootContainer = rootContainer ?? new StashboxContainer(); } /// public void ConfigureTenant(object tenantId, Action tenantConfig, bool attachTenantToRoot = true) => rootContainer.CreateChildContainer(tenantId, tenantConfig, attachTenantToRoot); /// public IDependencyResolver? GetTenant(object tenantId) => rootContainer.GetChildContainer(tenantId); /// public IEnumerable> ChildContainers => rootContainer.ChildContainers; /// public IStashboxContainer RegisterResolver(IResolver resolver) => rootContainer.RegisterResolver(resolver); /// public IStashboxContainer CreateChildContainer(Action? config = null, bool attachToParent = true) => rootContainer.CreateChildContainer(config, attachToParent); /// public IStashboxContainer CreateChildContainer(object identifier, Action? config = null, bool attachToParent = true) => rootContainer.CreateChildContainer(identifier, config, attachToParent); /// public IStashboxContainer? GetChildContainer(object identifier) => rootContainer.GetChildContainer(identifier); /// public IContainerContext ContainerContext => rootContainer.ContainerContext; /// public bool IsRegistered(object? name = null) => rootContainer.IsRegistered(name); /// public bool IsRegistered(Type typeFrom, object? name = null) => rootContainer.IsRegistered(typeFrom, name); /// public IStashboxContainer Configure(Action config) => rootContainer.Configure(config); /// public void Validate() => rootContainer.Validate(); /// public IEnumerable> GetRegistrationMappings() => rootContainer.GetRegistrationMappings(); /// public IEnumerable GetRegistrationDiagnostics() => rootContainer.GetRegistrationDiagnostics(); /// public void Dispose() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) return; this.rootContainer.Dispose(); } #if HAS_ASYNC_DISPOSABLE /// public ValueTask DisposeAsync() => Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0 ? new ValueTask(Task.CompletedTask) : this.rootContainer.DisposeAsync(); #endif /// public IStashboxContainer Register(Action> configurator) where TFrom : class where TTo : class, TFrom => rootContainer.Register(configurator); /// public IStashboxContainer Register(object? name = null) where TFrom : class where TTo : class, TFrom => rootContainer.Register(name); /// public IStashboxContainer Register(Type typeTo, Action>? configurator = null) where TFrom : class => rootContainer.Register(typeTo, configurator); /// public IStashboxContainer Register(Type typeFrom, Type typeTo, Action? configurator = null) => rootContainer.Register(typeFrom, typeTo, configurator); /// public IStashboxContainer Register(Action> configurator) where TTo : class => rootContainer.Register(configurator); /// public IStashboxContainer Register(object? name = null) where TTo : class => rootContainer.Register(name); /// public IStashboxContainer Register(Type typeTo, Action? configurator = null) => rootContainer.Register(typeTo, configurator); /// public IStashboxContainer RegisterSingleton(object? name = null) where TFrom : class where TTo : class, TFrom => rootContainer.RegisterSingleton(name); /// public IStashboxContainer RegisterSingleton(object? name = null) where TTo : class => rootContainer.RegisterSingleton(name); /// public IStashboxContainer RegisterSingleton(Type typeFrom, Type typeTo, object? name = null) => rootContainer.RegisterSingleton(typeFrom, typeTo, name); /// public IStashboxContainer RegisterScoped(object? name = null) where TFrom : class where TTo : class, TFrom => rootContainer.RegisterScoped(name); /// public IStashboxContainer RegisterScoped(Type typeFrom, Type typeTo, object? name = null) => rootContainer.RegisterScoped(typeFrom, typeTo, name); /// public IStashboxContainer RegisterScoped(object? name = null) where TTo : class => rootContainer.RegisterScoped(name); /// public IStashboxContainer RegisterInstance(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class => rootContainer.RegisterInstance(instance, name, withoutDisposalTracking, finalizerDelegate); /// public IStashboxContainer RegisterInstance(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false) => rootContainer.RegisterInstance(instance, serviceType, name, withoutDisposalTracking); /// public IStashboxContainer WireUp(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class => rootContainer.WireUp(instance, name, withoutDisposalTracking, finalizerDelegate); /// public IStashboxContainer WireUp(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false) => rootContainer.WireUp(instance, serviceType, name, withoutDisposalTracking); /// public object? GetService(Type serviceType) => rootContainer.GetService(serviceType); /// public object Resolve(Type typeFrom) => rootContainer.Resolve(typeFrom); /// public object Resolve(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => rootContainer.Resolve(typeFrom, name, dependencyOverrides, resolutionBehavior); /// public object? ResolveOrDefault(Type typeFrom) => rootContainer.ResolveOrDefault(typeFrom); /// public object? ResolveOrDefault(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => rootContainer.ResolveOrDefault(typeFrom, name, dependencyOverrides, resolutionBehavior); /// public Delegate ResolveFactory(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) => rootContainer.ResolveFactory(typeFrom, name, resolutionBehavior, parameterTypes); /// public Delegate? ResolveFactoryOrDefault(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) => rootContainer.ResolveFactoryOrDefault(typeFrom, name, resolutionBehavior, parameterTypes); /// public IDependencyResolver BeginScope(object? name = null, bool attachToParent = false) => rootContainer.BeginScope(name, attachToParent); /// public void PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false, object? name = null) => rootContainer.PutInstanceInScope(typeFrom, instance, withoutDisposalTracking, name); /// public TTo BuildUp(TTo instance, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) where TTo : class => rootContainer.BuildUp(instance, resolutionBehavior); /// public object Activate(Type type, ResolutionBehavior resolutionBehavior, params object[] arguments) => rootContainer.Activate(type, resolutionBehavior, arguments); /// public ValueTask InvokeAsyncInitializers(CancellationToken token = default) => rootContainer.InvokeAsyncInitializers(token); /// public bool CanResolve(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => rootContainer.CanResolve(typeFrom, name); /// public IEnumerable GetDelegateCacheEntries() => rootContainer.GetDelegateCacheEntries(); /// public IStashboxContainer ReMap(Action>? configurator = null) where TFrom : class where TTo : class, TFrom => rootContainer.ReMap(configurator); /// public IStashboxContainer ReMap(Type typeTo, Action>? configurator = null) where TFrom : class => rootContainer.ReMap(typeTo, configurator); /// public IStashboxContainer ReMap(Type typeFrom, Type typeTo, Action? configurator = null) => rootContainer.ReMap(typeFrom, typeTo, configurator); /// public IStashboxContainer ReMap(Action>? configurator = null) where TTo : class => rootContainer.ReMap(configurator); /// public IStashboxContainer ReMapDecorator(Type typeFrom, Type typeTo, Action? configurator = null) => rootContainer.ReMapDecorator(typeFrom, typeTo, configurator); /// public IStashboxContainer ReMapDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom => rootContainer.ReMapDecorator(configurator); /// public IStashboxContainer ReMapDecorator(Type typeTo, Action>? configurator = null) where TFrom : class => rootContainer.ReMapDecorator(typeTo, configurator); /// public IStashboxContainer RegisterTypesAs(Type typeFrom, IEnumerable types, Func? selector = null, Action? configurator = null) => rootContainer.RegisterTypesAs(typeFrom, types, selector, configurator); /// public IStashboxContainer RegisterTypes(IEnumerable types, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) => rootContainer.RegisterTypes(types, selector, serviceTypeSelector, registerSelf, configurator); /// public IStashboxContainer ComposeBy(Type compositionRootType, params object[] compositionRootArguments) => rootContainer.ComposeBy(compositionRootType, compositionRootArguments); /// public IStashboxContainer ComposeBy(ICompositionRoot compositionRoot) => rootContainer.ComposeBy(compositionRoot); /// public IStashboxContainer RegisterDecorator(Type typeFrom, Type typeTo, Action? configurator = null) => rootContainer.RegisterDecorator(typeFrom, typeTo, configurator); /// public IStashboxContainer RegisterDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom => rootContainer.RegisterDecorator(configurator); /// public IStashboxContainer RegisterDecorator(Type typeTo, Action? configurator = null) => rootContainer.RegisterDecorator(typeTo, configurator); /// public IStashboxContainer RegisterDecorator(Action>? configurator = null) where TTo : class => rootContainer.RegisterDecorator(configurator); /// public IStashboxContainer RegisterDecorator(Type typeTo, Action>? configurator = null) where TFrom : class => rootContainer.RegisterDecorator(typeTo, configurator); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => rootContainer.RegisterFunc(factory, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => rootContainer.RegisterFunc(factory, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => rootContainer.RegisterFunc(factory, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => rootContainer.RegisterFunc(factory, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => rootContainer.RegisterFunc(factory, name); } ================================================ FILE: src/Override.cs ================================================ using System; using Stashbox.Utils; namespace Stashbox; /// /// Represents a specialized resolution override. /// public class Override { private Override(object instance, object? dependencyName, Type type) { this.Instance = instance; this.DependencyName = dependencyName; this.Type = type; } /// /// The name of the override used for named resolution. /// public object? DependencyName { get; } /// /// The instance used as dependency override. /// public object Instance { get; } /// /// The type of the dependency override. /// public Type Type { get; } /// /// Creates a new instance. /// /// The instance used as dependency override. /// The optional name of the instance used as dependency override. /// The constructed override instance. public static Override Of(TType instance, object? name = null) where TType: notnull => new(instance, name, TypeCache.Type); /// /// Creates a new instance. /// /// The type of the override. /// The instance used as dependency override. /// The optional name of the instance used as dependency override. /// The constructed override instance. public static Override Of(Type type, object instance, object? name = null) => new(instance, name, type); } ================================================ FILE: src/ReadOnlyKeyValue.cs ================================================ using System.Diagnostics; namespace Stashbox; /// /// Represents a readonly key-value pair. /// /// Type of the key. /// Type of the value. [DebuggerDisplay("{Value}", Name = "{Key}")] public readonly struct ReadOnlyKeyValue { /// /// The key. /// public readonly TKey Key; /// /// The value. /// public readonly TValue Value; /// /// Constructs a . /// /// The key. /// The value. public ReadOnlyKeyValue(TKey key, TValue value) { this.Key = key; this.Value = value; } } ================================================ FILE: src/Registration/DecoratorRepository.cs ================================================ using Stashbox.Registration.Extensions; using Stashbox.Registration.SelectionRules; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; using Stashbox.Configuration; namespace Stashbox.Registration; internal class DecoratorRepository(ContainerConfiguration containerConfiguration) : IDecoratorRepository { private ImmutableTree> repository = ImmutableTree>.Empty; private readonly IRegistrationSelectionRule[] filters = [ RegistrationSelectionRules.GenericFilter, RegistrationSelectionRules.ConditionFilter, RegistrationSelectionRules.ScopeNameFilter, RegistrationSelectionRules.DecoratorFilter ]; public void AddDecorator(Type type, ServiceRegistration serviceRegistration, bool remap) { var newRepository = ImmutableBucket.Empty.Add(serviceRegistration.ImplementationType, serviceRegistration); if (remap) Swap.SwapValue(ref this.repository, (t1, t2, _, _, repo) => repo.AddOrUpdate(t1, t2, true, (_, newValue) => newValue), type, newRepository, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); else Swap.SwapValue(ref this.repository, (t1, t2, t3, _, repo) => repo.AddOrUpdate(t1, t2, true, (oldValue, _) => oldValue .AddOrUpdate(t3.ImplementationType, t3, t3.Options.IsOn(RegistrationOption.ReplaceExistingRegistration))), type, newRepository, serviceRegistration, Constants.DelegatePlaceholder); } public IEnumerable? GetDecoratorsOrDefault(Type implementationTypeToDecorate, TypeInformation typeInformation, ResolutionContext resolutionContext) => this.GetRegistrationsForType(typeInformation.Type)?.FilterInclusive(typeInformation.Clone(implementationTypeToDecorate), resolutionContext, this.filters); public IEnumerable> GetRegistrationMappings() => repository.Walk().SelectMany(reg => reg.Value.Select(r => new KeyValuePair(reg.Key, r))); private IEnumerable? GetRegistrationsForType(Type type) { IEnumerable? registrations = repository.GetOrDefaultByRef(type); if (!type.IsClosedGenericType()) return registrations; var openGenerics = repository.GetOrDefaultByRef(type.GetGenericTypeDefinition()); if (openGenerics != null) registrations = registrations == null ? openGenerics : openGenerics.Concat(registrations); if (!containerConfiguration.VariantGenericTypesEnabled) return registrations; var variantGenerics = repository.Walk() .Where(r => r.Key.IsGenericType && r.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition() && r.Key != type && r.Key.ImplementsWithoutGenericCheck(type)) .SelectMany(r => r.Value).ToArray(); if (variantGenerics.Length > 0) registrations = registrations == null ? variantGenerics : variantGenerics.Concat(registrations); return registrations; } } ================================================ FILE: src/Registration/Extensions/CollectionRegistratorExtensions.cs ================================================ using Stashbox.Exceptions; using Stashbox.Registration.Fluent; using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Stashbox; /// /// Represents the extension methods of . /// public static class CollectionRegistratorExtensions { /// /// Registers a collection of types mapped to a service type. /// /// The service type. /// The registrator. /// Types to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterTypesAs(this IDependencyCollectionRegistrator registrator, IEnumerable types, Func? selector = null, Action? configurator = null) where TFrom : class => registrator.RegisterTypesAs(TypeCache.Type, types, selector, configurator); /// /// Registers a collection of types mapped to a service type. /// /// The service type. /// The registrator. /// Assembly to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterTypesAs(this IDependencyCollectionRegistrator registrator, Assembly assembly, Func? selector = null, Action? configurator = null) where TFrom : class => registrator.RegisterTypesAs(TypeCache.Type, assembly.CollectTypes(), selector, configurator); /// /// Registers a collection of types mapped to a service type. /// /// The service type. /// The registrator. /// Assembly to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterTypesAs(this IDependencyCollectionRegistrator registrator, Type typeFrom, Assembly assembly, Func? selector = null, Action? configurator = null) => registrator.RegisterTypesAs(typeFrom, assembly.CollectTypes(), selector, configurator); /// /// Registers types from an assembly. /// /// The registrator. /// The assembly containing the types to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The service type selector. Used to filter which interface or base types the implementation should be mapped to. /// If it's true the types will be registered to their own type too. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterAssembly(this IDependencyCollectionRegistrator registrator, Assembly assembly, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) => registrator.RegisterTypes(assembly.CollectTypes(), selector, serviceTypeSelector, registerSelf, configurator); /// /// Registers types from an assembly collection. /// /// The registrator. /// The assemblies holding the types to register. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The service type selector. Used to filter which interface or base types the implementation should be mapped to. /// If it's true the types will be registered to their own type too. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterAssemblies(this IDependencyCollectionRegistrator registrator, IEnumerable assemblies, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) { Shield.EnsureNotNull(assemblies, nameof(assemblies)); foreach (var assembly in assemblies) registrator.RegisterAssembly(assembly, selector, serviceTypeSelector, registerSelf, configurator); return (IStashboxContainer)registrator; } /// /// Registers types from an assembly that contains the given service type. /// /// The service type the assembly contains. /// The registrator. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The service type selector. Used to filter which interface or base types the implementation should be mapped to. /// If it's true the types will be registered to their own type too. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterAssemblyContaining(this IDependencyCollectionRegistrator registrator, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) where TFrom : class => registrator.RegisterAssemblyContaining(TypeCache.Type, selector, serviceTypeSelector, registerSelf, configurator); /// /// Registers types from an assembly that contains the given service type. /// /// The registrator. /// The type the assembly contains. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The service type selector. Used to filter which interface or base types the implementation should be mapped to. /// If it's true the types will be registered to their own type too. /// The configurator for the registered types. /// The instance. public static IStashboxContainer RegisterAssemblyContaining(this IDependencyCollectionRegistrator registrator, Type typeFrom, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) => registrator.RegisterAssembly(typeFrom.Assembly, selector, serviceTypeSelector, registerSelf, configurator); /// /// Scans the given assemblies for implementations and invokes their method. /// /// The registrator. /// The assemblies to scan. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The instance. public static IStashboxContainer ComposeAssemblies(this IDependencyCollectionRegistrator registrator, IEnumerable assemblies, Func? selector = null) { Shield.EnsureNotNull(assemblies, nameof(assemblies)); foreach (var assembly in assemblies) registrator.ComposeAssembly(assembly, selector); return (IStashboxContainer)registrator; } /// /// Composes services by calling the method of the given root. /// /// The type of an implementation. /// The registrator. /// Optional composition root constructor arguments. /// The instance. public static IStashboxContainer ComposeBy(this IDependencyCollectionRegistrator registrator, params object[] compositionRootArguments) where TCompositionRoot : class, ICompositionRoot => registrator.ComposeBy(TypeCache.Type, compositionRootArguments); /// /// Scans the given assembly for implementations and invokes their method. /// /// The registrator. /// The assembly to scan. /// The type selector. Used to filter which types should be excluded/included in the registration process. /// The instance. public static IStashboxContainer ComposeAssembly(this IDependencyCollectionRegistrator registrator, Assembly assembly, Func? selector = null) { Shield.EnsureNotNull(assembly, nameof(assembly)); var types = selector == null ? assembly.CollectTypes() : assembly.CollectTypes().Where(selector); var compositionRootTypes = types.Where(type => type.IsResolvableType() && type.IsCompositionRoot()).CastToArray(); if (!compositionRootTypes.Any()) throw new CompositionRootNotFoundException(assembly); foreach (var compositionRootType in compositionRootTypes) registrator.ComposeBy(compositionRootType); return (IStashboxContainer)registrator; } } ================================================ FILE: src/Registration/Extensions/DependencyRegistratorExtensions.cs ================================================ using System.Collections.Generic; namespace Stashbox; /// /// Represents the extension methods of . /// public static class DependencyRegistratorExtensions { /// /// Registers instances that are already constructed. /// /// The dependency registrator. /// The collection of the constructed instances. /// If it's set to true the container will exclude the instance from the disposal tracking. /// The instance. public static IStashboxContainer RegisterInstances(this IDependencyRegistrator registrator, IEnumerable instances, bool withoutDisposalTracking = false) where TFrom : class { foreach (var instance in instances) registrator.RegisterInstance(instance, withoutDisposalTracking: withoutDisposalTracking); return (IStashboxContainer)registrator; } /// /// Registers instances that are already constructed. /// /// The dependency registrator. /// The collection of the constructed instances. /// The instance. public static IStashboxContainer RegisterInstances(this IDependencyRegistrator registrator, params TFrom[] instances) where TFrom : class => registrator.RegisterInstances(instances, false); } ================================================ FILE: src/Registration/Extensions/DependencyRemapperExtensions.cs ================================================ using Stashbox.Registration.Fluent; using System; namespace Stashbox; /// /// Represents the extension methods of . /// public static class DependencyReMapperExtensions { /// /// Re-maps an existing registration. /// /// The re-mapper. /// The service/implementation type. /// The configurator for the registered type. /// The instance. public static IStashboxContainer ReMap(this IDependencyReMapper reMapper, Type typeTo, Action? configurator = null) => reMapper.ReMap(typeTo, typeTo, configurator); } ================================================ FILE: src/Registration/Extensions/ServiceRepositoryExtensions.cs ================================================ using Stashbox.Registration.SelectionRules; using Stashbox.Resolution; using Stashbox.Utils.Data; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; namespace Stashbox.Registration.Extensions; internal static class ServiceRepositoryExtensions { public static bool ContainsRegistration(this ImmutableTree> repository, Type type, object? name, bool includeOpenGenerics) { var registrations = repository.GetOrDefaultByRef(type); if (name != null && registrations != null) return Array.Exists(registrations.Repository, reg => name.Equals(reg.Name)); if (registrations != null || !includeOpenGenerics || !type.IsClosedGenericType()) return registrations != null; registrations = repository.GetOrDefaultByRef(type.GetGenericTypeDefinition()); return registrations?.Repository != null && Array.Exists(registrations.Repository, reg => reg.ImplementationType.SatisfiesGenericConstraintsOf(type)); } public static ServiceRegistration? SelectOrDefault(this IEnumerable registrations, TypeInformation typeInformation, ResolutionContext resolutionContext, IRegistrationSelectionRule[] registrationSelectionRules) { var maxWeight = 0; ServiceRegistration? result = null; foreach (var serviceRegistration in registrations) { if (!registrationSelectionRules.IsSelectionPassed(typeInformation, serviceRegistration, resolutionContext, out var weight)) continue; if (weight < maxWeight) continue; maxWeight = weight; result = serviceRegistration; } return result; } public static IEnumerable? FilterExclusiveOrDefault(this IEnumerable registrations, TypeInformation typeInformation, ResolutionContext resolutionContext, IRegistrationSelectionRule[] registrationSelectionRules) { var common = new ExpandableArray(); var priority = new ExpandableArray(); foreach (var serviceRegistration in registrations) { if (!registrationSelectionRules.IsSelectionPassed(typeInformation, serviceRegistration, resolutionContext, out var weight)) continue; if (weight > 0) priority.Add(serviceRegistration); else common.Add(serviceRegistration); } if (common.Length == 0 && priority.Length == 0) return null; return priority.Length > 0 ? priority : common; } public static IEnumerable FilterInclusive(this IEnumerable registrations, TypeInformation typeInformation, ResolutionContext resolutionContext, IRegistrationSelectionRule[] registrationSelectionRules) => registrations.Where(serviceRegistration => registrationSelectionRules.IsSelectionPassed(typeInformation, serviceRegistration, resolutionContext, out _)); private static bool IsSelectionPassed(this IRegistrationSelectionRule[] registrationSelectionRules, TypeInformation typeInformation, ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, out int weight) { weight = 0; var filterLength = registrationSelectionRules.Length; for (var i = 0; i < filterLength; i++) { if (!registrationSelectionRules[i].IsValidForCurrentRequest(typeInformation, serviceRegistration, resolutionContext, out var shouldIncrementWeight)) return false; if (shouldIncrementWeight) weight++; } return true; } } ================================================ FILE: src/Registration/Fluent/BaseDecoratorConfigurator.cs ================================================ using Stashbox.Lifetime; using System; using System.Linq; using Stashbox.Utils; namespace Stashbox.Registration.Fluent; /// /// Represents the generic base of the fluent registration api. /// /// public class BaseDecoratorConfigurator : BaseFluentConfigurator where TConfigurator : BaseDecoratorConfigurator { internal BaseDecoratorConfigurator(Type serviceType, Type implementationType, object? name) : base(serviceType, implementationType, name, Lifetimes.Empty, true) { } /// /// Sets a decorated target condition for the registration. /// /// The type of the parent. /// The fluent configurator. public TConfigurator WhenDecoratedServiceIs() where TTarget : class => this.WhenDecoratedServiceIs(TypeCache.Type); /// /// Sets a decorated target condition for the registration. /// /// The type of the decorated service. /// The fluent configurator. public TConfigurator WhenDecoratedServiceIs(Type targetType) => this.When(typeInfo => typeInfo.Type == targetType); /// /// Sets a decorated target condition for the registration. /// /// The name of the decorated service. /// The fluent configurator. public TConfigurator WhenDecoratedServiceIs(object name) => this.When(typeInfo => name.Equals(typeInfo.DependencyName)); /// /// Sets an attribute condition that the decorated target has to satisfy. /// /// The type of the attribute. /// The fluent configurator. public TConfigurator WhenDecoratedServiceHas() where TAttribute : Attribute => this.WhenDecoratedServiceHas(TypeCache.Type); /// /// Sets an attribute condition that the decorated target has to satisfy. /// /// The type of the attribute. /// The fluent configurator. public TConfigurator WhenDecoratedServiceHas(Type attributeType) => this.When(t => t.Type.GetCustomAttributes(attributeType, false).FirstOrDefault() != null); } ================================================ FILE: src/Registration/Fluent/BaseFluentConfigurator.cs ================================================ using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Lifetime; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; namespace Stashbox.Registration.Fluent; /// /// Represents the base of the fluent registration api. /// /// public class BaseFluentConfigurator : ServiceRegistration where TConfigurator : BaseFluentConfigurator { /// /// The service type. /// public Type ServiceType { get; } internal BaseFluentConfigurator(Type serviceType, Type implementationType, object? name, LifetimeDescriptor lifetimeDescriptor, bool isDecorator) : base(implementationType, name, lifetimeDescriptor, isDecorator) { this.ServiceType = serviceType; } /// /// Determines whether the registration is mapped to the given service type. /// /// The target service type. /// True when the registration is mapped to the given service type, otherwise false. public bool HasServiceType() => this.HasServiceType(TypeCache.Type); /// /// Determines whether the registration is mapped to the given service type. /// /// The target service type. /// True when the registration is mapped to the given service type, otherwise false. public bool HasServiceType(Type serviceType) { Shield.EnsureNotNull(serviceType, nameof(serviceType)); if (this.ServiceType == serviceType) return true; if (this.Options == null) return false; if (this.Options.TryGetValue(RegistrationOption.AdditionalServiceTypes, out var option) && option is ExpandableArray serviceTypes) return serviceTypes.Contains(serviceType); return false; } /// /// Sets the lifetime of the registration. /// /// An implementation. /// The configurator itself. public TConfigurator WithLifetime(LifetimeDescriptor lifetime) { this.Lifetime = lifetime; return (TConfigurator)this; } /// /// Sets scoped lifetime for the registration. /// /// The configurator itself. public TConfigurator WithScopedLifetime() => this.WithLifetime(Lifetimes.Scoped); /// /// Sets singleton lifetime for the registration. /// /// The configurator itself. public TConfigurator WithSingletonLifetime() => this.WithLifetime(Lifetimes.Singleton); /// /// Sets transient lifetime for the registration. /// /// The configurator itself. public TConfigurator WithTransientLifetime() => this.WithLifetime(Lifetimes.Transient); /// /// Sets the lifetime to . This lifetime will create a new instance between resolution requests. /// Within the request the same instance will be re-used. /// /// The configurator itself. public TConfigurator WithPerRequestLifetime() => this.WithLifetime(Lifetimes.PerRequest); /// /// Sets the lifetime to auto lifetime. This lifetime aligns to the lifetime of the resolved service's dependencies. /// When the underlying service has a dependency with a higher lifespan, this lifetime will inherit that lifespan up to a given boundary. /// /// The lifetime that represents a boundary which the derived lifetime must not exceed. /// The configurator itself. public TConfigurator WithAutoLifetime(LifetimeDescriptor boundaryLifetime) => this.WithLifetime(Lifetimes.Auto(boundaryLifetime)); /// /// Sets a scope name condition for the registration, it will be used only when a scope with the given name requests it. /// /// The name of the scope. /// The configurator itself. public TConfigurator InNamedScope(object scopeName) => this.WithLifetime(Lifetimes.NamedScope(scopeName)); /// /// Sets a condition for the registration that it will be used only within the scope defined by the given type. /// /// The type which defines the scope. /// The configurator itself. public TConfigurator InScopeDefinedBy(Type type) => this.WithLifetime(Lifetimes.NamedScope(type)); /// /// Sets a condition for the registration that it will be used only within the scope defined by the given type. /// /// The configurator itself. public TConfigurator InScopeDefinedBy() => this.WithLifetime(Lifetimes.NamedScope(TypeCache.Type)); /// /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency. /// /// The name of the bound named registration. /// The configurator itself. public TConfigurator WithDependencyBinding(object? dependencyName = null) => this.WithDependencyBinding(TypeCache.Type, dependencyName); /// /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency. /// /// The type of the dependency to search for. /// The name of the bound named registration. /// The fluent configurator. public TConfigurator WithDependencyBinding(Type dependencyType, object? dependencyName = null) { Shield.EnsureNotNull(dependencyType, nameof(dependencyType)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.DependencyBindings, out var value) && value is Dictionary bindings) bindings.Add(dependencyType, dependencyName); else this.Options[RegistrationOption.DependencyBindings] = new Dictionary { { dependencyType, dependencyName } }; return (TConfigurator)this; } /// /// Binds a constructor/method parameter or a property/field to a named registration, so the container will perform a named resolution on the bound dependency. /// /// The parameter name of the dependency to search for. /// The name of the bound named registration. /// The fluent configurator. public TConfigurator WithDependencyBinding(string parameterName, object? dependencyName = null) { Shield.EnsureNotNull(parameterName, nameof(parameterName)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.DependencyBindings, out var value) && value is Dictionary bindings) bindings.Add(parameterName, dependencyName); else this.Options[RegistrationOption.DependencyBindings] = new Dictionary { { parameterName, dependencyName } }; return (TConfigurator)this; } /// /// Sets a parent target condition for the registration. /// /// The type of the parent. /// The optional name of the parent. /// The configurator itself. public TConfigurator WhenDependantIs(object? name = null) where TTarget : class => this.WhenDependantIs(TypeCache.Type, name); /// /// Sets a parent target condition for the registration. /// /// The type of the parent. /// The optional name of the parent. /// The configurator itself. public TConfigurator WhenDependantIs(Type targetType, object? name = null) { Shield.EnsureNotNull(targetType, nameof(targetType)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.ConditionOptions, out var value) && value is ConditionOptions conditions) { conditions.TargetTypeConditions ??= []; conditions.TargetTypeConditions.Add(new ReadOnlyKeyValue(name, targetType)); } else this.Options[RegistrationOption.ConditionOptions] = new ConditionOptions { TargetTypeConditions = new ExpandableArray { new ReadOnlyKeyValue(name, targetType) } }; return (TConfigurator)this; } /// /// Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the given target. /// This means that only the direct and sub-dependencies of the target type will get the configured service. /// /// The type of the parent. /// The optional name of the parent. /// The configurator itself. public TConfigurator WhenInResolutionPathOf(object? name = null) where TTarget : class => this.WhenInResolutionPathOf(TypeCache.Type, name); /// /// Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the given target. /// This means that only the direct and sub-dependencies of the target type will get the configured service. /// /// The type of the parent. /// The optional name of the parent. /// The configurator itself. public TConfigurator WhenInResolutionPathOf(Type targetType, object? name = null) { Shield.EnsureNotNull(targetType, nameof(targetType)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.ConditionOptions, out var value) && value is ConditionOptions conditions) { conditions.TargetTypeInResolutionPathConditions ??= []; conditions.TargetTypeInResolutionPathConditions.Add(new ReadOnlyKeyValue(name, targetType)); } else this.Options[RegistrationOption.ConditionOptions] = new ConditionOptions { TargetTypeInResolutionPathConditions = new ExpandableArray { new ReadOnlyKeyValue(name, targetType) } }; return (TConfigurator)this; } /// /// Sets an attribute condition for the registration. /// /// The type of the attribute. /// The configurator itself. public TConfigurator WhenHas(object? name = null) where TAttribute : Attribute => this.WhenHas(TypeCache.Type); /// /// Sets an attribute condition for the registration. /// /// The type of the attribute. /// The configurator itself. public TConfigurator WhenHas(Type attributeType) { Shield.EnsureNotNull(attributeType, nameof(attributeType)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.ConditionOptions, out var value) && value is ConditionOptions conditions) { conditions.AttributeConditions ??= []; conditions.AttributeConditions.Add(attributeType); } else this.Options[RegistrationOption.ConditionOptions] = new ConditionOptions { AttributeConditions = new ExpandableArray { attributeType } }; return (TConfigurator)this; } /// /// Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the target that has the given attribute. /// This means that only the direct and sub-dependencies of the target type that has the given attribute will get the configured service. /// /// The type of the attribute. /// The configurator itself. public TConfigurator WhenResolutionPathHas(object? name = null) where TAttribute : Attribute => this.WhenResolutionPathHas(TypeCache.Type, name); /// /// Sets a resolution path condition for the registration. The service will be selected only in the resolution path of the target that has the given attribute. /// This means that only the direct and sub-dependencies of the target type that has the given attribute will get the configured service. /// /// The type of the attribute. /// The optional name of the target that has the desired attribute. /// The configurator itself. public TConfigurator WhenResolutionPathHas(Type attributeType, object? name = null) { Shield.EnsureNotNull(attributeType, nameof(attributeType)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.ConditionOptions, out var value) && value is ConditionOptions conditions) { conditions.AttributeInResolutionPathConditions ??= []; conditions.AttributeInResolutionPathConditions.Add(new ReadOnlyKeyValue(name, attributeType)); } else this.Options[RegistrationOption.ConditionOptions] = new ConditionOptions { AttributeInResolutionPathConditions = new ExpandableArray { new ReadOnlyKeyValue(name, attributeType) } }; return (TConfigurator)this; } /// /// Sets a generic condition for the registration. /// /// The predicate. /// The configurator itself. public TConfigurator When(Func resolutionCondition) { Shield.EnsureNotNull(resolutionCondition, nameof(resolutionCondition)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.ConditionOptions, out var value) && value is ConditionOptions conditions) { conditions.ResolutionConditions ??= []; conditions.ResolutionConditions.Add(resolutionCondition); } else this.Options[RegistrationOption.ConditionOptions] = new ConditionOptions { ResolutionConditions = new ExpandableArray> { resolutionCondition } }; return (TConfigurator)this; } /// /// Sets injection parameters for the registration. /// /// The injection parameters. /// The fluent configurator. public TConfigurator WithInjectionParameters(params KeyValuePair[] injectionParameters) { this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.InjectionParameters, out var value) && value is ExpandableArray> parameters) parameters.AddRange(injectionParameters); else this.Options[RegistrationOption.InjectionParameters] = new ExpandableArray>(injectionParameters); return (TConfigurator)this; } /// /// Sets injection parameters for the registration. /// /// The name of the injection parameter. /// The value of the injection parameter. /// The fluent configurator. public TConfigurator WithInjectionParameter(string name, object? value) { Shield.EnsureNotNull(name, nameof(name)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.InjectionParameters, out var injectionParams) && injectionParams is ExpandableArray> parameters) parameters.Add(new KeyValuePair(name, value)); else this.Options[RegistrationOption.InjectionParameters] = new ExpandableArray> { new KeyValuePair(name, value) }; return (TConfigurator)this; } /// /// Enables auto member injection on the registration. /// /// The auto member injection rule. /// A filter delegate used to determine which members should be auto injected and which are not. /// The fluent configurator. public TConfigurator WithAutoMemberInjection(Rules.AutoMemberInjectionRules rule = Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, Func? filter = null) { this.Options ??= new Dictionary(); this.Options[RegistrationOption.AutoMemberOptions] = new AutoMemberOptions(rule, filter); return (TConfigurator)this; } /// /// Enables or disables required member injection. /// /// True when the feature should be enabled, otherwise false. /// The container configurator. public TConfigurator WithRequiredMemberInjection(bool enabled = true) { this.Options ??= new Dictionary(); this.Options[RegistrationOption.RequiredMemberInjectionEnabled] = enabled; return (TConfigurator)this; } /// /// The constructor selection rule. /// /// The constructor selection rule. /// The fluent configurator. public TConfigurator WithConstructorSelectionRule(Func, IEnumerable> rule) { this.Options ??= new Dictionary(); this.Options[RegistrationOption.ConstructorSelectionRule] = rule; return (TConfigurator)this; } /// /// Sets the selected constructor. /// /// The constructor argument types. /// The fluent configurator. /// public TConfigurator WithConstructorByArgumentTypes(params Type[] argumentTypes) { var constructor = this.ImplementationType.GetConstructor(argumentTypes); if (constructor == null) { ThrowConstructorNotFoundException(this.ImplementationType, argumentTypes); return (TConfigurator)this; } this.Options ??= new Dictionary(); this.Options[RegistrationOption.ConstructorOptions] = new ConstructorOptions(constructor, null); return (TConfigurator)this; } /// /// Sets the selected constructor. /// /// The constructor arguments. /// The fluent configurator. /// public TConfigurator WithConstructorByArguments(params object[] arguments) { var argTypes = arguments.Select(arg => arg.GetType()).ToArray(); var constructor = this.ImplementationType.GetConstructor(argTypes); if (constructor == null) { ThrowConstructorNotFoundException(this.ImplementationType, argTypes); return (TConfigurator)this; } this.Options ??= new Dictionary(); this.Options[RegistrationOption.ConstructorOptions] = new ConstructorOptions(constructor, arguments); return (TConfigurator)this; } /// /// Tells the container that it shouldn't track the resolved transient object for disposal. /// /// The fluent configurator. public TConfigurator WithoutDisposalTracking() { this.Options ??= new Dictionary(); this.Options[RegistrationOption.IsLifetimeExternallyOwned] = true; return (TConfigurator)this; } /// /// Tells the container that it should replace an existing registration with the current one, or add it if there is no existing found. /// /// The fluent configurator. public TConfigurator ReplaceExisting() { this.Options ??= new Dictionary(); this.Options[RegistrationOption.ReplaceExistingRegistration] = true; return (TConfigurator)this; } /// /// Tells the container that it should replace an existing registration with the current one, but only if there is an existing registration. /// /// The fluent configurator. public TConfigurator ReplaceOnlyIfExists() { this.Options ??= new Dictionary(); this.Options[RegistrationOption.ReplaceExistingRegistrationOnlyIfExists] = true; return (TConfigurator)this; } /// /// Registers the given service by all of it's implemented types. /// /// The configurator itself. public TConfigurator AsImplementedTypes() { this.Options ??= new Dictionary(); var additionalTypes = this.ImplementationType.GetRegisterableInterfaceTypes() .Concat(this.ImplementationType.GetRegisterableBaseTypes()); if (this.Options.TryGetValue(RegistrationOption.AdditionalServiceTypes, out var option) && option is ExpandableArray serviceTypes) serviceTypes.AddRange(additionalTypes); else this.Options[RegistrationOption.AdditionalServiceTypes] = new ExpandableArray(additionalTypes); return (TConfigurator)this; } /// /// Binds the currently configured registration to an additional service type. /// /// The configurator itself. public TConfigurator AsServiceAlso() => this.AsServiceAlso(TypeCache.Type); /// /// Binds the currently configured registration to an additional service type. /// /// The additional service type. /// The configurator itself. public TConfigurator AsServiceAlso(Type serviceType) { if (!IsFactory() && !this.ImplementationType.Implements(serviceType)) throw new ArgumentException($"The implementation type {this.ImplementationType} does not implement the given service type {serviceType}."); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.AdditionalServiceTypes, out var option) && option is ExpandableArray serviceTypes) serviceTypes.Add(serviceType); else this.Options[RegistrationOption.AdditionalServiceTypes] = new ExpandableArray { serviceType }; return (TConfigurator)this; } internal void ValidateTypeMap() { if (IsFactory()) return; Shield.EnsureTypeMapIsValid(ServiceType, ImplementationType); } internal void ValidateImplementationIsResolvable() { if (IsFactory()) return; Shield.EnsureIsResolvable(ImplementationType); } private protected TConfigurator SetFactory(Delegate factory, Type implementationType, bool isCompiledLambda, params Type[] parameterTypes) { Shield.EnsureNotNull(implementationType, nameof(implementationType)); this.ImplementationType = implementationType; return this.SetFactory(factory, isCompiledLambda, parameterTypes); } private protected TConfigurator SetFactory(Delegate factory, bool isCompiledLambda, params Type[] parameterTypes) { Shield.EnsureNotNull(factory, nameof(factory)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.RegistrationTypeOptions] = new FactoryOptions(factory, parameterTypes, isCompiledLambda); return (TConfigurator)this; } private static void ThrowConstructorNotFoundException(Type type, params Type[] argTypes) { throw argTypes.Length switch { 0 => new ConstructorNotFoundException(type), 1 => new ConstructorNotFoundException(type, argTypes[0]), _ => new ConstructorNotFoundException(type, argTypes) }; } } ================================================ FILE: src/Registration/Fluent/DecoratorConfigurator.cs ================================================ using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; namespace Stashbox.Registration.Fluent; /// /// Represents the fluent service decorator registration api. /// public class DecoratorConfigurator : BaseDecoratorConfigurator> where TService : class where TImplementation : class, TService { internal DecoratorConfigurator(Type serviceType, Type implementationType, object? name = null) : base(serviceType, implementationType, name) { } /// /// Sets a member (property / field) as a dependency that should be filled by the container. /// /// The member expression. /// The name of the dependency. /// The fluent configurator. public DecoratorConfigurator WithDependencyBinding(Expression> expression, object? dependencyName = null) { if (expression.Body is not MemberExpression memberExpression) throw new ArgumentException("The expression must be a member expression (Property or Field)", nameof(expression)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.DependencyBindings, out var value) && value is Dictionary bindings) bindings.Add(memberExpression.Member.Name, dependencyName); else this.Options[RegistrationOption.DependencyBindings] = new Dictionary { { memberExpression.Member.Name, dependencyName } }; return this; } /// /// Sets a delegate which will be called when the container is being disposed. /// /// The cleanup delegate. /// The fluent configurator. public DecoratorConfigurator WithFinalizer(Action finalizer) { Shield.EnsureNotNull(finalizer, nameof(finalizer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.Finalizer] = new Action(o => finalizer((TImplementation)o)); return this; } /// /// Sets a delegate which will be called when the service is being constructed. /// /// The initializer delegate. /// The fluent configurator. public DecoratorConfigurator WithInitializer(Action initializer) { Shield.EnsureNotNull(initializer, nameof(initializer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.Initializer] = initializer; return this; } /// /// Sets an async initializer delegate which will be called when is called. /// /// The async initializer delegate. /// The fluent configurator. public DecoratorConfigurator WithAsyncInitializer(Func initializer) { Shield.EnsureNotNull(initializer, nameof(initializer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.AsyncInitializer] = new Func((o, r, t) => initializer((TImplementation)o, r, t)); return this; } /// /// Sets a parameter-less factory delegate for the registration. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type); /// /// Sets a factory delegate for the registration that takes an as parameter. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); } /// /// Represents the fluent service decorator registration api. /// public class DecoratorConfigurator : BaseDecoratorConfigurator { internal DecoratorConfigurator(Type serviceType, Type implementationType, object? name = null) : base(serviceType, implementationType, name) { } /// /// Sets a container factory delegate for the registration. /// /// The container factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameter-less factory delegate for the registration. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public DecoratorConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); } ================================================ FILE: src/Registration/Fluent/FluentServiceConfigurator.cs ================================================ using Stashbox.Lifetime; using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq.Expressions; using System.Threading; using System.Threading.Tasks; using Stashbox.Resolution; namespace Stashbox.Registration.Fluent; /// /// Represents the generic fluent service registration api. /// public class FluentServiceConfigurator : FluentServiceConfigurator where TConfigurator : FluentServiceConfigurator where TService : class where TImplementation : class, TService { internal FluentServiceConfigurator(Type serviceType, Type implementationType, object? name, LifetimeDescriptor lifetimeDescriptor, bool isDecorator) : base(serviceType, implementationType, name, lifetimeDescriptor, isDecorator) { } /// /// Sets a member (property / field) as a dependency that should be filled by the container. /// /// The member expression. /// The name of the dependency. /// The fluent configurator. public TConfigurator WithDependencyBinding(Expression> expression, object? dependencyName = null) { if (expression.Body is not MemberExpression memberExpression) throw new ArgumentException("The expression must be a member expression (Property or Field)", nameof(expression)); this.Options ??= new Dictionary(); if (this.Options.TryGetValue(RegistrationOption.DependencyBindings, out var value) && value is Dictionary bindings) bindings.Add(memberExpression.Member.Name, dependencyName); else this.Options[RegistrationOption.DependencyBindings] = new Dictionary { { memberExpression.Member.Name, dependencyName } }; return (TConfigurator)this; } /// /// Sets a delegate which will be called when the container is being disposed. /// /// The cleanup delegate. /// The fluent configurator. public TConfigurator WithFinalizer(Action finalizer) { Shield.EnsureNotNull(finalizer, nameof(finalizer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.Finalizer] = new Action(o => finalizer((TImplementation)o)); return (TConfigurator)this; } /// /// Sets a delegate which will be called when the service is being constructed. /// /// The initializer delegate. /// The fluent configurator. public TConfigurator WithInitializer(Action initializer) { Shield.EnsureNotNull(initializer, nameof(initializer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.Initializer] = initializer; return (TConfigurator)this; } /// /// Sets an async initializer delegate which will be called when is called. /// /// The async initializer delegate. /// The fluent configurator. public TConfigurator WithAsyncInitializer(Func initializer) { Shield.EnsureNotNull(initializer, nameof(initializer)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.AsyncInitializer] = new Func((o, r, t) => initializer((TImplementation)o, r, t)); return (TConfigurator)this; } /// /// Sets a parameter-less factory delegate for the registration. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameter-less factory delegate for the registration. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, TypeCache.Type, isCompiledLambda, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a factory delegate for the registration that takes an as parameter. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a factory delegate for the registration that takes an as parameter. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, TypeCache.Type, isCompiledLambda, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, TypeCache.Type, isCompiledLambda, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) { this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); return (TConfigurator)this; } } /// /// Represents the fluent service registration api. /// public class FluentServiceConfigurator : BaseFluentConfigurator where TConfigurator : FluentServiceConfigurator { internal FluentServiceConfigurator(Type serviceType, Type implementationType, object? name, LifetimeDescriptor lifetimeDescriptor, bool isDecorator) : base(serviceType, implementationType, name, lifetimeDescriptor, isDecorator) { } /// /// Indicates that the service's resolution should be handled by a dynamic call on the current instead of a pre-built instantiation expression. /// /// public TConfigurator WithDynamicResolution() { this.Options ??= new Dictionary(); this.Options[RegistrationOption.IsResolutionCallRequired] = true; return (TConfigurator)this; } /// /// Sets the metadata. /// /// The metadata. /// The fluent configurator. public TConfigurator WithMetadata(object? metadata) { this.Options ??= new Dictionary(); this.Options[RegistrationOption.Metadata] = metadata; return (TConfigurator)this; } /// /// Sets the name of the registration. /// /// The name of the registration. /// The fluent configurator. public TConfigurator WithName(object? name) { this.Name = name; return (TConfigurator)this; } /// /// This registration is used as a logical scope for it's dependencies. Dependencies registered with the with the same name will be preferred during resolution. /// /// The name of the scope. When the name is null, the type which defines the scope is used as name. /// The fluent configurator. public TConfigurator DefinesScope(object? scopeName = null) { this.Options ??= new Dictionary(); this.Options[RegistrationOption.DefinedScopeName] = scopeName ?? this.ImplementationType; return (TConfigurator)this; } /// /// Sets a container factory delegate for the registration. /// /// The container factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameter-less factory delegate for the registration. /// /// The factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); /// /// Sets a parameterized factory delegate for the registration. /// /// The parameterized factory delegate. /// Flag that indicates the passed factory delegate is a compiled lambda from . /// The fluent configurator. public TConfigurator WithFactory(Func factory, bool isCompiledLambda = false) => this.SetFactory(factory, isCompiledLambda, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type); } ================================================ FILE: src/Registration/Fluent/RegistrationConfigurator.cs ================================================ using Stashbox.Lifetime; using Stashbox.Utils; using System; using System.Collections.Generic; namespace Stashbox.Registration.Fluent; /// /// Represents the generic fluent service registration api. /// public class RegistrationConfigurator : FluentServiceConfigurator> where TService : class where TImplementation : class, TService { internal RegistrationConfigurator(Type serviceType, Type implementationType, LifetimeDescriptor lifetimeDescriptor, object? name = null) : base(serviceType, implementationType, name, lifetimeDescriptor, false) { } /// /// Sets an instance as the resolution target of the registration. /// /// The instance. /// If true, the instance will be wired into the container, it will perform member and method injection on it. /// The fluent configurator. public RegistrationConfigurator WithInstance(TService instance, bool wireUp = false) { Shield.EnsureNotNull(instance, nameof(instance)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.RegistrationTypeOptions] = new InstanceOptions(instance, wireUp); this.ImplementationType = instance.GetType(); return this; } } /// /// Represents the fluent service registration api. /// public class RegistrationConfigurator : FluentServiceConfigurator { internal RegistrationConfigurator(Type serviceType, Type implementationType, LifetimeDescriptor lifetimeDescriptor, object? name = null) : base(serviceType, implementationType, name, lifetimeDescriptor, false) { } /// /// Sets an instance as the resolution target of the registration. /// /// The instance. /// If true, the instance will be wired into the container, it will perform member and method injection on it. /// The fluent configurator. public RegistrationConfigurator WithInstance(object instance, bool wireUp = false) { Shield.EnsureNotNull(instance, nameof(instance)); this.Options ??= new Dictionary(); this.Options[RegistrationOption.RegistrationTypeOptions] = new InstanceOptions(instance, wireUp); this.ImplementationType = instance.GetType(); return this; } } ================================================ FILE: src/Registration/Fluent/UnknownRegistrationConfigurator.cs ================================================ using Stashbox.Lifetime; using System; namespace Stashbox.Registration.Fluent; /// /// Represents the fluent service registration api. /// public class UnknownRegistrationConfigurator : RegistrationConfigurator { internal bool RegistrationShouldBeSkipped { get; private set; } internal UnknownRegistrationConfigurator(Type serviceType, Type implementationType, object? name, LifetimeDescriptor lifetimeDescriptor) : base(serviceType, implementationType, lifetimeDescriptor, name) { } /// /// Sets the current registration's implementation type. /// /// The implementation type. /// The fluent configurator. public UnknownRegistrationConfigurator SetImplementationType(Type implementationType) { if (!implementationType.Implements(this.ServiceType)) throw new ArgumentException($"The type {implementationType} does not implement the actual service type {this.ServiceType}."); this.ImplementationType = implementationType; return this; } /// /// Marks the current unknown type registration as skipped. /// /// The fluent configurator. public UnknownRegistrationConfigurator Skip() { this.RegistrationShouldBeSkipped = true; return this; } } ================================================ FILE: src/Registration/IDecoratorRepository.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; namespace Stashbox.Registration; /// /// Represents a decorator registration repository. /// public interface IDecoratorRepository { /// /// Adds a decorator to the repository. /// /// The decorated type. /// The decorator registration. /// If true, all the registrations mapped to a service type will be replaced. void AddDecorator(Type type, ServiceRegistration serviceRegistration, bool remap); /// /// Gets all decorator registration. /// /// The implementation type to decorate. /// The info about the decorated type. /// The resolution context. /// The decorator registrations if any exists, otherwise null. IEnumerable? GetDecoratorsOrDefault(Type implementationTypeToDecorate, TypeInformation typeInformation, ResolutionContext resolutionContext); /// /// Returns all registration mappings. /// /// The registration mappings. IEnumerable> GetRegistrationMappings(); } ================================================ FILE: src/Registration/IRegistrationRepository.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; namespace Stashbox.Registration; /// /// Represents a registration repository. /// public interface IRegistrationRepository { /// /// Adds or updates an element in the repository. /// /// The registration. /// The service type of the registration. Used as the key for the registration mapping. /// True when the repository changed, otherwise false. bool AddOrUpdateRegistration(ServiceRegistration registration, Type serviceType); /// /// Remaps all the registrations mapped to a service type. /// /// The registration. /// The service type of the registration. Used as the key for the registration mapping. /// True when the repository changed, otherwise false. bool AddOrReMapRegistration(ServiceRegistration registration, Type serviceType); /// /// Returns a registration. /// /// The type info. /// The resolution context. /// The registration or null, if it doesn't exist. ServiceRegistration? GetRegistrationOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext); /// /// Returns all registrations for a type. /// /// The requested type. /// The resolution context. /// The registrations or null, if it doesn't exist. IEnumerable? GetRegistrationsOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext); /// /// Returns all registration mappings. /// /// The registration mappings. IEnumerable> GetRegistrationMappings(); /// /// Checks whether a type is registered in the repository. /// /// The requested type. /// The requested name. /// Determines whether open generic registrations should be taken into account when the given type is closed generic. /// True if the registration found, otherwise false. bool ContainsRegistration(Type type, object? name, bool includeOpenGenerics = true); } ================================================ FILE: src/Registration/OpenGenericRegistration.cs ================================================ using Stashbox.Utils.Data.Immutable; using Stashbox.Utils; using System; namespace Stashbox.Registration; /// /// Describes an open-generic service registration. /// public class OpenGenericRegistration : ServiceRegistration { private ImmutableTree closedGenericRegistrations = ImmutableTree.Empty; internal OpenGenericRegistration(ServiceRegistration serviceRegistration) : base(serviceRegistration.ImplementationType, serviceRegistration.Name, serviceRegistration.Lifetime, serviceRegistration.IsDecorator, serviceRegistration.Options, serviceRegistration.RegistrationId, serviceRegistration.RegistrationOrder) { } internal ServiceRegistration ProduceClosedRegistration(Type requestedType) { var found = closedGenericRegistrations.GetOrDefaultByRef(requestedType); if (found != null) return found; var genericType = ImplementationType.MakeGenericType(requestedType.GetGenericArguments()); var newRegistration = new ServiceRegistration(genericType, null, Lifetime, IsDecorator, Options); return Swap.SwapValue(ref closedGenericRegistrations, (t1, t2, _, _, items) => items.AddOrUpdate(t1, t2, true), requestedType, newRegistration, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder) ? newRegistration : closedGenericRegistrations.GetOrDefaultByRef(requestedType)!; } } ================================================ FILE: src/Registration/RegistrationDiagnosticsInfo.cs ================================================ using System; namespace Stashbox.Registration; /// /// Details about a registration. /// public readonly struct RegistrationDiagnosticsInfo { /// /// The service type. /// public readonly Type ServiceType; /// /// The implementation type. /// public readonly Type ImplementationType; /// /// The registration name. /// public readonly object? Name; /// /// Constructs a . /// /// The service type. /// The implementation type. /// The registration name. public RegistrationDiagnosticsInfo(Type serviceType, Type implementationType, object? name) : this() { this.ServiceType = serviceType; this.ImplementationType = implementationType; this.Name = name; } /// /// The string representation of the registration. /// /// The string representation of the registration. public override string ToString() => $"{this.ServiceType.GetDiagnosticsView()} => {this.ImplementationType.GetDiagnosticsView()}, name: {this.Name ?? "null"}"; } ================================================ FILE: src/Registration/RegistrationRepository.cs ================================================ using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Registration.Extensions; using Stashbox.Registration.SelectionRules; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; namespace Stashbox.Registration; internal class RegistrationRepository(ContainerConfiguration containerConfiguration) : IRegistrationRepository { private ImmutableTree> serviceRepository = ImmutableTree>.Empty; private readonly IRegistrationSelectionRule[] filters = [ RegistrationSelectionRules.GenericFilter, RegistrationSelectionRules.NameFilter, RegistrationSelectionRules.MetadataFilter, RegistrationSelectionRules.ScopeNameFilter, RegistrationSelectionRules.ConditionFilter ]; private readonly IRegistrationSelectionRule[] topLevelFilters = [ RegistrationSelectionRules.GenericFilter, RegistrationSelectionRules.NameFilter, RegistrationSelectionRules.MetadataFilter, RegistrationSelectionRules.ScopeNameFilter ]; private readonly IRegistrationSelectionRule[] enumerableFilters = [ RegistrationSelectionRules.GenericFilter, RegistrationSelectionRules.EnumerableNameFilter, RegistrationSelectionRules.ScopeNameFilter, RegistrationSelectionRules.ConditionFilter, RegistrationSelectionRules.MetadataFilter ]; public bool AddOrUpdateRegistration(ServiceRegistration registration, Type serviceType) { if (registration.Options.IsOn(RegistrationOption.ReplaceExistingRegistrationOnlyIfExists)) return Swap.SwapValue(ref this.serviceRepository, (reg, type, _, _, repo) => repo.UpdateIfExists(type, true, regs => { var existingIndex = -1; for (var i = 0; i < regs.Length; i++) { var current = regs[i]; var existingDiscriminator = current.Name ?? current.ImplementationType; var newDiscriminator = reg.Name ?? reg.ImplementationType; if (existingDiscriminator.Equals(newDiscriminator)) existingIndex = i; } if (existingIndex == -1) return regs; var replaced = regs[existingIndex]; reg.Replaces(replaced); return regs.ReplaceAt(existingIndex, reg); }), registration, serviceType, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return Swap.SwapValue(ref this.serviceRepository, (reg, type, newRepo, regBehavior, repo) => repo.AddOrUpdate(type, newRepo, true, (oldValue, _) => { var replaceExisting = reg.Options.IsOn(RegistrationOption.ReplaceExistingRegistration); var allowUpdate = replaceExisting || regBehavior == Rules.RegistrationBehavior.ReplaceExisting; if (!allowUpdate && regBehavior == Rules.RegistrationBehavior.PreserveDuplications) return oldValue.Add(reg); var existingIndex = -1; for (var i = 0; i < oldValue.Length; i++) { var current = oldValue[i]; if (!replaceExisting && current.ImplementationType != reg.ImplementationType) continue; var existingDiscriminator = current.Name ?? current.ImplementationType; var newDiscriminator = reg.Name ?? reg.ImplementationType; if (existingDiscriminator.Equals(newDiscriminator)) existingIndex = i; } if (existingIndex == -1) return oldValue.Add(reg); switch (allowUpdate) { case false when regBehavior == Rules.RegistrationBehavior.ThrowException: throw new ServiceAlreadyRegisteredException(reg.ImplementationType); case false: return oldValue; default: var replaced = oldValue[existingIndex]; reg.Replaces(replaced); return oldValue.ReplaceAt(existingIndex, reg); } }), registration, serviceType, new ImmutableBucket(registration), containerConfiguration.RegistrationBehavior); } public bool AddOrReMapRegistration(ServiceRegistration registration, Type serviceType) => registration.Options.IsOn(RegistrationOption.ReplaceExistingRegistrationOnlyIfExists) ? Swap.SwapValue(ref this.serviceRepository, (type, newRepo, _, _, repo) => repo.UpdateIfExists(type, newRepo, true), serviceType, new ImmutableBucket(registration), Constants.DelegatePlaceholder, Constants.DelegatePlaceholder) : Swap.SwapValue(ref this.serviceRepository, (type, newRepo, _, _, repo) => repo.AddOrUpdate(type, newRepo, true, true), serviceType, new ImmutableBucket(registration), Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); public bool ContainsRegistration(Type type, object? name, bool includeOpenGenerics = true) => serviceRepository.ContainsRegistration(type, name, includeOpenGenerics); public IEnumerable> GetRegistrationMappings() => serviceRepository.Walk().SelectMany(reg => reg.Value.Repository.Select(r => new KeyValuePair(reg.Key, r))); public ServiceRegistration? GetRegistrationOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext) => this.GetRegistrationsForType(typeInfo.Type)?.SelectOrDefault(typeInfo, resolutionContext, !typeInfo.IsDependency ? this.topLevelFilters : this.filters); public IEnumerable? GetRegistrationsOrDefault(TypeInformation typeInfo, ResolutionContext resolutionContext) => this.GetRegistrationsForType(typeInfo.Type) ?.FilterExclusiveOrDefault(typeInfo, resolutionContext, this.enumerableFilters) ?.OrderBy(reg => reg.RegistrationOrder); private IEnumerable? GetRegistrationsForType(Type type) { IEnumerable? registrations = serviceRepository.GetOrDefaultByRef(type)?.Repository; if (!type.IsClosedGenericType()) return registrations; var openGenerics = serviceRepository.GetOrDefaultByRef(type.GetGenericTypeDefinition())?.Repository; if (openGenerics != null) registrations = registrations == null ? openGenerics : openGenerics.Concat(registrations); if (!containerConfiguration.VariantGenericTypesEnabled) return registrations; var variantGenerics = serviceRepository.Walk() .Where(r => r.Key.IsGenericType && r.Key.GetGenericTypeDefinition() == type.GetGenericTypeDefinition() && r.Key != type && r.Key.ImplementsWithoutGenericCheck(type)) .SelectMany(r => r.Value.Repository).ToArray(); if (variantGenerics.Length > 0) registrations = registrations == null ? variantGenerics : variantGenerics.Concat(registrations); return registrations; } } ================================================ FILE: src/Registration/SelectionRules/ConditionRule.cs ================================================ using Stashbox.Resolution; using System.Collections.Generic; namespace Stashbox.Registration.SelectionRules; internal class ConditionRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { var conditions = registration.Options.GetOrDefault(RegistrationOption.ConditionOptions); if (conditions != null) { shouldIncrementWeight = ServiceRegistration.IsUsableForCurrentContext(typeInformation, conditions); return shouldIncrementWeight; } shouldIncrementWeight = false; return conditions is null; } } ================================================ FILE: src/Registration/SelectionRules/DecoratorRule.cs ================================================ using Stashbox.Resolution; namespace Stashbox.Registration.SelectionRules; internal class DecoratorRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { shouldIncrementWeight = false; return !resolutionContext.CurrentDecorators.ContainsReference(registration); } } ================================================ FILE: src/Registration/SelectionRules/EnumerableNameRule.cs ================================================ using Stashbox.Resolution; namespace Stashbox.Registration.SelectionRules; internal class EnumerableNameRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { if (typeInformation.DependencyName == null) { shouldIncrementWeight = false; return resolutionContext.CurrentContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled || registration.Name == null; } if (resolutionContext.CurrentContainerContext.ContainerConfiguration.IgnoreServicesWithUniversalNameForUniversalNamedRequests && typeInformation.DependencyName != null && typeInformation.DependencyName.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName) && registration.Name != null && registration.Name.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName)) { shouldIncrementWeight = false; return false; } if (typeInformation.DependencyName != null && registration.Name != null && registration.Name.Equals(typeInformation.DependencyName)) { shouldIncrementWeight = true; return true; } if (typeInformation.DependencyName != null && typeInformation.DependencyName.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName) && registration.Name != null) { shouldIncrementWeight = true; return true; } if (typeInformation.DependencyName != null && resolutionContext.CurrentContainerContext.ContainerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled && (registration.Name == null || (registration.Name != null && registration.Name.Equals(typeInformation.DependencyName)))) { shouldIncrementWeight = false; return true; } shouldIncrementWeight = false; return false; } } ================================================ FILE: src/Registration/SelectionRules/IRegistrationSelectionRule.cs ================================================ using Stashbox.Resolution; namespace Stashbox.Registration.SelectionRules; internal interface IRegistrationSelectionRule { bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight); } ================================================ FILE: src/Registration/SelectionRules/MetadataRule.cs ================================================ using Stashbox.Resolution; using System.Collections.Generic; namespace Stashbox.Registration.SelectionRules; internal class MetadataRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { shouldIncrementWeight = false; if (typeInformation.MetadataType != null) { var metadata = registration.Options.GetOrDefault(RegistrationOption.Metadata); shouldIncrementWeight = metadata != null && typeInformation.MetadataType.IsInstanceOfType(metadata); return shouldIncrementWeight; } return true; } } ================================================ FILE: src/Registration/SelectionRules/NameRule.cs ================================================ using Stashbox.Resolution; namespace Stashbox.Registration.SelectionRules; internal class NameRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { if (typeInformation.DependencyName == null && registration.Name == null) { shouldIncrementWeight = false; return true; } if (resolutionContext.CurrentContainerContext.ContainerConfiguration.IgnoreServicesWithUniversalNameForUniversalNamedRequests && typeInformation.DependencyName != null && typeInformation.DependencyName.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName)) { shouldIncrementWeight = false; return false; } if (typeInformation.DependencyName != null && registration.Name != null && registration.Name.Equals(typeInformation.DependencyName)) { shouldIncrementWeight = true; return true; } if (typeInformation.DependencyName != null && registration.Name != null && registration.Name.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName)) { shouldIncrementWeight = false; return true; } if (typeInformation.DependencyName != null && typeInformation.DependencyName.Equals(resolutionContext.CurrentContainerContext.ContainerConfiguration.UniversalName) && registration.Name != null) { shouldIncrementWeight = false; return true; } if (typeInformation.DependencyName == null && registration.Name != null && resolutionContext.CurrentContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedRequestsEnabled) { shouldIncrementWeight = false; return true; } if (typeInformation.DependencyName != null && resolutionContext.CurrentContainerContext.ContainerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled && (registration.Name == null || (registration.Name != null && registration.Name.Equals(typeInformation.DependencyName)))) { shouldIncrementWeight = false; return true; } shouldIncrementWeight = false; return false; } } ================================================ FILE: src/Registration/SelectionRules/OpenGenericRule.cs ================================================ using Stashbox.Resolution; using System; namespace Stashbox.Registration.SelectionRules; internal class OpenGenericRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { shouldIncrementWeight = false; return !typeInformation.Type.IsClosedGenericType() || registration.ImplementationType.SatisfiesGenericConstraintsOf(typeInformation.Type); } } ================================================ FILE: src/Registration/SelectionRules/RegistrationSelectionRules.cs ================================================ namespace Stashbox.Registration.SelectionRules; internal static class RegistrationSelectionRules { public static readonly IRegistrationSelectionRule ConditionFilter = new ConditionRule(); public static readonly IRegistrationSelectionRule GenericFilter = new OpenGenericRule(); public static readonly IRegistrationSelectionRule ScopeNameFilter = new ScopeNameRule(); public static readonly IRegistrationSelectionRule NameFilter = new NameRule(); public static readonly IRegistrationSelectionRule EnumerableNameFilter = new EnumerableNameRule(); public static readonly IRegistrationSelectionRule MetadataFilter = new MetadataRule(); public static readonly IRegistrationSelectionRule DecoratorFilter = new DecoratorRule(); } ================================================ FILE: src/Registration/SelectionRules/ScopeNameRule.cs ================================================ using Stashbox.Lifetime; using Stashbox.Resolution; namespace Stashbox.Registration.SelectionRules; internal class ScopeNameRule : IRegistrationSelectionRule { public bool IsValidForCurrentRequest(TypeInformation typeInformation, ServiceRegistration registration, ResolutionContext resolutionContext, out bool shouldIncrementWeight) { if (registration.Lifetime is not NamedScopeLifetime namedScopeLifetime) { shouldIncrementWeight = false; return true; } shouldIncrementWeight = false; if (resolutionContext.ScopeNames.Length == 0) return false; shouldIncrementWeight = resolutionContext.ScopeNames.First().Equals(namedScopeLifetime.ScopeName); return resolutionContext.ScopeNames.Contains(namedScopeLifetime.ScopeName); } } ================================================ FILE: src/Registration/ServiceRegistration.cs ================================================ using Stashbox.Configuration; using Stashbox.Lifetime; using Stashbox.Resolution; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading; namespace Stashbox.Registration; /// /// Represents a service registration. /// [DebuggerDisplay("Name = {Name}, Lifetime = {Lifetime.Name}", Name = "{ImplementationType}")] public class ServiceRegistration { private static int globalRegistrationId = int.MinValue; private static int globalRegistrationOrder = int.MinValue; /// /// The registration id. /// public readonly int RegistrationId; /// /// True if the registration is a decorator. /// public readonly bool IsDecorator; /// /// Name of the registration. /// public object? Name { get; internal set; } /// /// Lifetime of the registration. /// public LifetimeDescriptor Lifetime { get; internal set; } /// /// The registration order indicator. /// public int RegistrationOrder { get; private set; } /// /// The implementation type. /// public Type ImplementationType { get; internal set; } /// /// Advanced registration options. /// public IReadOnlyDictionary? RegistrationOptions => this.Options; [DebuggerBrowsable(DebuggerBrowsableState.Never)] internal Dictionary? Options; internal ServiceRegistration(Type implementationType, object? name, LifetimeDescriptor lifetimeDescriptor, bool isDecorator, Dictionary? options = null, int? registrationId = null, int? order = null) { this.ImplementationType = implementationType; this.IsDecorator = isDecorator; this.Name = name; this.Lifetime = lifetimeDescriptor; this.Options = options; this.RegistrationId = registrationId ?? ReserveRegistrationId(); this.RegistrationOrder = order ?? ReserveRegistrationOrder(); } /// /// Returns the service discriminator used to distinguish service instances. /// If used for service name it returns the requested dependency name's hash. /// Otherwise, the service's registration identifier is used. /// /// The type info of the requested type. /// The container configuration. /// The registration's discriminator. public int GetDiscriminator(TypeInformation typeInformation, ContainerConfiguration containerConfiguration) { if (containerConfiguration.UniversalName != null && containerConfiguration.UniversalName.Equals(this.Name) && typeInformation.DependencyName != null) return typeInformation.DependencyName.GetHashCode() ^ this.RegistrationId; return this.RegistrationId; } internal void Replaces(ServiceRegistration serviceRegistration) => this.RegistrationOrder = serviceRegistration.RegistrationOrder; internal bool IsFactory() => Options.GetOrDefault(RegistrationOption.RegistrationTypeOptions) is FactoryOptions; internal bool IsInstance() => Options.GetOrDefault(RegistrationOption.RegistrationTypeOptions) is InstanceOptions; internal static bool IsUsableForCurrentContext(TypeInformation typeInfo, ConditionOptions conditionOptions) => HasParentTypeConditionAndMatch(typeInfo, conditionOptions) || HasAttributeConditionAndMatch(typeInfo, conditionOptions) || HasResolutionConditionAndMatch(typeInfo, conditionOptions); private static bool HasParentTypeConditionAndMatch(TypeInformation typeInfo, ConditionOptions conditionOptions) => (conditionOptions.TargetTypeConditions != null && CheckTypeConditions(conditionOptions.TargetTypeConditions, typeInfo)) || (conditionOptions.TargetTypeInResolutionPathConditions != null && CheckInPathTypeConditions(conditionOptions.TargetTypeInResolutionPathConditions, typeInfo)); private static bool HasAttributeConditionAndMatch(TypeInformation typeInfo, ConditionOptions conditionOptions) => (conditionOptions.AttributeConditions != null && typeInfo.CustomAttributes != null && conditionOptions.AttributeConditions.Intersect(typeInfo.CustomAttributes.Select(attribute => attribute.GetType())).Any()) || (conditionOptions.AttributeInResolutionPathConditions != null && CheckInPathAttributeConditions(conditionOptions.AttributeInResolutionPathConditions, typeInfo)); private static bool HasResolutionConditionAndMatch(TypeInformation typeInfo, ConditionOptions conditionOptions) { if (conditionOptions.ResolutionConditions == null) return false; var length = conditionOptions.ResolutionConditions.Length; for (var i = 0; i < length; i++) { if (conditionOptions.ResolutionConditions[i](typeInfo)) return true; } return false; } private static bool CheckInPathTypeConditions(ExpandableArray conditions, TypeInformation typeInformation) { var current = typeInformation; do { if (CheckTypeConditions(conditions, current)) return true; current = current.Parent; } while (current != null); return false; } private static bool CheckTypeConditions(ExpandableArray conditions, TypeInformation typeInformation) { if (typeInformation.ParentType == null) return false; var length = conditions.Length; for (var i = 0; i < length; i++) { var item = conditions[i]; if (CheckSingleCondition(item, typeInformation)) return true; } return false; static bool CheckSingleCondition(ReadOnlyKeyValue condition, TypeInformation typeInformation) { if (condition.Key != null) { if (typeInformation.Parent == null) return false; return condition.Key.Equals(typeInformation.Parent.DependencyName) && condition.Value == typeInformation.ParentType; } return condition.Value == typeInformation.ParentType; } } private static bool CheckInPathAttributeConditions(ExpandableArray attributes, TypeInformation typeInformation) { var current = typeInformation; do { if (CheckAttributeConditions(attributes, current)) return true; current = current.Parent; } while (current != null); return false; } private static bool CheckAttributeConditions(ExpandableArray attributes, TypeInformation typeInformation) { if (typeInformation.CustomAttributes == null) return false; var customAttributes = typeInformation.CustomAttributes.Select(attribute => attribute.GetType()).ToList(); if (customAttributes.Count == 0) return false; var length = attributes.Length; for (var i = 0; i < length; i++) { var item = attributes[i]; if (item.Key != null) { if (typeInformation.Parent == null) continue; if(item.Key.Equals(typeInformation.Parent.DependencyName) && customAttributes.Contains(item.Value)) return true; } if (customAttributes.Contains(item.Value)) return true; } return false; } private static int ReserveRegistrationId() => Interlocked.Increment(ref globalRegistrationId); private static int ReserveRegistrationOrder() => Interlocked.Increment(ref globalRegistrationOrder); } /// /// Represents the registration option types. /// public enum RegistrationOption { /// /// Determines whether the service's resolution should be handled by a dynamic call on the current instead of a pre-built instantiation expression. /// IsResolutionCallRequired, /// /// Constructor related registration options. /// ConstructorOptions, /// /// Auto member injection related registration options. /// AutoMemberOptions, /// /// Dependency names or types that are bound to named registrations. /// DependencyBindings, /// /// The cleanup delegate. /// Finalizer, /// /// The initializer delegate. /// Initializer, /// /// The async initializer delegate. /// AsyncInitializer, /// /// True if the lifetime of the service is owned externally. /// IsLifetimeExternallyOwned, /// /// The name of the scope this registration defines. /// DefinedScopeName, /// /// The constructor selection rule. /// ConstructorSelectionRule, /// /// The additional metadata. /// Metadata, /// /// Indicates whether this registration should replace an existing registration. /// ReplaceExistingRegistration, /// /// Indicates whether this registration should replace a registration only when it's exist. /// ReplaceExistingRegistrationOnlyIfExists, /// /// Additional service types to map. /// AdditionalServiceTypes, /// /// Injection parameters. /// InjectionParameters, /// /// Condition related registration options. /// ConditionOptions, /// /// Options related to instance or factory registrations. /// RegistrationTypeOptions, /// /// Required member injection related registration options. /// RequiredMemberInjectionEnabled, } /// /// Represents the factory registration options. /// public class FactoryOptions { /// /// Container factory of the registration. /// public readonly Delegate Factory; /// /// Parameters to inject for the factory registration. /// public readonly Type[] FactoryParameters; /// /// Flag that indicates the passed factory delegate is a compiled lambda from . /// public readonly bool IsFactoryDelegateACompiledLambda; internal FactoryOptions(Delegate factory, Type[] factoryParameters, bool isCompiledLambda) { this.Factory = factory; this.FactoryParameters = factoryParameters; this.IsFactoryDelegateACompiledLambda = isCompiledLambda; } } /// /// Represents the instance registration options. /// public class InstanceOptions { /// /// If true, the existing instance will be wired into the container, it will perform member and method injection on it. /// public readonly bool IsWireUp; /// /// The already stored instance which was provided by instance or wired up registration. /// public readonly object ExistingInstance; internal InstanceOptions(object existingInstance, bool isWireUp) { this.IsWireUp = isWireUp; this.ExistingInstance = existingInstance; } } /// /// Represents the auto member injection related registration options. /// public class AutoMemberOptions { /// /// The auto member injection rule for the registration. /// public readonly Rules.AutoMemberInjectionRules AutoMemberInjectionRule; /// /// A filter delegate used to determine which members should be auto injected and which are not. /// public readonly Func? AutoMemberInjectionFilter; internal AutoMemberOptions(Rules.AutoMemberInjectionRules autoMemberInjectionRule, Func? autoMemberInjectionFilter) { this.AutoMemberInjectionRule = autoMemberInjectionRule; this.AutoMemberInjectionFilter = autoMemberInjectionFilter; } } /// /// Represents the constructor related registration options. /// public class ConstructorOptions { /// /// The selected constructor if any was set. /// public readonly ConstructorInfo SelectedConstructor; /// /// The arguments of the selected constructor if any was set. /// public readonly object[]? ConstructorArguments; internal ConstructorOptions(ConstructorInfo selectedConstructor, object[]? constructorArguments) { this.SelectedConstructor = selectedConstructor; this.ConstructorArguments = constructorArguments; } } internal class ConditionOptions { internal ExpandableArray? TargetTypeConditions { get; set; } internal ExpandableArray? TargetTypeInResolutionPathConditions { get; set; } internal ExpandableArray>? ResolutionConditions { get; set; } internal ExpandableArray? AttributeConditions { get; set; } internal ExpandableArray? AttributeInResolutionPathConditions { get; set; } } ================================================ FILE: src/Registration/ServiceRegistrator.cs ================================================ using Stashbox.Lifetime; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; namespace Stashbox.Registration; internal static class ServiceRegistrator { public static void Register(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType) { if (serviceRegistration.ImplementationType.IsOpenGenericType()) serviceRegistration = new OpenGenericRegistration(serviceRegistration); PreProcessRegistration(containerContext, serviceRegistration); if (serviceRegistration.Options.TryGet(RegistrationOption.AdditionalServiceTypes, out var types) && types is ExpandableArray additionalTypes) foreach (var additionalServiceType in additionalTypes.Distinct()) { if (additionalServiceType.IsOpenGenericType()) { RegisterInternal(containerContext, serviceRegistration, additionalServiceType.GetGenericTypeDefinition()); continue; } RegisterInternal(containerContext, serviceRegistration, additionalServiceType); } RegisterInternal(containerContext, serviceRegistration, serviceType); } public static void ReMap(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType) { if (serviceRegistration.ImplementationType.IsOpenGenericType()) serviceRegistration = new OpenGenericRegistration(serviceRegistration); PreProcessRegistration(containerContext, serviceRegistration); if (serviceRegistration.Options.TryGet(RegistrationOption.AdditionalServiceTypes, out var types) && types is ExpandableArray additionalTypes) foreach (var additionalServiceType in additionalTypes.Distinct()) ReMapInternal(containerContext, serviceRegistration, additionalServiceType); ReMapInternal(containerContext, serviceRegistration, serviceType); } private static void RegisterInternal(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType) { if (serviceRegistration.IsDecorator) { containerContext.DecoratorRepository.AddDecorator(serviceType, serviceRegistration, false); containerContext.RootScope.InvalidateDelegateCache(); } else if (containerContext.RegistrationRepository.AddOrUpdateRegistration(serviceRegistration, serviceType)) containerContext.RootScope.InvalidateDelegateCache(); } private static void ReMapInternal(IContainerContext containerContext, ServiceRegistration serviceRegistration, Type serviceType) { if (serviceRegistration.IsDecorator) containerContext.DecoratorRepository.AddDecorator(serviceType, serviceRegistration, true); else containerContext.RegistrationRepository.AddOrReMapRegistration(serviceRegistration, serviceType); containerContext.RootScope.InvalidateDelegateCache(); } private static void PreProcessRegistration(IContainerContext containerContext, ServiceRegistration serviceRegistration) { if (!serviceRegistration.Options.TryGet(RegistrationOption.RegistrationTypeOptions, out var opts) || opts is not InstanceOptions instanceOptions) return; PreProcessExistingInstanceIfNeeded(containerContext, instanceOptions.ExistingInstance, serviceRegistration.Options.IsOn(RegistrationOption.IsLifetimeExternallyOwned), serviceRegistration.Options.GetOrDefault>(RegistrationOption.Finalizer), serviceRegistration.ImplementationType); if (instanceOptions.IsWireUp) serviceRegistration.Lifetime = Lifetimes.Singleton; } private static void PreProcessExistingInstanceIfNeeded(IContainerContext containerContext, object? instance, bool isLifetimeExternallyOwned, Action? finalizer, Type implementationType) { if (instance == null) return; if (!isLifetimeExternallyOwned && implementationType.IsDisposable()) containerContext.RootScope.AddDisposableTracking(instance); if (finalizer == null) return; containerContext.RootScope.AddWithFinalizer(instance, finalizer); } } ================================================ FILE: src/Resolution/DelegateCache.cs ================================================ using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; namespace Stashbox.Resolution; internal class DelegateCache { public ImmutableTree> ServiceDelegates = ImmutableTree>.Empty; public ImmutableTree> RequestContextAwareDelegates = ImmutableTree>.Empty; } internal class CacheEntry { public readonly Func? ServiceFactory; public readonly ImmutableTree>? NamedFactories; public CacheEntry(Func? serviceFactory, ImmutableTree>? namedFactories) { this.ServiceFactory = serviceFactory; this.NamedFactories = namedFactories; } } internal class DelegateCacheProvider { public readonly DelegateCache DefaultCache = new(); public ImmutableTree NamedCache = ImmutableTree.Empty; public DelegateCache GetNamedCache(object name) { var cache = this.NamedCache.GetOrDefaultByValue(name); if (cache != null) return cache; var newCache = new DelegateCache(); return Swap.SwapValue(ref this.NamedCache, (t1, t2, _, _, items) => items.AddOrUpdate(t1, t2, false), name, newCache, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder) ? newCache : this.NamedCache.GetOrDefaultByValue(name) ?? newCache; } } ================================================ FILE: src/Resolution/DelegateCacheEntry.cs ================================================ using System; using System.Collections.Generic; namespace Stashbox.Resolution; /// /// Details about Stashbox's internal delegate cache state. /// public readonly struct DelegateCacheEntry { /// /// The service type. /// public readonly Type ServiceType; /// /// The resolution behavior that was used to construct this cache entry. /// public readonly ResolutionBehavior ResolutionBehavior; /// /// The cached resolution delegate. /// public readonly Func? CachedDelegate; /// /// Named resolution delegates cached for this service. /// public readonly IEnumerable? NamedCacheEntries; /// /// Constructs a . /// /// The service type. /// The cached resolution delegate. /// Named resolution delegates cached for this service. /// The resolution behavior. public DelegateCacheEntry(Type serviceType, Func? cachedDelegate, IEnumerable? namedCacheEntries, ResolutionBehavior resolutionBehavior) : this() { this.ServiceType = serviceType; this.CachedDelegate = cachedDelegate; this.NamedCacheEntries = namedCacheEntries; this.ResolutionBehavior = resolutionBehavior; } } /// /// Details about a named delegate cache entry. /// public readonly struct NamedCacheEntry { /// /// The service name. /// public readonly object Name; /// /// The cached resolution delegate. /// public readonly Func CachedDelegate; /// /// Constructs a . /// /// The service name. /// The cached resolution delegate. public NamedCacheEntry(object name, Func cachedDelegate) : this() { this.Name = name; this.CachedDelegate = cachedDelegate; } } ================================================ FILE: src/Resolution/Extensions/DependencyResolverExtensions.cs ================================================ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using Stashbox.Resolution; using Stashbox.Utils; namespace Stashbox; /// /// Represents the extensions of the . /// public static class DependencyResolverExtensions { /// /// Resolves an instance from the container. /// /// The type of the requested instance. /// The dependency resolver. /// The resolved object. public static TKey Resolve(this IDependencyResolver resolver) => (TKey)resolver.Resolve(TypeCache.Type); /// /// Resolves an instance from the container. /// /// The type of the requested instance. /// The dependency resolver. /// The resolution behavior. /// The resolved object. public static TKey Resolve(this IDependencyResolver resolver, ResolutionBehavior resolutionBehavior) => (TKey)resolver.Resolve(TypeCache.Type, null, null, resolutionBehavior); /// /// Resolves an instance from the container with dependency overrides. /// /// The type of the requested instance. /// The dependency resolver. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static TKey Resolve(this IDependencyResolver resolver, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey)resolver.Resolve(TypeCache.Type, null, dependencyOverrides, resolutionBehavior); /// /// Resolves a named instance from the container. /// /// The type of the requested instance. /// The dependency resolver. /// The name of the requested registration. /// The resolution behavior. /// The resolved object. public static TKey Resolve(this IDependencyResolver resolver, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey)resolver.Resolve(TypeCache.Type, name, null, resolutionBehavior); /// /// Resolves a named instance from the container with dependency overrides. /// /// The type of the requested instance. /// The dependency resolver. /// The name of the requested registration. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static TKey Resolve(this IDependencyResolver resolver, object? name, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey)resolver.Resolve(TypeCache.Type, name, dependencyOverrides, resolutionBehavior); /// /// Resolves an instance from the container. /// /// The dependency resolver. /// The type of the requested service. /// The resolution behavior. /// The resolved object. public static object Resolve(this IDependencyResolver resolver, Type typeFrom, ResolutionBehavior resolutionBehavior) => resolver.Resolve(typeFrom, null, null, resolutionBehavior); /// /// Resolves an instance from the container with dependency overrides. /// /// The dependency resolver. /// The type of the requested service. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static object Resolve(this IDependencyResolver resolver, Type typeFrom, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => resolver.Resolve(typeFrom, null, dependencyOverrides, resolutionBehavior); /// /// Resolves a named instance from the container. /// /// The dependency resolver. /// The type of the requested service. /// The name of the requested service. /// The resolution behavior. /// The resolved object. public static object Resolve(this IDependencyResolver resolver, Type typeFrom, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => resolver.Resolve(typeFrom, name, null, resolutionBehavior); /// /// Resolves a named instance from the container or returns default if the type is not resolvable. /// /// The type of the requested instance. /// The dependency resolver. /// The name of the requested registration. /// The resolution behavior. /// The resolved object. public static TKey? ResolveOrDefault(this IDependencyResolver resolver, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey?)(resolver.ResolveOrDefault(TypeCache.Type, name, null, resolutionBehavior) ?? default(TKey)); /// /// Resolves a named instance from the container with dependency overrides or returns default if the type is not resolvable. /// /// The type of the requested instance. /// The dependency resolver. /// The name of the requested registration. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static TKey? ResolveOrDefault(this IDependencyResolver resolver, object? name, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey?)(resolver.ResolveOrDefault(TypeCache.Type, name, dependencyOverrides, resolutionBehavior) ?? default(TKey)); /// /// Resolves an instance from the container or returns default if the type is not resolvable. /// /// The type of the requested instance. /// The dependency resolver. /// The resolved object. public static TKey? ResolveOrDefault(this IDependencyResolver resolver) => (TKey?)(resolver.ResolveOrDefault(TypeCache.Type) ?? default(TKey)); /// /// Resolves an instance from the container or returns default if the type is not resolvable. /// /// The type of the requested instance. /// The dependency resolver. /// The resolution behavior. /// The resolved object. public static TKey? ResolveOrDefault(this IDependencyResolver resolver, ResolutionBehavior resolutionBehavior) => (TKey?)(resolver.ResolveOrDefault(TypeCache.Type, null, null, resolutionBehavior) ?? default(TKey)); /// /// Resolves an instance from the container with dependency overrides or returns default if the type is not resolvable. /// /// The type of the requested instance. /// The dependency resolver. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static TKey? ResolveOrDefault(this IDependencyResolver resolver, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (TKey?)(resolver.ResolveOrDefault(TypeCache.Type, null, dependencyOverrides, resolutionBehavior) ?? default(TKey)); /// /// Resolves an instance from the container or returns default if the type is not resolvable. /// /// The dependency resolver. /// The type of the requested service. /// The resolution behavior. /// The resolved object. public static object? ResolveOrDefault(this IDependencyResolver resolver, Type typeFrom, ResolutionBehavior resolutionBehavior) => resolver.ResolveOrDefault(typeFrom, null, null, resolutionBehavior); /// /// Resolves an instance from the container with dependency overrides or returns default if the type is not resolvable. /// /// The dependency resolver. /// The type of the requested service. /// A collection of objects which are used to override certain dependencies of the requested service. /// The resolution behavior. /// The resolved object. public static object? ResolveOrDefault(this IDependencyResolver resolver, Type typeFrom, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => resolver.ResolveOrDefault(typeFrom, null, dependencyOverrides, resolutionBehavior); /// /// Resolves a named instance from the container or returns default if the type is not resolvable. /// /// The dependency resolver. /// The type of the requested service. /// The name of the requested service. /// The resolution behavior. /// The resolved object. public static object? ResolveOrDefault(this IDependencyResolver resolver, Type typeFrom, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => resolver.ResolveOrDefault(typeFrom, name, null, resolutionBehavior); /// /// Resolves all registered implementations of a service. /// /// The type of the requested service. /// The dependency resolver. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver) => (IEnumerable)resolver.Resolve(TypeCache>.Type); /// /// Resolves all registered implementations of a service. /// /// The type of the requested service. /// The dependency resolver. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, ResolutionBehavior resolutionBehavior) => (IEnumerable)resolver.Resolve(TypeCache>.Type, null, null, resolutionBehavior); /// /// Resolves all registered implementations of a service identified by a name. /// /// The type of the requested service. /// The dependency resolver. /// The name of the requested service. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (IEnumerable)resolver.Resolve(TypeCache>.Type, name, null, resolutionBehavior); /// /// Resolves all registered implementations of a service with dependency overrides. /// /// The type of the requested service. /// The dependency resolver. /// A collection of objects which are used to override certain dependencies of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (IEnumerable)resolver.Resolve(TypeCache>.Type, null, dependencyOverrides, resolutionBehavior); /// /// Resolves all registered implementations of a service identified by a name and with dependency overrides. /// /// The type of the requested services. /// The dependency resolver. /// The name of the requested services. /// A collection of objects which are used to override certain dependencies of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, object? name, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => (IEnumerable)resolver.Resolve(TypeCache>.Type, name, dependencyOverrides, resolutionBehavior); /// /// Resolves all registered implementations of a service. /// /// The dependency resolver. /// The type of the requested services. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, Type typeFrom) { var type = TypeCache.EnumerableType.MakeGenericType(typeFrom); return (IEnumerable)resolver.Resolve(type); } /// /// Resolves all registered implementations of a service. /// /// The dependency resolver. /// The type of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, Type typeFrom, ResolutionBehavior resolutionBehavior) { var type = TypeCache.EnumerableType.MakeGenericType(typeFrom); return (IEnumerable)resolver.Resolve(type, null, null, resolutionBehavior); } /// /// Resolves all registered implementations of a service. /// /// The dependency resolver. /// The type of the requested services. /// The name of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, Type typeFrom, object? name, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { var type = TypeCache.EnumerableType.MakeGenericType(typeFrom); return (IEnumerable)resolver.Resolve(type, name, null, resolutionBehavior); } /// /// Resolves all registered implementations of a service with dependency overrides. /// /// The dependency resolver. /// The type of the requested services. /// A collection of objects which are used to override certain dependencies of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, Type typeFrom, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { var type = TypeCache.EnumerableType.MakeGenericType(typeFrom); return (IEnumerable)resolver.Resolve(type, null, dependencyOverrides, resolutionBehavior); } /// /// Resolves all registered implementations of a service with dependency overrides. /// /// The dependency resolver. /// The type of the requested services. /// The name of the requested services. /// A collection of objects which are used to override certain dependencies of the requested services. /// The resolution behavior. /// The resolved object. public static IEnumerable ResolveAll(this IDependencyResolver resolver, Type typeFrom, object? name, object[] dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { var type = TypeCache.EnumerableType.MakeGenericType(typeFrom); return (IEnumerable)resolver.Resolve(type, name, dependencyOverrides, resolutionBehavior); } /// /// On the fly activates an object without registering it into the container. If you want to resolve a /// registered service use the instead. /// /// The service type. /// The dependency resolver. /// Optional dependency overrides. /// The built object. public static TTo Activate(this IDependencyResolver resolver, params object[] arguments) => (TTo)resolver.Activate(TypeCache.Type, arguments); /// /// Activates an object without registering it into the container. If you want to resolve a /// registered service use the method instead. /// /// The dependency resolver. /// The type to activate. /// Optional dependency overrides. /// The built object. public static object Activate(this IDependencyResolver resolver, Type type, params object[] arguments) => resolver.Activate(type, Constants.DefaultResolutionBehavior, arguments); /// /// On the fly activates an object without registering it into the container. If you want to resolve a /// registered service use the instead. /// /// The service type. /// The dependency resolver. /// Optional dependency overrides. /// The resolution behavior. /// The built object. public static TTo Activate(this IDependencyResolver resolver, ResolutionBehavior resolutionBehavior, params object[] arguments) => (TTo)resolver.Activate(TypeCache.Type, resolutionBehavior, arguments); /// /// Puts an instance into the scope which will be dropped when the scope is being disposed. /// /// The service type. /// The resolver. /// The instance. /// If it's set to true the container will exclude the instance from the disposal tracking. /// The identifier. /// The scope. public static void PutInstanceInScope(this IDependencyResolver resolver, TFrom instance, bool withoutDisposalTracking = false, object? name = null) where TFrom : class => resolver.PutInstanceInScope(TypeCache.Type, instance, withoutDisposalTracking, name); /// /// Checks whether a type can be resolved by the container, or not. /// /// The service type. /// The resolver. /// The registration name. /// The resolution behavior. /// True if the service can be resolved, otherwise false. public static bool CanResolve(this IDependencyResolver resolver, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => resolver.CanResolve(TypeCache.Type, name, resolutionBehavior); } ================================================ FILE: src/Resolution/Extensions/InjectionParameterExtensions.cs ================================================ using Stashbox.Utils; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Stashbox.Resolution.Extensions; internal static class InjectionParameterExtensions { public static Expression? SelectInjectionParameterOrDefault(this IEnumerable> injectionParameters, TypeInformation typeInformation) { var memberName = typeInformation.ParameterOrMemberName; var matchingParam = injectionParameters.FirstOrDefault(param => param.Key == memberName); if (matchingParam.Equals(default(KeyValuePair))) return null; if (matchingParam.Value == null) return typeInformation.Type == TypeCache.Type ? matchingParam.Value.AsConstant() : matchingParam.Value.AsConstant().ConvertTo(typeInformation.Type); return matchingParam.Value.GetType() == typeInformation.Type ? matchingParam.Value.AsConstant() : matchingParam.Value.AsConstant().ConvertTo(typeInformation.Type); } } ================================================ FILE: src/Resolution/Extensions/ResolutionBehaviorExtensions.cs ================================================ namespace Stashbox.Resolution; internal static class ResolutionBehaviorExtensions { public static bool Has(this ResolutionBehavior resolutionBehavior, ResolutionBehavior toCheck) => (resolutionBehavior & toCheck) == toCheck; } ================================================ FILE: src/Resolution/ILookupResolver.cs ================================================ namespace Stashbox.Resolution; internal interface ILookup { bool CanLookupService(TypeInformation typeInfo, ResolutionContext resolutionContext); } ================================================ FILE: src/Resolution/IRequestContext.cs ================================================ using System; namespace Stashbox.Resolution; /// /// Represents an information storage for resolution requests. /// public interface IRequestContext { /// /// Returns a dependency override for a given type. /// /// The type of the dependency override. /// The object used to override a dependency. object? GetDependencyOverrideOrDefault(Type dependencyType); /// /// Returns a dependency override for a given type. /// /// The type of the dependency override. /// The object used to override a dependency. TResult? GetDependencyOverrideOrDefault(); /// /// Returns each dependency override passed to the resolution request. /// object[] GetOverrides(); /// /// Marks an instance as non-disposable, so the container will exclude it from disposal tracking. /// /// The instance to mark. /// The instance. TInstance ExcludeFromTracking(TInstance value) where TInstance : class; } internal interface IInternalRequestContext : IRequestContext { object GetOrAddInstance(int key, Func factory, IResolutionScope scope); bool IsInstanceExcludedFromTracking(object instance); } ================================================ FILE: src/Resolution/IResolutionStrategy.cs ================================================ using System; using Stashbox.Registration; using System.Collections.Generic; namespace Stashbox.Resolution; /// /// Represents a resolution strategy. /// public interface IResolutionStrategy { /// /// Registers an . /// /// The resolver implementation. void RegisterResolver(IResolver resolver); /// /// Builds the resolution expression for the requested service. /// /// The resolution context. /// The type info of the requested service. /// The built expression tree. ServiceContext BuildExpressionForType(ResolutionContext resolutionContext, TypeInformation typeInformation); /// /// Builds all the resolution expressions for the enumerable service request. /// /// The resolution context. /// The type information of the enumerable item type. /// The built expression tree. IEnumerable BuildExpressionsForEnumerableRequest(ResolutionContext resolutionContext, TypeInformation typeInformation); /// /// Builds the resolution expression for the requested service registration. /// /// The service registration. /// The resolution context. /// The type info of the requested service. /// The built expression tree. ServiceContext BuildExpressionForRegistration(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation); /// /// Determines whether a type is resolvable with the current container state or not. /// /// The resolution context. /// The type info of the requested service. /// True if a type is resolvable, otherwise false. bool IsTypeResolvable(ResolutionContext resolutionContext, TypeInformation typeInformation); } ================================================ FILE: src/Resolution/IResolver.cs ================================================ using System; using System.Collections.Generic; namespace Stashbox.Resolution; /// /// The base interface for wrappers and resolvers. /// public interface IResolver; /// /// Represents a dependency resolver. /// public interface IServiceResolver : IResolver { /// /// Produces an expression for creating an instance. /// /// The resolution strategy used to build the underlying resolution expression tree. /// The information about the type to resolve. /// The contextual information about the current resolution call. /// The built resolution expression. ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext); /// /// Returns true, if the resolver can be used to activate the requested service, otherwise false. /// /// The information about the type to resolve. /// The contextual information about the current resolution call. /// Returns true, if the resolver can be used to activate the requested service, otherwise false. bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext); } /// /// Represents a dependency resolver that can produce a collection of services. /// public interface IEnumerableSupportedResolver : IServiceResolver { /// /// Produces an array of expressions, one for every registered service identified by the requested type. /// /// The resolution strategy used to build the underlying resolution expression tree. /// The information about the enumerable item type to resolve. /// The contextual information about the current resolution call. /// The array of all the resolution expression built by the resolver. IEnumerable GetExpressionsForEnumerableRequest( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext); } ================================================ FILE: src/Resolution/IWrapper.cs ================================================ using System; using System.Collections.Generic; using System.Linq.Expressions; namespace Stashbox.Resolution; /// /// Represents a wrapper that can wrap a service. /// public interface IServiceWrapper : IResolver { /// /// Wraps the expression that describes the service. /// /// The requested type's meta information. /// The wrapped type's meta information. /// The wrapped service's context that contains the actual instantiation expression and additional meta information. /// The wrapped service expression. Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext); /// /// Un-wraps the underlying service type from a wrapped type request. /// /// The requested type to unwrap. /// The un-wrapped service type. /// True if the un-wrapping was successful, otherwise false. bool TryUnWrap(Type type, out Type unWrappedType); } /// /// Represents a wrapper that can wrap a collection of a service. /// public interface IEnumerableWrapper : IResolver { /// /// Wraps the expression that describes the service. /// /// The requested type's meta information. /// The wrapped type's meta information. /// The service contexts that contains the actual instantiation expressions and additional meta information. /// The wrapped service expression. Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, IEnumerable serviceContexts); /// /// Un-wraps the underlying service type from a wrapped type request. /// /// The requested type to unwrap. /// The un-wrapped service type. /// True if the un-wrapping was successful, otherwise false. bool TryUnWrap(Type type, out Type unWrappedType); } /// /// Represents a wrapper that can wrap a service with function parameters. /// public interface IParameterizedWrapper : IResolver { /// /// Wraps the expression that describes the service. /// /// The requested type's meta information. /// The wrapped type's meta information. /// The wrapped service's context that contains the actual instantiation expression and additional meta information. /// The wrapper's parameter expressions. /// The wrapped service expression. Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext, IEnumerable parameterExpressions); /// /// Un-wraps the underlying service type from a wrapped type request. /// /// The requested type to unwrap. /// The un-wrapped service type. /// The wrapper's parameter types. /// True if the un-wrapping was successful, otherwise false. bool TryUnWrap(Type type, out Type unWrappedType, out IEnumerable parameterTypes); } /// /// Represents a wrapper that can wrap a service with metadata. /// public interface IMetadataWrapper : IResolver { /// /// Wraps the expression that describes the service. /// /// The requested type's meta information. /// The wrapped type's meta information. /// The wrapped service's context that contains the actual instantiation expression and additional meta information. /// The wrapped service expression. Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext); /// /// Un-wraps the underlying service type from a wrapped type request. /// /// The requested type to unwrap. /// The un-wrapped service type. /// The wrapper's metadata types. /// True if the un-wrapping was successful, otherwise false. bool TryUnWrap(Type type, out Type unWrappedType, out Type metadataType); } ================================================ FILE: src/Resolution/RequestContext.cs ================================================ using Stashbox.Utils; using Stashbox.Utils.Data; using System; using System.Linq; namespace Stashbox.Resolution; internal class RequestContext : IInternalRequestContext { public static readonly RequestContext Empty = new(); public static RequestContext FromOverrides(object[]? overrides) => new(overrides); public static RequestContext Begin() => new(); private readonly Tree excludedInstances = new(); private readonly Tree perRequestInstances = new(); private readonly object[]? overrides; private RequestContext(object[]? overrides = null) { this.overrides = overrides; } public object GetOrAddInstance(int key, Func factory, IResolutionScope scope) { var instance = this.perRequestInstances.GetOrDefault(key); if (instance != null) return instance; instance = factory(scope, this); this.perRequestInstances.Add(key, instance); return instance; } public object? GetDependencyOverrideOrDefault(Type dependencyType) => this.overrides == null ? null : Array.Find(this.overrides, dependencyType.IsInstanceOfType); public TResult? GetDependencyOverrideOrDefault() => (TResult?)this.GetDependencyOverrideOrDefault(TypeCache.Type); public object[] GetOverrides() => this.overrides ?? TypeCache.EmptyArray(); public bool IsInstanceExcludedFromTracking(object instance) { var excluded = this.excludedInstances.GetOrDefault(instance.GetHashCode()); return excluded != null && ReferenceEquals(excluded, instance); } public TInstance ExcludeFromTracking(TInstance value) where TInstance : class { this.excludedInstances.Add(value.GetHashCode(), value); return value; } } ================================================ FILE: src/Resolution/ResolutionBehavior.cs ================================================ using System; namespace Stashbox.Resolution; /// /// Service resolution behavior. /// [Flags] public enum ResolutionBehavior { /// /// Indicates that both the current container (which initiated the resolution request) and its parents can participate in the resolution request's service selection. /// Default = Current | Parent, /// /// Indicates that parent containers (including all indirect ancestors) can participate in the resolution request's service selection. /// Parent = 1 << 0, /// /// Indicates that the current container (which initiated the resolution request) can participate in the service selection. /// Current = 1 << 1, /// /// Indicates that parent containers (including all indirect ancestors) can only provide dependencies for services that are already selected for resolution. /// ParentDependency = 1 << 2, /// /// Upon enumerable resolution, when both and behaviors are enabled, and the current container has the appropriate services, the resolution will prefer those and ignore the parent containers. When the current container doesn't have the requested services, the parent containers will serve the request. /// PreferEnumerableInCurrent = 1 << 3, } ================================================ FILE: src/Resolution/ResolutionContext.cs ================================================ using Stashbox.Registration; using Stashbox.Utils; using Stashbox.Utils.Data; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Runtime.CompilerServices; using Stashbox.Exceptions; using Stashbox.Lifetime; namespace Stashbox.Resolution; /// /// Represents information about the actual resolution flow. /// public class ResolutionContext { internal class PerRequestConfiguration { public bool RequiresRequestContext; public bool FactoryDelegateCacheEnabled; } internal class AutoLifetimeTracker { public LifetimeDescriptor HighestRankingLifetime = Lifetimes.Transient; } private readonly bool shouldFallBackToRequestInitiatorContext; internal readonly Tree ExpressionCache; internal readonly Utils.Data.Stack ScopeNames; internal readonly PerRequestConfiguration RequestConfiguration; internal readonly Utils.Data.Stack CircularDependencyBarrier; internal readonly Tree> FactoryCache; internal readonly HashTree>? ExpressionOverrides; internal readonly ExpandableArray SingleInstructions; internal readonly Tree DefinedVariables; internal readonly ExpandableArray[]> ParameterExpressions; internal readonly ExpandableArray> RemainingDecorators; internal readonly ExpandableArray CurrentDecorators; internal readonly IContainerContext RequestInitiatorContainerContext; internal readonly ResolutionBehavior RequestInitiatorResolutionBehavior; internal readonly int CurrentLifeSpan; internal readonly string? NameOfServiceLifeSpanValidatingAgainst; internal readonly bool PerResolutionRequestCacheEnabled; internal readonly bool UnknownTypeCheckDisabled; internal readonly RequestContext RequestContext; internal readonly bool IsValidationContext; internal readonly AutoLifetimeTracker? AutoLifetimeTracking; /// /// True if the resolution is currently in the parent context. /// public readonly bool IsInParentContext; /// /// True if a null result is allowed, otherwise false. /// public readonly bool NullResultAllowed; /// /// When it's true, it indicates that the current resolution request was made from the root scope. /// public readonly bool IsRequestedFromRoot; /// /// The currently resolving scope. /// public readonly ParameterExpression RequestContextParameter; /// /// The currently resolving scope. /// public readonly ParameterExpression CurrentScopeParameter; /// /// The context of the current container instance. /// public readonly IContainerContext CurrentContainerContext; /// /// The resolution behavior. /// public readonly ResolutionBehavior ResolutionBehavior; private ResolutionContext(IEnumerable initialScopeNames, IContainerContext currentContainerContext, ResolutionBehavior resolutionBehavior, bool isRequestedFromRoot, bool nullResultAllowed, bool isValidationContext, object[]? dependencyOverrides, ImmutableTree>? knownInstances, ParameterExpression[]? initialParameters) { this.RequestConfiguration = new PerRequestConfiguration(); this.DefinedVariables = new Tree(); this.SingleInstructions = []; this.RemainingDecorators = []; this.CurrentDecorators = []; this.CircularDependencyBarrier = []; this.ExpressionCache = new Tree(); this.FactoryCache = new Tree>(); this.ResolutionBehavior = this.RequestInitiatorResolutionBehavior = resolutionBehavior; this.NullResultAllowed = nullResultAllowed; this.IsRequestedFromRoot = isRequestedFromRoot; this.ScopeNames = initialScopeNames.AsStack(); this.CurrentScopeParameter = Constants.ResolutionScopeParameter; this.RequestContextParameter = Constants.RequestContextParameter; this.CurrentContainerContext = this.RequestInitiatorContainerContext = currentContainerContext; this.RequestConfiguration.FactoryDelegateCacheEnabled = this.PerResolutionRequestCacheEnabled = dependencyOverrides == null; this.RequestContext = dependencyOverrides != null ? RequestContext.FromOverrides(dependencyOverrides) : RequestContext.Begin(); this.IsValidationContext = isValidationContext; this.AutoLifetimeTracking = null; this.ExpressionOverrides = dependencyOverrides == null && (knownInstances == null || knownInstances.IsEmpty) ? null : ProcessDependencyOverrides(dependencyOverrides, knownInstances); this.ParameterExpressions = initialParameters != null ? [initialParameters.AsParameterPairs()] : []; } private ResolutionContext(PerRequestConfiguration perRequestConfiguration, Tree definedVariables, ExpandableArray singleInstructions, ExpandableArray> remainingDecorators, ExpandableArray currentDecorators, Utils.Data.Stack circularDependencyBarrier, Tree cachedExpressions, Tree> factoryCache, Utils.Data.Stack scopeNames, ParameterExpression currentScopeParameter, ParameterExpression requestContextParameter, IContainerContext currentContainerContext, IContainerContext requestInitiatorContainerContext, HashTree>? expressionOverrides, ExpandableArray[]> parameterExpressions, RequestContext requestContext, AutoLifetimeTracker? autoLifetimeTracker, ResolutionBehavior resolutionBehavior, ResolutionBehavior requestInitiatorResolutionBehavior, bool nullResultAllowed, bool isRequestedFromRoot, string? nameOfServiceLifeSpanValidatingAgainst, int currentLifeSpan, bool perResolutionRequestCacheEnabled, bool unknownTypeCheckDisabled, bool shouldFallBackToRequestInitiatorContext, bool isValidationContext) { this.RequestConfiguration = perRequestConfiguration; this.DefinedVariables = definedVariables; this.SingleInstructions = singleInstructions; this.RemainingDecorators = remainingDecorators; this.CurrentDecorators = currentDecorators; this.CircularDependencyBarrier = circularDependencyBarrier; this.ExpressionCache = cachedExpressions; this.FactoryCache = factoryCache; this.NullResultAllowed = nullResultAllowed; this.IsRequestedFromRoot = isRequestedFromRoot; this.ScopeNames = scopeNames; this.CurrentScopeParameter = currentScopeParameter; this.RequestContextParameter = requestContextParameter; this.CurrentContainerContext = currentContainerContext; this.RequestInitiatorContainerContext = requestInitiatorContainerContext; this.ExpressionOverrides = expressionOverrides; this.ParameterExpressions = parameterExpressions; this.NameOfServiceLifeSpanValidatingAgainst = nameOfServiceLifeSpanValidatingAgainst; this.CurrentLifeSpan = currentLifeSpan; this.PerResolutionRequestCacheEnabled = perResolutionRequestCacheEnabled; this.UnknownTypeCheckDisabled = unknownTypeCheckDisabled; this.shouldFallBackToRequestInitiatorContext = shouldFallBackToRequestInitiatorContext; this.RequestContext = requestContext; this.IsValidationContext = isValidationContext; this.ResolutionBehavior = resolutionBehavior; this.RequestInitiatorResolutionBehavior = requestInitiatorResolutionBehavior; this.AutoLifetimeTracking = autoLifetimeTracker; this.IsInParentContext = requestInitiatorContainerContext != currentContainerContext; } /// /// Adds a custom expression to the instruction list /// /// The custom expression. public void AddInstruction(Expression instruction) => this.SingleInstructions.Add(instruction); /// /// Adds a global keyed variable to the compiled expression tree. /// /// The key of the variable. /// The variable. public void AddDefinedVariable(int key, ParameterExpression parameter) => this.DefinedVariables.Add(key, parameter); /// /// Adds a global variable to the compiled expression tree. /// /// The variable. public void AddDefinedVariable(ParameterExpression parameter) => this.DefinedVariables.Add(RuntimeHelpers.GetHashCode(parameter), parameter); internal void CacheExpression(int key, Expression expression) => this.ExpressionCache.Add(key, expression); internal static ResolutionContext BeginTopLevelContext( IEnumerable initialScopeNames, IContainerContext currentContainerContext, ResolutionBehavior resolutionBehavior, bool isRequestedFromRoot, bool nullResultAllowed, object[]? dependencyOverrides = null, ImmutableTree>? knownInstances = null, ParameterExpression[]? initialParameters = null) => new(initialScopeNames, currentContainerContext, resolutionBehavior, isRequestedFromRoot, nullResultAllowed, false, dependencyOverrides, knownInstances, initialParameters); internal static ResolutionContext BeginValidationContext(IContainerContext currentContainerContext, ResolutionBehavior resolutionBehavior) => new(TypeCache.EmptyArray(), currentContainerContext, resolutionBehavior, false, false, true, null, null, null); internal ResolutionContext BeginParentContainerContext(IContainerContext currentContainerContext) => this.Clone(currentContainerContext: currentContainerContext, shouldFallBackToRequestInitiator: this.RequestInitiatorContainerContext != currentContainerContext, resolutionBehavior: this.ResolutionBehavior | ResolutionBehavior.Current); internal ResolutionContext FallBackToRequestInitiatorIfNeeded() => this.shouldFallBackToRequestInitiatorContext ? this.Clone(currentContainerContext: this.RequestInitiatorContainerContext, shouldFallBackToRequestInitiator: false, resolutionBehavior: this.RequestInitiatorResolutionBehavior) : this; internal ResolutionContext BeginNewScopeContext(ReadOnlyKeyValue scopeParameter) { this.ScopeNames.PushBack(scopeParameter.Key); return this.Clone(definedVariables: new Tree(), singleInstructions: [], cachedExpressions: new Tree(), scopeNames: this.ScopeNames, currentScopeParameter: scopeParameter.Value); } internal ResolutionContext BeginSubGraph() => this.Clone(definedVariables: new Tree(), singleInstructions: [], cachedExpressions: new Tree()); internal ResolutionContext BeginUnknownTypeCheckDisabledContext() => this.UnknownTypeCheckDisabled ? this : this.Clone(unknownTypeCheckDisabled: true); internal ResolutionContext BeginContextWithFunctionParameters(ParameterExpression[] parameterExpressions) => this.Clone(parameterExpressions: new ExpandableArray[]>(this.ParameterExpressions) {parameterExpressions.AsParameterPairs()}, perResolutionRequestCacheEnabled: false); internal ResolutionContext BeginDecoratingContext(Type decoratingType, IEnumerable serviceRegistrations) { var newStack = new Utils.Data.Stack(serviceRegistrations); var current = newStack.Pop(); var decorators = new ExpandableArray>(this.RemainingDecorators); decorators.AddOrUpdate(decoratingType, newStack); return this.Clone(currentDecorators: new ExpandableArray(this.CurrentDecorators) { current }, remainingDecorators: decorators); } internal ResolutionContext BeginLifetimeValidationContext(int lifeSpan, string currentlyLifeSpanValidatingService) => this.Clone(currentLifeSpan: lifeSpan, nameOfServiceLifeSpanValidatingAgainst: currentlyLifeSpanValidatingService); internal ResolutionContext BeginAutoLifetimeTrackingContext(AutoLifetimeTracker autoLifetimeTracker) => this.Clone(autoLifetimeTracker: autoLifetimeTracker, definedVariables: new Tree(), singleInstructions: [], cachedExpressions: new Tree()); private static HashTree> ProcessDependencyOverrides(object[]? dependencyOverrides, ImmutableTree>? knownInstances) { var overrides = new HashTree>(); if (knownInstances is { IsEmpty: false }) foreach (var lateKnownInstance in knownInstances.Walk()) overrides.Add(lateKnownInstance.Key, new ExpandableArray(lateKnownInstance.Value.Repository)); if (dependencyOverrides == null) return overrides; foreach (var dependencyOverride in dependencyOverrides) { if (dependencyOverride is Override @override) { var arr = overrides.GetOrDefault(@override.Type); if (arr != null) arr.Add(@override); else overrides.Add(@override.Type, [@override]); continue; } var type = dependencyOverride.GetType(); Type[] allTypes = [type, .. type.GetRegisterableInterfaceTypes().Concat(type.GetRegisterableBaseTypes())]; foreach (var depType in allTypes) { var expOverride = Override.Of(depType, instance: dependencyOverride); var depOverride = overrides.GetOrDefault(depType); if (depOverride != null) depOverride.Add(expOverride); else overrides.Add(depType, new ExpandableArray(new [] {expOverride})); } } return overrides; } private ResolutionContext Clone( Tree? definedVariables = null, ExpandableArray? singleInstructions = null, ExpandableArray>? remainingDecorators = null, ExpandableArray? currentDecorators = null, Tree? cachedExpressions = null, Utils.Data.Stack? scopeNames = null, ParameterExpression? currentScopeParameter = null, IContainerContext? currentContainerContext = null, ExpandableArray[]>? parameterExpressions = null, ResolutionBehavior? resolutionBehavior = null, AutoLifetimeTracker? autoLifetimeTracker = null, string? nameOfServiceLifeSpanValidatingAgainst = null, int? currentLifeSpan = null, bool? perResolutionRequestCacheEnabled = null, bool? unknownTypeCheckDisabled = null, bool? shouldFallBackToRequestInitiator = null, bool? nullResultAllowed = null) => new(this.RequestConfiguration, definedVariables ?? this.DefinedVariables, singleInstructions ?? this.SingleInstructions, remainingDecorators ?? this.RemainingDecorators, currentDecorators ?? this.CurrentDecorators, this.CircularDependencyBarrier, cachedExpressions ?? this.ExpressionCache, this.FactoryCache, scopeNames ?? this.ScopeNames, currentScopeParameter ?? this.CurrentScopeParameter, this.RequestContextParameter, currentContainerContext ?? this.CurrentContainerContext, this.RequestInitiatorContainerContext, this.ExpressionOverrides, parameterExpressions ?? this.ParameterExpressions, this.RequestContext, autoLifetimeTracker ?? this.AutoLifetimeTracking, resolutionBehavior ?? this.ResolutionBehavior, this.RequestInitiatorResolutionBehavior, nullResultAllowed ?? this.NullResultAllowed, this.IsRequestedFromRoot, nameOfServiceLifeSpanValidatingAgainst ?? this.NameOfServiceLifeSpanValidatingAgainst, currentLifeSpan ?? this.CurrentLifeSpan, perResolutionRequestCacheEnabled ?? this.PerResolutionRequestCacheEnabled, unknownTypeCheckDisabled ?? this.UnknownTypeCheckDisabled, shouldFallBackToRequestInitiator ?? this.shouldFallBackToRequestInitiatorContext, this.IsValidationContext); } ================================================ FILE: src/Resolution/ResolutionStrategy.cs ================================================ using Stashbox.Expressions; using Stashbox.Lifetime; using Stashbox.Registration; using Stashbox.Resolution.Resolvers; using Stashbox.Resolution.Wrappers; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Stashbox.Exceptions; namespace Stashbox.Resolution; internal class ResolutionStrategy : IResolutionStrategy { private readonly ParentContainerResolver parentContainerResolver; private ImmutableBucket resolverRepository; public ResolutionStrategy() { this.parentContainerResolver = new ParentContainerResolver(); this.resolverRepository = new ImmutableBucket([ new EnumerableWrapper(), new LazyWrapper(), new FuncWrapper(), new MetadataWrapper(), new KeyValueWrapper(), new ServiceProviderResolver(), new OptionalValueResolver(), new DefaultValueResolver(), this.parentContainerResolver, new UnknownTypeResolver() ]); } public ServiceContext BuildExpressionForType(ResolutionContext resolutionContext, TypeInformation typeInformation) { if (typeInformation.Type == TypeCache.Type) return resolutionContext.CurrentScopeParameter.AsServiceContext(); if (typeInformation.Type == TypeCache.Type) { resolutionContext.RequestConfiguration.RequiresRequestContext = true; return resolutionContext.RequestContextParameter.AsServiceContext(); } if (typeInformation is { HasDependencyNameAttribute: true, Parent.DependencyName: not null } && typeInformation.Parent.DependencyName.GetType() == typeInformation.Type) return typeInformation.Parent.DependencyName.AsConstant().ConvertTo(typeInformation.Type) .AsServiceContext(); if (typeInformation.IsDependency) { if (resolutionContext.ParameterExpressions.Length > 0) { var type = typeInformation.Type; var length = resolutionContext.ParameterExpressions.Length; for (var i = length; i-- > 0;) { var parameters = resolutionContext.ParameterExpressions[i] .Where(p => p.I2.Type == type || p.I2.Type.Implements(type)).CastToArray(); if (parameters.Length == 0) continue; #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER var selected = Array.Find(parameters, parameter => !parameter.I1) ?? parameters[^1]; #else var selected = Array.Find(parameters, parameter => !parameter.I1) ?? parameters[parameters.Length - 1]; #endif selected.I1 = true; return selected.I2.AsServiceContext(); } } var decorators = resolutionContext.RemainingDecorators.GetOrDefaultByRef(typeInformation.Type); if (decorators is { Length: > 0 }) return BuildExpressionForDecorator(decorators.Front(), resolutionContext.BeginDecoratingContext(typeInformation.Type, decorators), typeInformation, decorators).AsServiceContext(); } var exprOverride = resolutionContext.ExpressionOverrides?.GetOrDefault(typeInformation.Type); if (exprOverride != null) { var expression = typeInformation.DependencyName != null ? exprOverride.LastOrDefault(e => typeInformation.DependencyName.Equals(e.DependencyName)) : exprOverride.LastOrDefault(); if (expression != null) return expression.Instance.AsConstant().AsServiceContext(); } var registration = resolutionContext.ResolutionBehavior.Has(ResolutionBehavior.Current) ? resolutionContext.CurrentContainerContext.RegistrationRepository .GetRegistrationOrDefault(typeInformation, resolutionContext) : null; var isResolutionCallRequired = registration?.Options.IsOn(RegistrationOption.IsResolutionCallRequired) ?? false; if (isResolutionCallRequired && typeInformation.IsDependency && registration != null) return resolutionContext.CurrentScopeParameter .ConvertTo(TypeCache.Type) .CallMethod(Constants.ResolveMethod, typeInformation.Type.AsConstant(), typeInformation.DependencyName.AsConstant(), resolutionContext.ExpressionOverrides?.Walk().SelectMany(c => c.Select(ov => ov)).ToArray().AsConstant() ?? TypeCache.EmptyArray().AsConstant(), resolutionContext.ResolutionBehavior.AsConstant()) .ConvertTo(typeInformation.Type) .AsServiceContext(registration); return registration != null ? this.BuildExpressionForRegistration(registration, resolutionContext, typeInformation) : this.BuildExpressionUsingWrappersOrResolvers(resolutionContext, typeInformation); } public IEnumerable BuildExpressionsForEnumerableRequest(ResolutionContext resolutionContext, TypeInformation typeInformation) { var registrations = resolutionContext.ResolutionBehavior.Has(ResolutionBehavior.Current) ? resolutionContext.CurrentContainerContext.RegistrationRepository .GetRegistrationsOrDefault(typeInformation, resolutionContext) : null; if (registrations == null) return this.BuildEnumerableExpressionUsingWrappersOrResolvers(resolutionContext, typeInformation); var expressions = registrations.Select(registration => { var decorators = resolutionContext.RemainingDecorators.GetOrDefaultByRef(typeInformation.Type); if (decorators == null || decorators.Length == 0) return this.BuildExpressionForRegistration(registration, resolutionContext, typeInformation); decorators.ReplaceBack(registration); return BuildExpressionForDecorator(decorators.Front(), resolutionContext.BeginDecoratingContext(typeInformation.Type, decorators), typeInformation, decorators).AsServiceContext(registration); }); if (resolutionContext.ResolutionBehavior.Has(ResolutionBehavior.PreferEnumerableInCurrent) && !resolutionContext.IsInParentContext) return expressions; if (!this.parentContainerResolver.CanUseForResolution(typeInformation, resolutionContext)) return expressions; var parentRegistrations = this.parentContainerResolver.GetExpressionsForEnumerableRequest(this, typeInformation, resolutionContext); return parentRegistrations.Concat(expressions); } public ServiceContext BuildExpressionForRegistration(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation) { if (serviceRegistration is OpenGenericRegistration openGenericRegistration) { var genericType = serviceRegistration.ImplementationType.MakeGenericType(typeInformation.Type.GetGenericArguments()); serviceRegistration = openGenericRegistration.ProduceClosedRegistration(genericType); } var decoratorContext = resolutionContext.RequestInitiatorResolutionBehavior.Has(ResolutionBehavior.Current) ? resolutionContext.RequestInitiatorContainerContext : resolutionContext.RequestInitiatorContainerContext.ParentContext; var decorators = resolutionContext.RequestInitiatorResolutionBehavior.Has(ResolutionBehavior.Parent) || resolutionContext.RequestInitiatorResolutionBehavior.Has(ResolutionBehavior.ParentDependency) ? CollectDecorators(serviceRegistration.ImplementationType, typeInformation, resolutionContext, decoratorContext) : SearchAndFilterDecorators(serviceRegistration.ImplementationType, typeInformation, resolutionContext, decoratorContext); if (decorators == null) return BuildExpressionAndApplyLifetime(serviceRegistration, resolutionContext, typeInformation) .AsServiceContext(serviceRegistration); var stack = decorators.AsStack(); stack.PushBack(serviceRegistration); return BuildExpressionForDecorator(stack.Front(), resolutionContext.BeginDecoratingContext(typeInformation.Type, stack), typeInformation, stack) .AsServiceContext(serviceRegistration); } public bool IsTypeResolvable(ResolutionContext resolutionContext, TypeInformation typeInformation) { if (typeInformation.Type.IsGenericTypeDefinition) return false; if (typeInformation.Type == TypeCache.Type || typeInformation.Type == TypeCache.Type || typeInformation.Type == TypeCache.Type) return true; if (resolutionContext.CurrentContainerContext.RegistrationRepository.ContainsRegistration(typeInformation.Type, typeInformation.DependencyName) || this.IsWrappedTypeRegistered(typeInformation, resolutionContext)) return true; var exprOverride = resolutionContext.ExpressionOverrides?.GetOrDefault(typeInformation.Type); if (exprOverride == null) return this.CanLookupService(typeInformation, resolutionContext); if (typeInformation.DependencyName != null) return exprOverride.LastOrDefault(exp => exp.DependencyName == typeInformation.DependencyName) != null; return true; } public void RegisterResolver(IResolver resolver) => Swap.SwapValue(ref this.resolverRepository, (t1, _, _, _, repo) => repo.Insert(repo.Length - 4, t1), resolver, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); private ServiceContext BuildExpressionUsingWrappersOrResolvers(ResolutionContext resolutionContext, TypeInformation typeInformation) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var wrapper = this.resolverRepository[i]; switch (wrapper) { case IEnumerableWrapper enumerableWrapper when enumerableWrapper.TryUnWrap(typeInformation.Type, out var unWrappedEnumerable): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedEnumerable); var expressions = this.BuildExpressionsForEnumerableRequest(resolutionContext, unwrappedTypeInformation); if (resolutionContext.CurrentContainerContext.ContainerConfiguration .ExceptionOverEmptyCollectionEnabled && !typeInformation.Type.MapsToGenericTypeDefinition(TypeCache.EnumerableType) && expressions == TypeCache.EmptyArray()) return ServiceContext.Empty; return enumerableWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, expressions) .AsServiceContext(); } case IServiceWrapper serviceWrapper when serviceWrapper.TryUnWrap(typeInformation.Type, out var unWrappedServiceType): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedServiceType); var serviceContext = this.BuildExpressionForType(resolutionContext, unwrappedTypeInformation); return serviceContext.IsEmpty() ? ServiceContext.Empty : serviceWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext) .AsServiceContext(serviceContext.ServiceRegistration); } case IParameterizedWrapper parameterizedWrapper when parameterizedWrapper.TryUnWrap( typeInformation.Type, out var unWrappedParameterizedType, out var parameters): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedParameterizedType); var parameterExpressions = parameters.Select(p => p.AsParameter()).CastToArray(); var serviceContext = this.BuildExpressionForType( resolutionContext.BeginContextWithFunctionParameters(parameterExpressions), unwrappedTypeInformation); return serviceContext.IsEmpty() ? ServiceContext.Empty : parameterizedWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext, parameterExpressions) .AsServiceContext(serviceContext.ServiceRegistration); } case IMetadataWrapper metadataWrapper when metadataWrapper.TryUnWrap(typeInformation.Type, out var unWrappedType, out var unwrappedMetadataType): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedType, metadataType: unwrappedMetadataType); var serviceContext = this.BuildExpressionForType(resolutionContext, unwrappedTypeInformation); return serviceContext.IsEmpty() ? ServiceContext.Empty : metadataWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext) .AsServiceContext(serviceContext.ServiceRegistration); } } } return this.BuildExpressionUsingResolvers(resolutionContext, typeInformation); } private IEnumerable BuildEnumerableExpressionUsingWrappersOrResolvers( ResolutionContext resolutionContext, TypeInformation typeInformation) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var wrapper = this.resolverRepository[i]; switch (wrapper) { case IServiceWrapper serviceWrapper when serviceWrapper.TryUnWrap(typeInformation.Type, out var unWrappedServiceType): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedServiceType); var serviceContexts = this.BuildExpressionsForEnumerableRequest(resolutionContext, unwrappedTypeInformation); return serviceContexts.Select(serviceContext => serviceWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext) .AsServiceContext(serviceContext.ServiceRegistration)); } case IParameterizedWrapper parameterizedWrapper when parameterizedWrapper.TryUnWrap(typeInformation.Type, out var unWrappedParameterizedType, out var parameters): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedParameterizedType); var parameterExpressions = parameters.Select(p => p.AsParameter()).CastToArray(); var serviceContexts = this.BuildExpressionsForEnumerableRequest( resolutionContext.BeginContextWithFunctionParameters(parameterExpressions), unwrappedTypeInformation); return serviceContexts.Select(serviceContext => parameterizedWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext, parameterExpressions) .AsServiceContext(serviceContext.ServiceRegistration)); } case IMetadataWrapper metadataWrapper when metadataWrapper.TryUnWrap(typeInformation.Type, out var unWrappedType, out var unwrappedMetadataType): { var unwrappedTypeInformation = typeInformation.Clone(unWrappedType, metadataType: unwrappedMetadataType); var serviceContexts = this.BuildExpressionsForEnumerableRequest(resolutionContext, unwrappedTypeInformation); return serviceContexts.Select(serviceContext => metadataWrapper .WrapExpression(typeInformation, unwrappedTypeInformation, serviceContext) .AsServiceContext(serviceContext.ServiceRegistration)); } } } return this.BuildEnumerableExpressionsUsingResolvers(resolutionContext, typeInformation); } private ServiceContext BuildExpressionUsingResolvers(ResolutionContext resolutionContext, TypeInformation typeInformation) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var item = this.resolverRepository[i]; if (item is IServiceResolver resolver && resolver.CanUseForResolution(typeInformation, resolutionContext)) return resolver.GetExpression(this, typeInformation, resolutionContext); } return ServiceContext.Empty; } private IEnumerable BuildEnumerableExpressionsUsingResolvers(ResolutionContext resolutionContext, TypeInformation typeInformation) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var item = this.resolverRepository[i]; if (item is IEnumerableSupportedResolver resolver && resolver.CanUseForResolution(typeInformation, resolutionContext)) return resolver.GetExpressionsForEnumerableRequest(this, typeInformation, resolutionContext); } return TypeCache.EmptyArray(); } private bool IsWrappedTypeRegistered(TypeInformation typeInformation, ResolutionContext resolutionContext) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var middleware = this.resolverRepository[i]; switch (middleware) { case IEnumerableWrapper enumerableWrapper when enumerableWrapper.TryUnWrap(typeInformation.Type, out var unWrappedEnumerableType) && !resolutionContext.CurrentContainerContext.ContainerConfiguration .ExceptionOverEmptyCollectionEnabled || typeInformation.Type.MapsToGenericTypeDefinition( TypeCache.EnumerableType) || resolutionContext.CurrentContainerContext .RegistrationRepository .ContainsRegistration(unWrappedEnumerableType, typeInformation.DependencyName): case IServiceWrapper serviceWrapper when serviceWrapper.TryUnWrap(typeInformation.Type, out var unWrappedServiceType) && resolutionContext.CurrentContainerContext.RegistrationRepository.ContainsRegistration( unWrappedServiceType, typeInformation.DependencyName): case IParameterizedWrapper parameterizedWrapper when parameterizedWrapper.TryUnWrap(typeInformation.Type, out var unWrappedParameterizedType, out var _) && resolutionContext.CurrentContainerContext.RegistrationRepository.ContainsRegistration( unWrappedParameterizedType, typeInformation.DependencyName): case IMetadataWrapper metadataWrapper when metadataWrapper.TryUnWrap(typeInformation.Type, out var unWrappedType, out var _) && resolutionContext.CurrentContainerContext.RegistrationRepository.ContainsRegistration( unWrappedType, typeInformation.DependencyName): return true; } } return false; } private bool CanLookupService(TypeInformation typeInfo, ResolutionContext resolutionContext) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var item = this.resolverRepository[i]; if (item is ILookup lookup && lookup.CanLookupService(typeInfo, resolutionContext)) return true; } return false; } private IEnumerable? CollectDecorators(Type implementationType, TypeInformation typeInformation, ResolutionContext resolutionContext, IContainerContext? decoratorContext) { if (decoratorContext == null) return null; var parentDecorators = CollectDecorators(implementationType, typeInformation, resolutionContext, decoratorContext.ParentContext); if (parentDecorators == null) return SearchAndFilterDecorators(implementationType, typeInformation, resolutionContext, decoratorContext); var currentDecorators = SearchAndFilterDecorators(implementationType, typeInformation, resolutionContext, decoratorContext); return currentDecorators == null ? parentDecorators : parentDecorators.Concat(currentDecorators); } private IEnumerable? SearchAndFilterDecorators(Type implementationType, TypeInformation typeInformation, ResolutionContext resolutionContext, IContainerContext? decoratorContext) { var decorators = decoratorContext?.DecoratorRepository.GetDecoratorsOrDefault(implementationType, typeInformation, resolutionContext); if (decorators == null) return null; var filtered = decorators.Where(d => { var types = d.ImplementationType.GetPossibleDependencyTypes().ToArray(); return types.Any(implementationType.Implements) || types.Any(t => TryUnwrapTypeFrom(t, out var unwrapped) && implementationType.Implements(unwrapped)); }).ToArray(); return filtered.Length == 0 ? null : filtered; } private bool TryUnwrapTypeFrom(Type wrapped, out Type unwrapped) { var length = this.resolverRepository.Length; for (var i = 0; i < length; i++) { var resolver = this.resolverRepository[i]; switch (resolver) { case IEnumerableWrapper enumerableWrapper when enumerableWrapper.TryUnWrap(wrapped, out unwrapped): case IServiceWrapper serviceWrapper when serviceWrapper.TryUnWrap(wrapped, out unwrapped): case IParameterizedWrapper parameterizedWrapper when parameterizedWrapper.TryUnWrap(wrapped, out unwrapped, out _): case IMetadataWrapper metadataWrapper when metadataWrapper.TryUnWrap(wrapped, out unwrapped, out _): return true; } } unwrapped = TypeCache.EmptyType; return false; } private static Expression? BuildExpressionForDecorator(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation, Utils.Data.Stack decorators) { if (serviceRegistration is OpenGenericRegistration openGenericRegistration) { var genericType = serviceRegistration.ImplementationType.MakeGenericType(typeInformation.Type.GetGenericArguments()); serviceRegistration = openGenericRegistration.ProduceClosedRegistration(genericType); } return BuildExpressionAndApplyLifetime(serviceRegistration, resolutionContext, typeInformation, decorators.PeekBack()?.Lifetime); } private static Expression? BuildExpressionAndApplyLifetime(ServiceRegistration serviceRegistration, ResolutionContext resolutionContext, TypeInformation typeInformation, LifetimeDescriptor? secondaryLifetimeDescriptor = null) { var lifetimeDescriptor = secondaryLifetimeDescriptor != null && serviceRegistration.Lifetime is EmptyLifetime ? secondaryLifetimeDescriptor : serviceRegistration.Lifetime; var expression = !IsOutputLifetimeManageable(serviceRegistration) || lifetimeDescriptor is EmptyLifetime ? ExpressionBuilder.BuildExpressionForRegistration(serviceRegistration, resolutionContext, typeInformation) : lifetimeDescriptor.ApplyLifetime(serviceRegistration, resolutionContext, typeInformation); return typeInformation.Type != expression?.Type ? expression?.ConvertTo(typeInformation.Type) : expression; } private static bool IsOutputLifetimeManageable(ServiceRegistration serviceRegistration) => serviceRegistration is not OpenGenericRegistration && !serviceRegistration.IsInstance(); } ================================================ FILE: src/Resolution/Resolvers/DefaultValueResolver.cs ================================================ using Stashbox.Utils; using System.Linq.Expressions; namespace Stashbox.Resolution.Resolvers; internal class DefaultValueResolver : IServiceResolver { public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) => typeInfo.Type.AsDefault().AsServiceContext(); public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionContext.CurrentContainerContext.ContainerConfiguration.DefaultValueInjectionEnabled && (typeInfo.Type.IsValueType || typeInfo.Type == TypeCache.Type); } ================================================ FILE: src/Resolution/Resolvers/OptionalValueResolver.cs ================================================ using System.Linq.Expressions; namespace Stashbox.Resolution.Resolvers; internal class OptionalValueResolver : IServiceResolver { public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) => (typeInfo.DefaultValue?.Value ?? null).AsConstant(typeInfo.Type).AsServiceContext(); public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext) => typeInfo.DefaultValue != null; } ================================================ FILE: src/Resolution/Resolvers/ParentContainerResolver.cs ================================================ using System; using System.Collections.Generic; namespace Stashbox.Resolution.Resolvers; internal class ParentContainerResolver : IEnumerableSupportedResolver, ILookup { public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionContext.CurrentContainerContext.ParentContext != null && (resolutionContext.ResolutionBehavior.Has(ResolutionBehavior.Parent) || typeInfo.IsDependency && resolutionContext.ResolutionBehavior.Has(ResolutionBehavior.ParentDependency)); public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionStrategy.BuildExpressionForType(resolutionContext.BeginParentContainerContext(resolutionContext .CurrentContainerContext.ParentContext!), typeInfo); public IEnumerable GetExpressionsForEnumerableRequest( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionStrategy.BuildExpressionsForEnumerableRequest(resolutionContext.BeginParentContainerContext(resolutionContext .CurrentContainerContext.ParentContext!), typeInfo); public bool CanLookupService(TypeInformation typeInfo, ResolutionContext resolutionContext) { if (resolutionContext.CurrentContainerContext.ParentContext == null || !this.CanUseForResolution(typeInfo, resolutionContext)) return false; return resolutionContext.CurrentContainerContext.ResolutionStrategy.IsTypeResolvable(resolutionContext.BeginParentContainerContext(resolutionContext .CurrentContainerContext.ParentContext), typeInfo); } } ================================================ FILE: src/Resolution/Resolvers/ServiceProviderResolver.cs ================================================ using System; using System.Linq.Expressions; using Stashbox.Utils; namespace Stashbox.Resolution.Resolvers; internal class ServiceProviderResolver : IServiceResolver { public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionContext.CurrentScopeParameter.AsServiceContext(); public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext) => typeInfo.Type == TypeCache.Type; } ================================================ FILE: src/Resolution/Resolvers/UnknownTypeResolver.cs ================================================ using Stashbox.Registration; using Stashbox.Registration.Fluent; using System; namespace Stashbox.Resolution.Resolvers; internal class UnknownTypeResolver : IServiceResolver, ILookup { public bool CanLookupService(TypeInformation typeInfo, ResolutionContext resolutionContext) => this.CanUseForResolution(typeInfo, resolutionContext); public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionContext) => resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled && !resolutionContext.UnknownTypeCheckDisabled && typeInfo.Type.IsResolvableType() || resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.UnknownTypeConfigurator != null; public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionContext) { var name = typeInfo.DependencyName; var configurator = resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.UnknownTypeConfigurator; var unknownRegistrationConfigurator = new UnknownRegistrationConfigurator(typeInfo.Type, typeInfo.Type, name, resolutionContext.RequestInitiatorContainerContext.ContainerConfiguration.DefaultLifetime); configurator?.Invoke(unknownRegistrationConfigurator); if (unknownRegistrationConfigurator.RegistrationShouldBeSkipped) return ServiceContext.Empty; if (!unknownRegistrationConfigurator.IsFactory() && (!unknownRegistrationConfigurator.ImplementationType.IsResolvableType() || !unknownRegistrationConfigurator.ImplementationType.Implements(unknownRegistrationConfigurator.ServiceType))) return ServiceContext.Empty; ServiceRegistrator.Register(resolutionContext.RequestInitiatorContainerContext, unknownRegistrationConfigurator, typeInfo.Type); return resolutionStrategy.BuildExpressionForRegistration(unknownRegistrationConfigurator, resolutionContext.FallBackToRequestInitiatorIfNeeded(), typeInfo); } } ================================================ FILE: src/Resolution/ServiceContext.cs ================================================ using Stashbox.Registration; using System.Linq.Expressions; namespace Stashbox.Resolution; /// /// Represents the context of a service. /// public readonly struct ServiceContext { /// /// The expression that describes the instantiation of the service. /// public readonly Expression ServiceExpression; /// /// The registration of the service. /// public readonly ServiceRegistration? ServiceRegistration; /// /// Constructs a . /// /// The expression that describes the instantiation of the service. /// The registration of the service. public ServiceContext(Expression serviceExpression, ServiceRegistration? serviceRegistration) { this.ServiceExpression = serviceExpression; this.ServiceRegistration = serviceRegistration; } internal bool IsEmpty() => this.Equals(Empty); private bool Equals(ServiceContext other) => ReferenceEquals(ServiceExpression, other.ServiceExpression) && ReferenceEquals(ServiceRegistration, other.ServiceRegistration); internal static readonly ServiceContext Empty = default; } ================================================ FILE: src/Resolution/TypeInformation.cs ================================================ using Stashbox.Utils; using System; using System.Collections.Generic; namespace Stashbox.Resolution; /// /// Represents type information about a dependency. /// public class TypeInformation { /// /// Represents a method or ctor parameter's default value. /// /// public class DefaultValueHolder(object? value) { /// /// The default value. /// public object? Value { get; } = value; } /// /// The reflected type of the dependency. /// public readonly Type Type; /// /// The name of the dependency. /// public readonly object? DependencyName; /// /// The type of the metadata. /// public readonly Type? MetadataType; /// /// The reflected type of the parent of the dependency. /// public readonly Type? ParentType; /// /// Custom attributes of the dependency. /// public readonly IEnumerable? CustomAttributes; /// /// If the dependency is a method or a constructor parameter, this property holds the parameter name, if it's a class member then the member name. /// public readonly string? ParameterOrMemberName; /// /// The default value of the dependency. /// public readonly DefaultValueHolder? DefaultValue; /// /// Indicates whether the dependency has a or similar attribute. /// public bool HasDependencyNameAttribute { get; set; } /// /// The parent type's metadata. /// public readonly TypeInformation? Parent; internal readonly bool IsDependency; internal TypeInformation(Type type, object? dependencyName) { this.Type = type; this.DependencyName = dependencyName; this.ParentType = null; this.MetadataType = null; this.CustomAttributes = null; this.ParameterOrMemberName = null; this.DefaultValue = null; this.Parent = null; this.IsDependency = false; this.HasDependencyNameAttribute = false; } internal TypeInformation(Type type, Type? parentType, TypeInformation? parent, object? dependencyName, IEnumerable? customAttributes, string? parameterOrMemberName, DefaultValueHolder? defaultValue, bool hasDependencyNameAttribute, Type? metaDataType) { this.Type = type; this.ParentType = parentType; this.DependencyName = dependencyName; this.CustomAttributes = customAttributes; this.ParameterOrMemberName = parameterOrMemberName; this.DefaultValue = defaultValue; this.MetadataType = metaDataType; this.Parent = parent; this.IsDependency = parentType != null; this.HasDependencyNameAttribute = hasDependencyNameAttribute; } /// /// Clones the type information with different type. /// /// The type. /// The dependency name. /// The metadata type. /// The cloned type information. public TypeInformation Clone(Type type, object? dependencyName = null, Type? metadataType = null) => new(type, this.ParentType, this.Parent, dependencyName ?? this.DependencyName, this.CustomAttributes, this.ParameterOrMemberName, this.DefaultValue, this.HasDependencyNameAttribute, metadataType ?? this.MetadataType); internal static readonly TypeInformation Empty = new(TypeCache.Type, null); } ================================================ FILE: src/Resolution/Wrappers/EnumerableWrapper.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Stashbox.Utils; namespace Stashbox.Resolution.Wrappers; internal class EnumerableWrapper : IEnumerableWrapper { public Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, IEnumerable serviceContexts) => wrappedTypeInformation.Type.InitNewArray(serviceContexts.Select(e => e.ServiceExpression)); public bool TryUnWrap(Type type, out Type unWrappedType) { var enumerableType = type.GetEnumerableType(); if (enumerableType == null) { unWrappedType = TypeCache.EmptyType; return false; } unWrappedType = enumerableType; return true; } } ================================================ FILE: src/Resolution/Wrappers/FuncWrapper.cs ================================================ using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Stashbox.Resolution.Wrappers; internal class FuncWrapper : IParameterizedWrapper { public Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext, IEnumerable parameterExpressions) => serviceContext.ServiceExpression.AsLambda(originalTypeInformation.Type, parameterExpressions); public bool TryUnWrap(Type type, out Type unWrappedType, out IEnumerable parameterTypes) { if (!type.IsSubclassOf(TypeCache.Type)) { unWrappedType = TypeCache.EmptyType; parameterTypes = TypeCache.EmptyTypes; return false; } var method = type.GetMethod("Invoke"); if (method == null || method.ReturnType == TypeCache.VoidType) { unWrappedType = TypeCache.EmptyType; parameterTypes = TypeCache.EmptyTypes; return false; } unWrappedType = method.ReturnType; parameterTypes = method.GetParameters().Select(p => p.ParameterType); return true; } } ================================================ FILE: src/Resolution/Wrappers/KeyValueWrapper.cs ================================================ using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq.Expressions; namespace Stashbox.Resolution.Wrappers; internal class KeyValueWrapper : IServiceWrapper { private static readonly HashSet SupportedTypes = [ typeof(KeyValuePair<,>), typeof(ReadOnlyKeyValue<,>) ]; private static bool IsKeyValueType(Type type) => type.IsClosedGenericType() && SupportedTypes.Contains(type.GetGenericTypeDefinition()); public Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext) { var arguments = originalTypeInformation.Type.GetGenericArguments(); var constructor = originalTypeInformation.Type.GetConstructor(arguments)!; var name = serviceContext.ServiceRegistration?.Name; return constructor.MakeNew(name.AsConstant(), serviceContext.ServiceExpression); } public bool TryUnWrap(Type type, out Type unWrappedType) { if (!IsKeyValueType(type)) { unWrappedType = TypeCache.EmptyType; return false; } var arguments = type.GetGenericArguments(); var nameType = arguments[0]; if (nameType != TypeCache.Type) { unWrappedType = TypeCache.EmptyType; return false; } unWrappedType = arguments[1]; return true; } } ================================================ FILE: src/Resolution/Wrappers/LazyWrapper.cs ================================================ using Stashbox.Utils; using System; using System.Linq.Expressions; namespace Stashbox.Resolution.Wrappers; internal class LazyWrapper : IServiceWrapper { private static bool IsLazy(Type type) => type.IsClosedGenericType() && type.GetGenericTypeDefinition() == typeof(Lazy<>); public Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext) { var ctorParamType = TypeCache.FuncType.MakeGenericType(wrappedTypeInformation.Type); var lazyConstructor = originalTypeInformation.Type.GetConstructor(ctorParamType)!; return lazyConstructor.MakeNew(serviceContext.ServiceExpression.AsLambda()); } public bool TryUnWrap(Type type, out Type unWrappedType) { if (!IsLazy(type)) { unWrappedType = TypeCache.EmptyType; return false; } unWrappedType = type.GetGenericArguments()[0]; return true; } } ================================================ FILE: src/Resolution/Wrappers/MetadataWrapper.cs ================================================ using Stashbox.Registration; using System; using System.Collections.Generic; using System.Linq.Expressions; using Stashbox.Utils; namespace Stashbox.Resolution.Wrappers; internal class MetadataWrapper : IMetadataWrapper { private static readonly HashSet SupportedTypes = [ typeof(ValueTuple<,>), typeof(Tuple<,>), typeof(Metadata<,>) ]; private static bool IsMetadataType(Type type) => type.IsClosedGenericType() && SupportedTypes.Contains(type.GetGenericTypeDefinition()); public Expression WrapExpression(TypeInformation originalTypeInformation, TypeInformation wrappedTypeInformation, ServiceContext serviceContext) { var arguments = originalTypeInformation.Type.GetGenericArguments(); var constructor = originalTypeInformation.Type.GetConstructor(arguments)!; var metadata = serviceContext.ServiceRegistration?.Options.GetOrDefault(RegistrationOption.Metadata); return constructor.MakeNew(serviceContext.ServiceExpression, metadata.AsConstant()); } public bool TryUnWrap(Type type, out Type unWrappedType, out Type metadataType) { if (!IsMetadataType(type)) { unWrappedType = TypeCache.EmptyType; metadataType = TypeCache.EmptyType; return false; } var arguments = type.GetGenericArguments(); var serviceType = arguments[0]; metadataType = arguments[1]; unWrappedType = serviceType; return true; } } ================================================ FILE: src/ResolutionScope.AsyncInitializer.cs ================================================ using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Threading; using System.Threading.Tasks; namespace Stashbox; internal sealed partial class ResolutionScope { private sealed class AsyncInitializable { private readonly object item; private readonly Func initializer; private int initialized; public AsyncInitializable(object item, Func initializer) { this.item = item; this.initializer = initializer; this.initialized = 0; } public ValueTask InvokeAsync(IDependencyResolver resolver, CancellationToken token) => Interlocked.CompareExchange(ref initialized, 1, 0) != 0 ? default : new ValueTask(initializer(item, resolver, token)); } private ImmutableLinkedList initializables = ImmutableLinkedList.Empty; public object AddWithAsyncInitializer(object initializable, Func initializer) { this.ThrowIfDisposed(); Swap.SwapValue(ref this.initializables, (t1, t2, _, _, root) => root.Add(new AsyncInitializable(t1, t2)), initializable, initializer, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return initializable; } /// public async ValueTask InvokeAsyncInitializers(CancellationToken token = default) { this.ThrowIfDisposed(); if (this.ParentScope != null) await this.ParentScope.InvokeAsyncInitializers(token).ConfigureAwait(false); var initializable = this.initializables; while (!ReferenceEquals(initializable, ImmutableLinkedList.Empty)) { await initializable.Value.InvokeAsync(this, token).ConfigureAwait(false); initializable = initializable.Next; } } } ================================================ FILE: src/ResolutionScope.Disposable.cs ================================================ using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; namespace Stashbox; internal partial class ResolutionScope { private readonly struct Finalizable { public readonly object Item; public readonly Action Finalizer; public Finalizable(object item, Action finalizer) { this.Item = item; this.Finalizer = finalizer; } } private ImmutableLinkedList disposables = ImmutableLinkedList.Empty; private ImmutableLinkedList finalizables = ImmutableLinkedList.Empty; public object AddDisposableTracking(object disposable) { this.ThrowIfDisposed(); Swap.SwapValue(ref this.disposables, (t1, _, _, _, root) => root.Add(t1), disposable, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return disposable; } public object AddRequestContextAwareDisposableTracking(object disposable, IRequestContext requestContext) { this.ThrowIfDisposed(); var internalContext = (IInternalRequestContext)requestContext; return internalContext.IsInstanceExcludedFromTracking(disposable) ? disposable : this.AddDisposableTracking(disposable); } public object AddWithFinalizer(object finalizable, Action finalizer) { this.ThrowIfDisposed(); Swap.SwapValue(ref this.finalizables, (t1, t2, _, _, root) => root.Add(new Finalizable(t1, t2)), finalizable, finalizer, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return finalizable; } public void Dispose() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) return; this.RemoveSelfFromParent(); this.DisposeChildScopes(); this.CallFinalizers(); this.CallDisposes(); } #if HAS_ASYNC_DISPOSABLE /// public async ValueTask DisposeAsync() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) return; this.RemoveSelfFromParent(); await this.DisposeChildScopesAsync().ConfigureAwait(false); this.CallFinalizers(); await CallAsyncDisposes().ConfigureAwait(false); async ValueTask CallAsyncDisposes() { var root = this.disposables; while (!ReferenceEquals(root, ImmutableLinkedList.Empty)) { switch (root.Value) { case IAsyncDisposable asyncDisposable: await asyncDisposable.DisposeAsync().ConfigureAwait(false); break; case IDisposable disposable: disposable.Dispose(); break; default: throw new InvalidOperationException($"Could not dispose {root.Value.GetType()} as it doesn't implement either IDisposable or IAsyncDisposable."); } root = root.Next; } } } private async ValueTask DisposeChildScopesAsync() { if (this.childScopes.IsEmpty) return; foreach (var childScope in this.childScopes.Walk()) await childScope.DisposeAsync().ConfigureAwait(false); } #endif private void DisposeChildScopes() { if (this.childScopes.IsEmpty) return; foreach (var childScope in this.childScopes.Walk()) childScope.Dispose(); } private void RemoveSelfFromParent() { if (this.parentScope is null || !this.attachedToParent) return; Swap.SwapValue(ref this.parentScope.childScopes, (t1, _, _, _, childRepo) => childRepo.Remove(t1), this, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); } private void CallFinalizers() { var rootFinalizable = this.finalizables; while (!ReferenceEquals(rootFinalizable, ImmutableLinkedList.Empty)) { rootFinalizable.Value.Finalizer(rootFinalizable.Value.Item); rootFinalizable = rootFinalizable.Next; } } private void CallDisposes() { var root = this.disposables; while (!ReferenceEquals(root, ImmutableLinkedList.Empty) && root.Value is IDisposable disposable) { disposable.Dispose(); root = root.Next; } } private void ThrowIfDisposed([CallerMemberName] string caller = "") { if (this.disposed == 1) Shield.ThrowDisposedException(this.GetType().FullName, caller); } } ================================================ FILE: src/ResolutionScope.Resolver.cs ================================================ using Stashbox.Exceptions; using Stashbox.Expressions; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Stashbox.Utils.Data; namespace Stashbox; internal partial class ResolutionScope { /// public object Resolve(Type typeFrom) { this.ThrowIfDisposed(); var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault(Constants.DefaultResolutionBehaviorInt)?.ServiceFactory; if (cachedFactory != null) return cachedFactory(this, RequestContext.Empty); return this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault(Constants.DefaultResolutionBehaviorInt)?.ServiceFactory?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveService(typeFrom, name: null, dependencyOverrides: null, Constants.DefaultResolutionBehavior); } /// public object Resolve(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { this.ThrowIfDisposed(); if (dependencyOverrides != null) return this.BuildAndResolveService(typeFrom, name, dependencyOverrides, resolutionBehavior); if (name != null) { var resultFromCachedFactory = this.GetObjectFromCachedFactoryOrDefault(typeFrom, name, resolutionBehavior); return resultFromCachedFactory ?? this.BuildAndResolveService(typeFrom, name, dependencyOverrides: null, resolutionBehavior); } var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.ServiceFactory; if (cachedFactory != null) return cachedFactory(this, RequestContext.Empty); return this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.ServiceFactory?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveService(typeFrom, name: null, dependencyOverrides: null, resolutionBehavior); } /// public object? ResolveOrDefault(Type typeFrom) { this.ThrowIfDisposed(); var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault(Constants.DefaultResolutionBehaviorInt)?.ServiceFactory; if (cachedFactory != null) return cachedFactory(this, RequestContext.Empty); return this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault(Constants.DefaultResolutionBehaviorInt)?.ServiceFactory?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveServiceOrDefault(typeFrom, name: null, dependencyOverrides: null, Constants.DefaultResolutionBehavior); } /// public object? ResolveOrDefault(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { this.ThrowIfDisposed(); if (dependencyOverrides != null) return this.BuildAndResolveServiceOrDefault(typeFrom, name, dependencyOverrides, resolutionBehavior); if (name != null) { var resultFromCachedFactory = this.GetObjectFromCachedFactoryOrDefault(typeFrom, name, resolutionBehavior); return resultFromCachedFactory ?? this.BuildAndResolveServiceOrDefault(typeFrom, name, dependencyOverrides: null, resolutionBehavior); } var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.ServiceFactory; if (cachedFactory != null) return cachedFactory(this, RequestContext.Empty); return this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.ServiceFactory?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveServiceOrDefault(typeFrom, name: null, dependencyOverrides: null, resolutionBehavior); } /// public object? GetService(Type serviceType) => this.ResolveOrDefault(serviceType); /// public Delegate ResolveFactory(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) { this.ThrowIfDisposed(); var key = $"{name ?? ""}{string.Join("", parameterTypes.Append(typeFrom).Select(t => t.FullName))}"; var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(key); if (cachedFactory != null) return (Delegate)cachedFactory(this, RequestContext.Empty); return (Delegate?)this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(key)?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveFactoryDelegate(typeFrom, parameterTypes, name, key, resolutionBehavior); } /// public Delegate? ResolveFactoryOrDefault(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) { this.ThrowIfDisposed(); var key = $"{name ?? ""}{string.Join("", parameterTypes.Append(typeFrom).Select(t => t.FullName))}"; var cachedFactory = this.delegateCache.ServiceDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(key); if (cachedFactory != null) return (Delegate)cachedFactory(this, RequestContext.Empty); return (Delegate?)this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(typeFrom)? .GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(key)?.Invoke(this, RequestContext.Begin()) ?? this.BuildAndResolveFactoryDelegateOrDefault(typeFrom, parameterTypes, name, key, resolutionBehavior); } /// public TTo BuildUp(TTo instance, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) where TTo : class { this.ThrowIfDisposed(); var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, false); var expression = ExpressionFactory.ConstructBuildUpExpression(resolutionContext, instance.AsConstant(), new TypeInformation(TypeCache.Type, null)); return (TTo)expression.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration)(this, resolutionContext.RequestConfiguration.RequiresRequestContext ? RequestContext.Begin() : RequestContext.Empty); } /// public object Activate(Type type, ResolutionBehavior resolutionBehavior, params object[] arguments) { this.ThrowIfDisposed(); if (!type.IsResolvableType()) throw new ArgumentException($"The given type ({type.FullName}) could not be activated on the fly by the container."); var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, false, arguments, this.lateKnownInstances); var expression = ExpressionFactory.ConstructExpression(resolutionContext, new TypeInformation(type, null)); return expression?.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration)(this, resolutionContext.RequestConfiguration.RequiresRequestContext ? RequestContext.Begin() : RequestContext.Empty) ?? throw ResolutionFailedException.CreateWithDesiredExceptionType(type, externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); } /// public bool CanResolve(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); return this.containerContext.ResolutionStrategy .IsTypeResolvable(ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, false, false), new TypeInformation(typeFrom, name)); } /// public IDependencyResolver BeginScope(object? name = null, bool attachToParent = false) { this.ThrowIfDisposed(); var scope = new ResolutionScope(this, this.containerContext, this.delegateCacheProvider, name, attachToParent); if (attachToParent) Swap.SwapValue(ref this.childScopes, (t1, _, _, _, childStore) => childStore.AddOrSkip(t1), scope, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return scope; } /// public void PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false, object? name = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(instance, nameof(instance)); var @override = Override.Of(typeFrom, instance, name); Swap.SwapValue(ref this.lateKnownInstances, (t1, t2, t3, _, instances) => instances.AddOrUpdate(t1, t2, false, (o, _) => o.Add(t3)), typeFrom, new ImmutableBucket([@override]), @override, Constants.DelegatePlaceholder); if (!withoutDisposalTracking && instance is IDisposable disposable) this.AddDisposableTracking(disposable); this.delegateCache = new DelegateCache(); } /// public IEnumerable GetDelegateCacheEntries() { this.ThrowIfDisposed(); return this.delegateCache.ServiceDelegates.Walk().SelectMany(d => d.Value.Walk().Select(e => new DelegateCacheEntry(d.Key, e.Value.ServiceFactory, e.Value.NamedFactories?.Walk().Select(n => new NamedCacheEntry(n.Key, n.Value)), (ResolutionBehavior)e.Key)).OrderBy(c => c.ServiceType.FullName)); } private object BuildAndResolveService(Type type, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior) { var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, nullResultAllowed: false, dependencyOverrides, this.lateKnownInstances); var serviceContext = this.containerContext.ResolutionStrategy .BuildExpressionForType(resolutionContext, new TypeInformation(type, name)); if (serviceContext.IsEmpty()) throw ResolutionFailedException.CreateWithDesiredExceptionType(type, name, externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); var factory = serviceContext.ServiceExpression.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration); return name != null ? StoreAndInvokeNamedServiceDelegate(type, name, factory, resolutionContext, resolutionBehavior) : StoreAndInvokeServiceDelegate(type, factory, resolutionContext, resolutionBehavior); } private object? BuildAndResolveServiceOrDefault(Type type, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior) { var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, nullResultAllowed: true, dependencyOverrides, this.lateKnownInstances); var serviceContext = this.containerContext.ResolutionStrategy.BuildExpressionForType(resolutionContext, new TypeInformation(type, name)); if (serviceContext.IsEmpty()) return null; var factory = serviceContext.ServiceExpression.CompileDelegate(resolutionContext, this.containerContext.ContainerConfiguration); return name != null ? StoreAndInvokeNamedServiceDelegate(type, name, factory, resolutionContext, resolutionBehavior) : StoreAndInvokeServiceDelegate(type, factory, resolutionContext, resolutionBehavior); } private Delegate BuildAndResolveFactoryDelegate(Type type, Type[] parameterTypes, object? name, string key, ResolutionBehavior resolutionBehavior) { var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, nullResultAllowed: false, initialParameters: parameterTypes.AsParameters()); var serviceContext = this.containerContext.ResolutionStrategy .BuildExpressionForType(resolutionContext, new TypeInformation(type, name)); if (serviceContext.IsEmpty()) throw ResolutionFailedException.CreateWithDesiredExceptionType(type, name, externalExceptionType: resolutionContext.CurrentContainerContext.ContainerConfiguration.ExternalResolutionFailedExceptionType); var expression = serviceContext.ServiceExpression.AsLambda(resolutionContext.ParameterExpressions .SelectMany(x => x.Select(i => i.I2))); var factory = expression.CompileDynamicDelegate(resolutionContext, this.containerContext.ContainerConfiguration); return (Delegate)StoreAndInvokeNamedServiceDelegate(type, key, factory, resolutionContext, resolutionBehavior); } private Delegate? BuildAndResolveFactoryDelegateOrDefault(Type type, Type[] parameterTypes, object? name, string key, ResolutionBehavior resolutionBehavior) { var resolutionContext = ResolutionContext.BeginTopLevelContext(this.GetActiveScopeNames(), this.containerContext, resolutionBehavior, this.ParentScope == null, nullResultAllowed: true, initialParameters: parameterTypes.AsParameters()); var serviceContext = this.containerContext.ResolutionStrategy .BuildExpressionForType(resolutionContext, new TypeInformation(type, name)); if (serviceContext.IsEmpty()) return null; var expression = serviceContext.ServiceExpression.AsLambda(resolutionContext.ParameterExpressions .SelectMany(x => x.Select(i => i.I2))); var factory = expression.CompileDynamicDelegate(resolutionContext, this.containerContext.ContainerConfiguration); return (Delegate)StoreAndInvokeNamedServiceDelegate(type, key, factory, resolutionContext, resolutionBehavior); } private object StoreAndInvokeServiceDelegate(Type serviceType, Func factory, ResolutionContext resolutionContext, ResolutionBehavior resolutionBehavior) { if (resolutionContext.RequestConfiguration.FactoryDelegateCacheEnabled) { Swap.SwapValue(ref resolutionContext.RequestConfiguration.RequiresRequestContext ? ref this.delegateCache.RequestContextAwareDelegates : ref this.delegateCache.ServiceDelegates, (t1, t2, t3, _, c) => { var newEntry = new CacheEntry(t2, null); var tree = ImmutableTree.Empty.AddOrUpdate((int)t3, newEntry); return c.AddOrUpdate(t1, tree, true, (old, _) => old.AddOrUpdate((int)t3, newEntry, (oldEntry, _) => new CacheEntry(t2, oldEntry.NamedFactories))); }, serviceType, factory, resolutionBehavior, Constants.DelegatePlaceholder); } return factory(this, resolutionContext.RequestConfiguration.RequiresRequestContext ? resolutionContext.RequestContext : RequestContext.Empty); } private object StoreAndInvokeNamedServiceDelegate(Type serviceType, object key, Func factory, ResolutionContext resolutionContext, ResolutionBehavior resolutionBehavior) { if (resolutionContext.RequestConfiguration.FactoryDelegateCacheEnabled) Swap.SwapValue(ref resolutionContext.RequestConfiguration.RequiresRequestContext ? ref this.delegateCache.RequestContextAwareDelegates : ref this.delegateCache.ServiceDelegates, (t1, t2, t3, t4, c) => { var newEntry = new CacheEntry(null, ImmutableTree>.Empty.AddOrUpdate(t2, t3, false)); var tree = ImmutableTree.Empty.AddOrUpdate((int)t4, newEntry); return c.AddOrUpdate(t1, tree, true, (old, _) => old.AddOrUpdate((int)t4, newEntry, (oldEntry, _) => oldEntry.NamedFactories != null ? new CacheEntry(oldEntry.ServiceFactory, oldEntry.NamedFactories.AddOrUpdate(t2, t3, false)) : new CacheEntry(oldEntry.ServiceFactory, ImmutableTree>.Empty.AddOrUpdate(t2, t3, false)))); }, serviceType, key, factory, resolutionBehavior); return factory(this, resolutionContext.RequestConfiguration.RequiresRequestContext ? resolutionContext.RequestContext : RequestContext.Empty); } private T? GetObjectFromCachedFactoryOrDefault(Type type, object? name, ResolutionBehavior resolutionBehavior) { var cachedFactory = name == null ? this.delegateCache.ServiceDelegates.GetOrDefaultByRef(type)?.GetOrDefault((int)resolutionBehavior)?.ServiceFactory : this.delegateCache.ServiceDelegates.GetOrDefaultByRef(type)?.GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(name); if (cachedFactory != null) return (T?)cachedFactory(this, RequestContext.Empty); var requestContextAwareFactory = name == null ? this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(type)?.GetOrDefault((int)resolutionBehavior)?.ServiceFactory : this.delegateCache.RequestContextAwareDelegates.GetOrDefaultByRef(type)?.GetOrDefault((int)resolutionBehavior)?.NamedFactories?.GetOrDefaultByValue(name); return (T?)requestContextAwareFactory?.Invoke(this, RequestContext.Begin()); } } ================================================ FILE: src/ResolutionScope.cs ================================================ using Stashbox.Exceptions; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data.Immutable; using System; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Threading; namespace Stashbox; internal sealed partial class ResolutionScope : IResolutionScope { private sealed class ScopedEvaluator { private const int MaxWaitTimeInMs = 3000; private static readonly object Default = new(); private int constructingThreadId = -1; private object evaluatedObject = Default; public object Evaluate(IResolutionScope scope, IRequestContext requestContext, Func factory, Type serviceType) { var evaluated = Volatile.Read(ref this.evaluatedObject); if (!ReferenceEquals(evaluated, Default)) return GetEvaluatedObjectOrThrow(evaluated); return Interlocked.CompareExchange(ref this.constructingThreadId, Environment.CurrentManagedThreadId, -1) != -1 ? this.WaitForEvaluation(serviceType) : this.EvaluateFactory(scope, requestContext, factory); } private object WaitForEvaluation(Type serviceType) { if (Volatile.Read(ref this.constructingThreadId) == Environment.CurrentManagedThreadId) throw new ResolutionFailedException(serviceType, message: $"Circular dependency was detected while resolving '{serviceType}'. " + $"The same scoped/singleton service was requested again before its construction completed. " + $"This service is configured to allow only one instance per scope."); SpinWait spin = default; var startTime = (uint)Environment.TickCount; object evaluated; while (ReferenceEquals(evaluated = Volatile.Read(ref this.evaluatedObject), Default)) { if (spin.NextSpinWillYield) { var currentTime = (uint)Environment.TickCount; if (MaxWaitTimeInMs <= currentTime - startTime) throw new ResolutionFailedException(serviceType, message: $"The construction of '{serviceType}' did not complete within the expected time. " + $"It's possible that the factory is blocked, deadlocked, or waiting on another resolution."); } spin.SpinOnce(); } return GetEvaluatedObjectOrThrow(evaluated); } private static object GetEvaluatedObjectOrThrow(object evaluated) { if (evaluated is FailedEvaluation failedEvaluation) failedEvaluation.ExceptionDispatchInfo.Throw(); return evaluated; } [MethodImpl(MethodImplOptions.NoInlining)] private object EvaluateFactory(IResolutionScope scope, IRequestContext requestContext, Func factory) { try { var evaluated = factory(scope, requestContext); Volatile.Write(ref this.evaluatedObject, evaluated); return evaluated; } catch (Exception ex) { Volatile.Write(ref this.evaluatedObject, new FailedEvaluation(ex)); throw; } } private sealed class FailedEvaluation(Exception exception) { internal readonly ExceptionDispatchInfo ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); } } private int disposed; private readonly bool attachedToParent; private readonly IContainerContext containerContext; private readonly DelegateCacheProvider delegateCacheProvider; private readonly ResolutionScope? parentScope; private ImmutableTree scopedInstances = ImmutableTree.Empty; private ImmutableTree> lateKnownInstances = ImmutableTree>.Empty; private ImmutableRefTree childScopes = ImmutableRefTree.Empty; private DelegateCache delegateCache; public object? Name { get; } public IResolutionScope? ParentScope => this.parentScope; public ResolutionScope(IContainerContext containerContext) { this.containerContext = containerContext; this.delegateCacheProvider = new DelegateCacheProvider(); this.delegateCache = new DelegateCache(); } private ResolutionScope(ResolutionScope parent, IContainerContext containerContext, DelegateCacheProvider delegateCacheProvider, object? name, bool attachedToParent) { this.containerContext = containerContext; this.delegateCacheProvider = delegateCacheProvider; this.parentScope = parent; this.Name = name; this.attachedToParent = attachedToParent; this.delegateCache = name == null ? delegateCacheProvider.DefaultCache : delegateCacheProvider.GetNamedCache(name); } public object GetOrAddScopedObject(int key, Func factory, IRequestContext requestContext, Type serviceType) { var item = this.scopedInstances.GetOrDefault(key); if (item != null) return item.Evaluate(this, requestContext, factory, serviceType); var evaluator = new ScopedEvaluator(); return Swap.SwapValue(ref this.scopedInstances, (t1, t2, _, _, items) => items.AddOrUpdate(t1, t2), key, evaluator, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder) ? evaluator.Evaluate(this, requestContext, factory, serviceType) : this.scopedInstances.GetOrDefault(key)!.Evaluate(this, requestContext, factory, serviceType); } public void InvalidateDelegateCache() { this.ThrowIfDisposed(); this.delegateCacheProvider.DefaultCache.ServiceDelegates = this.delegateCache.ServiceDelegates = ImmutableTree>.Empty; this.delegateCacheProvider.DefaultCache.RequestContextAwareDelegates = this.delegateCache.RequestContextAwareDelegates = ImmutableTree>.Empty; this.delegateCacheProvider.NamedCache = ImmutableTree.Empty; } public IEnumerable GetActiveScopeNames() { this.ThrowIfDisposed(); IResolutionScope? current = this; while (current != null) { if (current.Name != null) yield return current.Name; current = current.ParentScope; } } } ================================================ FILE: src/StashboxContainer.CollectionRegistrator.cs ================================================ using Stashbox.Registration.Fluent; using Stashbox.Utils; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Stashbox; public partial class StashboxContainer { /// public IStashboxContainer RegisterTypesAs(Type typeFrom, IEnumerable types, Func? selector = null, Action? configurator = null) { Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(types, nameof(types)); types = selector != null ? types.Where(selector).Where(t => t.IsResolvableType()) : types.Where(t => t.IsResolvableType()); foreach (var type in types) { if (type.ImplementsWithoutGenericCheck(typeFrom)) { this.RegisterTypeAs(typeFrom, type, configurator); continue; } if (!typeFrom.IsGenericTypeDefinition) continue; var serviceTypes = type.GetRegisterableBaseTypes().Concat(type.GetRegisterableInterfaceTypes()) .Where(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeFrom); foreach (var service in serviceTypes) this.RegisterTypeAs(type.IsGenericTypeDefinition ? typeFrom : service, type, configurator); } return this; } /// public IStashboxContainer RegisterTypes(IEnumerable types, Func? selector = null, Func? serviceTypeSelector = null, bool registerSelf = true, Action? configurator = null) { Shield.EnsureNotNull(types, nameof(types)); types = selector != null ? types.Where(t => t.IsResolvableType()).Where(selector) : types.Where(t => t.IsResolvableType()); foreach (var type in types) { var serviceTypes = type.GetRegisterableBaseTypes().Concat(type.GetRegisterableInterfaceTypes()); if (serviceTypeSelector != null) serviceTypes = serviceTypes.Where(t => serviceTypeSelector(type, t)); if (type.IsGenericTypeDefinition) serviceTypes = serviceTypes.Where(t => { if (!t.IsGenericType) return false; return type.GetGenericArguments().Length == t.GetGenericArguments().Length; }).Select(t => t.GetGenericTypeDefinition()); var finalServiceTypes = serviceTypes.ToArray(); var serviceType = finalServiceTypes.Length > 0 ? finalServiceTypes[0] : type; if (finalServiceTypes.Length == 0 && !registerSelf) continue; var configuration = new RegistrationConfigurator(serviceType, type, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); for (var i = 1; i < finalServiceTypes.Length; i++) { configuration.AsServiceAlso(finalServiceTypes[i]); } if (registerSelf) configuration.AsServiceAlso(type); configurator?.Invoke(configuration); this.RegisterInternal(configuration.ServiceType, configuration); } return this; } /// public IStashboxContainer ComposeBy(Type compositionRootType, params object[] compositionRootArguments) { this.ThrowIfDisposed(); Shield.EnsureNotNull(compositionRootType, nameof(compositionRootType)); Shield.EnsureTrue(compositionRootType.IsCompositionRoot(), $"The given type {compositionRootType} doesn't implement ICompositionRoot."); var compositionRoot = (ICompositionRoot)this.Activate(compositionRootType, compositionRootArguments); compositionRoot.Compose(this); return this; } /// public IStashboxContainer ComposeBy(ICompositionRoot compositionRoot) { this.ThrowIfDisposed(); Shield.EnsureNotNull(compositionRoot, nameof(compositionRoot)); compositionRoot.Compose(this); return this; } private void RegisterTypeAs(Type typeFrom, Type type, Action? configurator) { _ = configurator switch { null => this.Register(typeFrom, type), _ => this.Register(typeFrom, type, configurator) }; } } ================================================ FILE: src/StashboxContainer.FuncRegistrator.cs ================================================ using Stashbox.Registration; using System; using System.Collections.Generic; using Stashbox.Utils; namespace Stashbox; public partial class StashboxContainer { /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => this.RegisterFuncInternal(factory, TypeCache>.Type, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => this.RegisterFuncInternal(factory, TypeCache>.Type, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => this.RegisterFuncInternal(factory, TypeCache>.Type, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => this.RegisterFuncInternal(factory, TypeCache>.Type, name); /// public IStashboxContainer RegisterFunc(Func factory, object? name = null) => this.RegisterFuncInternal(factory, TypeCache>.Type, name); private IStashboxContainer RegisterFuncInternal(Delegate factory, Type factoryType, object? name) { this.ThrowIfDisposed(); var registration = new ServiceRegistration(factoryType, name, this.ContainerContext.ContainerConfiguration.DefaultLifetime, false, new Dictionary { { RegistrationOption.RegistrationTypeOptions, factory } }); this.ContainerContext.RegistrationRepository.AddOrUpdateRegistration(registration, factoryType); return this; } } ================================================ FILE: src/StashboxContainer.ReMapper.cs ================================================ using Stashbox.Registration; using Stashbox.Registration.Fluent; using System; using Stashbox.Utils; namespace Stashbox; public partial class StashboxContainer { /// public IStashboxContainer ReMap(Action>? configurator = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); var typeFrom = TypeCache.Type; var registrationConfigurator = new RegistrationConfigurator(typeFrom, TypeCache.Type, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator?.Invoke(registrationConfigurator); return this.ReMapInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer ReMap(Type typeTo, Action>? configurator = null) where TFrom : class { this.ThrowIfDisposed(); var typeFrom = TypeCache.Type; var registrationConfigurator = new RegistrationConfigurator(TypeCache.Type, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator?.Invoke(registrationConfigurator); registrationConfigurator.ValidateTypeMap(); return this.ReMapInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer ReMap(Type typeFrom, Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); var registrationConfigurator = new RegistrationConfigurator(typeFrom, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator?.Invoke(registrationConfigurator); registrationConfigurator.ValidateTypeMap(); return this.ReMapInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer ReMap(Action>? configurator = null) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; var registrationConfigurator = new RegistrationConfigurator(type, type, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator?.Invoke(registrationConfigurator); registrationConfigurator.ValidateImplementationIsResolvable(); return this.ReMapInternal(type, registrationConfigurator); } /// public IStashboxContainer ReMapDecorator(Type typeFrom, Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); var decoratorConfigurator = new DecoratorConfigurator(typeFrom, typeTo); configurator?.Invoke(decoratorConfigurator); decoratorConfigurator.ValidateTypeMap(); return this.ReMapInternal(typeFrom, decoratorConfigurator); } /// public IStashboxContainer ReMapDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); var typeFrom = TypeCache.Type; var decoratorConfigurator = new DecoratorConfigurator(typeFrom, TypeCache.Type); configurator?.Invoke(decoratorConfigurator); return this.ReMapInternal(typeFrom, decoratorConfigurator); } /// public IStashboxContainer ReMapDecorator(Type typeTo, Action>? configurator = null) where TFrom : class { this.ThrowIfDisposed(); var typeFrom = TypeCache.Type; var decoratorConfigurator = new DecoratorConfigurator(typeFrom, typeTo); configurator?.Invoke(decoratorConfigurator); decoratorConfigurator.ValidateTypeMap(); return this.ReMapInternal(typeFrom, decoratorConfigurator); } private IStashboxContainer ReMapInternal(Type serviceType, ServiceRegistration serviceRegistration) { ServiceRegistrator.ReMap( this.ContainerContext, serviceRegistration, serviceType); return this; } } ================================================ FILE: src/StashboxContainer.Registrator.cs ================================================ using Stashbox.Lifetime; using Stashbox.Registration; using Stashbox.Registration.Fluent; using Stashbox.Utils; using System; using System.Collections.Generic; namespace Stashbox; public partial class StashboxContainer { /// public IStashboxContainer Register(Action> configurator) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); var typeFrom = TypeCache.Type; var typeTo = TypeCache.Type; var registrationConfigurator = new RegistrationConfigurator(typeFrom, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator(registrationConfigurator); return this.RegisterInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer Register(object? name = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); return this.RegisterInternal(TypeCache.Type, TypeCache.Type, name); } /// public IStashboxContainer Register(Type typeTo, Action>? configurator = null) where TFrom : class { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeTo, nameof(typeTo)); var typeFrom = TypeCache.Type; if (configurator == null) { Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo); } var registrationConfigurator = new RegistrationConfigurator(typeFrom, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator(registrationConfigurator); registrationConfigurator.ValidateTypeMap(); return this.RegisterInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer Register(Type typeFrom, Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(typeTo, nameof(typeTo)); if (configurator == null) { Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo); } var registrationConfigurator = new RegistrationConfigurator(typeFrom, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator(registrationConfigurator); registrationConfigurator.ValidateTypeMap(); return this.RegisterInternal(typeFrom, registrationConfigurator); } /// public IStashboxContainer Register(Action> configurator) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; var registrationConfigurator = new RegistrationConfigurator(type, type, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator(registrationConfigurator); registrationConfigurator.ValidateImplementationIsResolvable(); return this.RegisterInternal(type, registrationConfigurator); } /// public IStashboxContainer Register(object? name = null) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; Shield.EnsureIsResolvable(type); return this.RegisterInternal(type, type, name); } /// public IStashboxContainer Register(Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeTo, nameof(typeTo)); if (configurator == null) { Shield.EnsureIsResolvable(typeTo); return this.RegisterInternal(typeTo, typeTo); } var registrationConfigurator = new RegistrationConfigurator(typeTo, typeTo, this.containerConfigurator.ContainerConfiguration.DefaultLifetime); configurator(registrationConfigurator); registrationConfigurator.ValidateImplementationIsResolvable(); return this.RegisterInternal(typeTo, registrationConfigurator); } /// public IStashboxContainer RegisterSingleton(object? name = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); return this.RegisterInternal(TypeCache.Type, TypeCache.Type, name, lifetime: Lifetimes.Singleton); } /// public IStashboxContainer RegisterSingleton(object? name = null) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; Shield.EnsureIsResolvable(type); return this.RegisterInternal(type, type, name, lifetime: Lifetimes.Singleton); } /// public IStashboxContainer RegisterSingleton(Type typeFrom, Type typeTo, object? name = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(typeTo, nameof(typeTo)); Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo, name, lifetime: Lifetimes.Singleton); } /// public IStashboxContainer RegisterScoped(object? name = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); return this.RegisterInternal(TypeCache.Type, TypeCache.Type, name, lifetime: Lifetimes.Scoped); } /// public IStashboxContainer RegisterScoped(Type typeFrom, Type typeTo, object? name = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(typeTo, nameof(typeTo)); Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo, name, lifetime: Lifetimes.Scoped); } /// public IStashboxContainer RegisterScoped(object? name = null) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; Shield.EnsureIsResolvable(type); return this.RegisterInternal(type, type, name, lifetime: Lifetimes.Scoped); } /// public IStashboxContainer RegisterInstance(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class { this.ThrowIfDisposed(); Shield.EnsureNotNull(instance, nameof(instance)); return this.RegisterInstanceInternal(TypeCache.Type, instance.GetType(), instance, false, withoutDisposalTracking, name, finalizerDelegate != null ? o => finalizerDelegate((TInstance)o) : null); } /// public IStashboxContainer RegisterInstance(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false) { this.ThrowIfDisposed(); Shield.EnsureNotNull(instance, nameof(instance)); Shield.EnsureNotNull(serviceType, nameof(serviceType)); return this.RegisterInstanceInternal(serviceType, instance.GetType(), instance, false, withoutDisposalTracking, name); } /// public IStashboxContainer WireUp(TInstance instance, object? name = null, bool withoutDisposalTracking = false, Action? finalizerDelegate = null) where TInstance : class { this.ThrowIfDisposed(); Shield.EnsureNotNull(instance, nameof(instance)); return this.RegisterInstanceInternal(TypeCache.Type, instance.GetType(), instance, true, withoutDisposalTracking, name, finalizerDelegate != null ? o => finalizerDelegate((TInstance)o) : null); } /// public IStashboxContainer WireUp(object instance, Type serviceType, object? name = null, bool withoutDisposalTracking = false) { this.ThrowIfDisposed(); Shield.EnsureNotNull(instance, nameof(instance)); Shield.EnsureNotNull(serviceType, nameof(serviceType)); return this.RegisterInstanceInternal(serviceType, instance.GetType(), instance, true, withoutDisposalTracking, name); } /// public IStashboxContainer RegisterDecorator(Type typeFrom, Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); Shield.EnsureNotNull(typeTo, nameof(typeTo)); if (configurator == null) { Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo, lifetime: Lifetimes.Empty, isDecorator: true); } var decoratorConfigurator = new DecoratorConfigurator(typeFrom, typeTo); configurator(decoratorConfigurator); decoratorConfigurator.ValidateTypeMap(); return this.RegisterInternal(typeFrom, decoratorConfigurator); } /// public IStashboxContainer RegisterDecorator(Action>? configurator = null) where TFrom : class where TTo : class, TFrom { this.ThrowIfDisposed(); if (configurator == null) return this.RegisterInternal(TypeCache.Type, TypeCache.Type, lifetime: Lifetimes.Empty, isDecorator: true); var typeFrom = TypeCache.Type; var typeTo = TypeCache.Type; var decoratorConfigurator = new DecoratorConfigurator(typeFrom, typeTo); configurator(decoratorConfigurator); return this.RegisterInternal(typeFrom, decoratorConfigurator); } /// public IStashboxContainer RegisterDecorator(Type typeTo, Action? configurator = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeTo, nameof(typeTo)); var decoratorConfigurator = new DecoratorConfigurator(typeTo, typeTo); configurator?.Invoke(decoratorConfigurator); decoratorConfigurator .AsImplementedTypes() .ValidateTypeMap(); return this.RegisterInternal(typeTo, decoratorConfigurator); } /// public IStashboxContainer RegisterDecorator(Action>? configurator = null) where TTo : class { this.ThrowIfDisposed(); var type = TypeCache.Type; var decoratorConfigurator = new DecoratorConfigurator(type, type); configurator?.Invoke(decoratorConfigurator); decoratorConfigurator .AsImplementedTypes() .ValidateImplementationIsResolvable(); return this.RegisterInternal(type, decoratorConfigurator); } /// public IStashboxContainer RegisterDecorator(Type typeTo, Action>? configurator = null) where TFrom : class { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeTo, nameof(typeTo)); var typeFrom = TypeCache.Type; if (configurator == null) { Shield.EnsureTypeMapIsValid(typeFrom, typeTo); return this.RegisterInternal(typeFrom, typeTo, lifetime: Lifetimes.Empty, isDecorator: true); } var decoratorConfigurator = new DecoratorConfigurator(typeFrom, typeTo); configurator(decoratorConfigurator); decoratorConfigurator.ValidateTypeMap(); return this.RegisterInternal(typeFrom, decoratorConfigurator); } private IStashboxContainer RegisterInternal(Type serviceType, Type implementationType, object? name = null, LifetimeDescriptor? lifetime = null, bool isDecorator = false) { ServiceRegistrator.Register( this.ContainerContext, new ServiceRegistration(implementationType, name, lifetime ?? this.ContainerContext.ContainerConfiguration.DefaultLifetime, isDecorator), serviceType); return this; } private IStashboxContainer RegisterInternal(Type serviceType, ServiceRegistration serviceRegistration) { ServiceRegistrator.Register( this.ContainerContext, serviceRegistration, serviceType); return this; } private IStashboxContainer RegisterInstanceInternal(Type serviceType, Type implementationType, object instance, bool isWireUp, bool withoutDisposalTracking, object? name, Action? finalizer = null) { ServiceRegistrator.Register( this.ContainerContext, new ServiceRegistration(implementationType, name, Lifetimes.Empty, false, new Dictionary { { RegistrationOption.Finalizer, finalizer }, { RegistrationOption.IsLifetimeExternallyOwned, withoutDisposalTracking }, { RegistrationOption.RegistrationTypeOptions, new InstanceOptions(instance, isWireUp) } }), serviceType); return this; } } ================================================ FILE: src/StashboxContainer.Resolver.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; namespace Stashbox; public partial class StashboxContainer { /// public object Resolve(Type typeFrom) => this.rootScope.Resolve(typeFrom); /// public object Resolve(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => this.rootScope.Resolve(typeFrom, name, dependencyOverrides, resolutionBehavior); /// public object? ResolveOrDefault(Type typeFrom) => this.rootScope.ResolveOrDefault(typeFrom); /// public object? ResolveOrDefault(Type typeFrom, object? name, object[]? dependencyOverrides, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => this.rootScope.ResolveOrDefault(typeFrom, name, dependencyOverrides, resolutionBehavior); /// public object? GetService(Type serviceType) => this.rootScope.ResolveOrDefault(serviceType); /// public Delegate ResolveFactory(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) => this.rootScope.ResolveFactory(typeFrom, name, resolutionBehavior, parameterTypes); /// public Delegate? ResolveFactoryOrDefault(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default, params Type[] parameterTypes) => this.rootScope.ResolveFactoryOrDefault(typeFrom, name, resolutionBehavior, parameterTypes); /// public TTo BuildUp(TTo instance, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) where TTo : class => this.rootScope.BuildUp(instance, resolutionBehavior); /// public object Activate(Type type, ResolutionBehavior resolutionBehavior, params object[] arguments) => this.rootScope.Activate(type, resolutionBehavior, arguments); /// public bool CanResolve(Type typeFrom, object? name = null, ResolutionBehavior resolutionBehavior = ResolutionBehavior.Default) => this.rootScope.CanResolve(typeFrom, name, resolutionBehavior); /// public ValueTask InvokeAsyncInitializers(CancellationToken token = default) => this.rootScope.InvokeAsyncInitializers(token); /// public IDependencyResolver BeginScope(object? name = null, bool attachToParent = false) => this.rootScope.BeginScope(name, attachToParent); /// public void PutInstanceInScope(Type typeFrom, object instance, bool withoutDisposalTracking = false, object? name = null) => this.rootScope.PutInstanceInScope(typeFrom, instance, withoutDisposalTracking, name); /// public IEnumerable GetDelegateCacheEntries() => this.rootScope.GetDelegateCacheEntries(); } ================================================ FILE: src/StashboxContainer.cs ================================================ using Stashbox.Configuration; using Stashbox.Registration; using Stashbox.Resolution; using Stashbox.Utils; using Stashbox.Utils.Data; using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Stashbox.Utils.Data.Immutable; namespace Stashbox; /// /// Represents the Stashbox dependency injection container. /// public sealed partial class StashboxContainer : IStashboxContainer { private sealed class ChildContainerStore { public ImmutableTree ChildContainers = ImmutableTree.Empty; } private static int globalContainerId = int.MinValue; private int disposed; private readonly ChildContainerStore childContainerStore; private readonly ChildContainerStore? directParentChildContainerStore; private readonly ContainerConfigurator containerConfigurator; private readonly ResolutionScope rootScope; private readonly object containerId; private readonly bool shouldDisposeWithParent; /// /// Constructs a . /// public StashboxContainer(Action? config = null) : this(null, new ResolutionStrategy(), new ContainerConfigurator(), ReserveContainerId(), false, config) { } private StashboxContainer(StashboxContainer? parentContainer, IResolutionStrategy resolutionStrategy, ContainerConfigurator containerConfigurator, object containerId, bool shouldDisposeWithParent, Action? config = null) { this.childContainerStore = new ChildContainerStore(); this.directParentChildContainerStore = parentContainer?.childContainerStore; this.ContainerContext = new ContainerContext(parentContainer?.ContainerContext, resolutionStrategy, containerConfigurator.ContainerConfiguration); this.rootScope = (ResolutionScope)this.ContainerContext.RootScope; this.containerConfigurator = containerConfigurator; this.containerId = containerId; this.shouldDisposeWithParent = shouldDisposeWithParent; config?.Invoke(this.containerConfigurator); } /// public IContainerContext ContainerContext { get; } /// public IEnumerable> ChildContainers => this.childContainerStore.ChildContainers.Walk(); /// public IStashboxContainer RegisterResolver(IResolver resolver) { this.ThrowIfDisposed(); Shield.EnsureNotNull(resolver, nameof(resolver)); this.ContainerContext.ResolutionStrategy.RegisterResolver(resolver); return this; } /// public bool IsRegistered(object? name = null) => this.IsRegistered(TypeCache.Type, name); /// public bool IsRegistered(Type typeFrom, object? name = null) { this.ThrowIfDisposed(); Shield.EnsureNotNull(typeFrom, nameof(typeFrom)); return this.ContainerContext.RegistrationRepository.ContainsRegistration(typeFrom, name, false); } /// public void Validate() { this.ThrowIfDisposed(); var exceptions = new ExpandableArray(); foreach (var serviceRegistration in this.ContainerContext.RegistrationRepository .GetRegistrationMappings() .Where(reg => !reg.Key.IsOpenGenericType())) { try { this.ContainerContext.ResolutionStrategy.BuildExpressionForRegistration(serviceRegistration.Value, ResolutionContext.BeginValidationContext(this.ContainerContext, ResolutionBehavior.Default), new TypeInformation(serviceRegistration.Key, serviceRegistration.Value.Name)); } catch (Exception ex) { exceptions.Add(ex); } } foreach (var child in this.childContainerStore.ChildContainers.Walk()) { try { child.Value.Validate(); } catch (AggregateException ex) { exceptions.Add(new AggregateException( $"Child container validation failed for '{child.Key}'. See the inner exceptions for details.", ex.InnerExceptions)); } } if (exceptions.Length > 0) throw new AggregateException("Container validation failed. See the inner exceptions for details.", exceptions); } /// public IStashboxContainer CreateChildContainer(Action? config = null, bool attachToParent = true) => this.CreateChildContainer(ReserveContainerId(), config == null ? null : cont => cont.Configure(config), attachToParent); /// public IStashboxContainer CreateChildContainer(object identifier, Action? config = null, bool attachToParent = true) { this.ThrowIfDisposed(); var child = new StashboxContainer(this, this.ContainerContext.ResolutionStrategy, new ContainerConfigurator(this.ContainerContext.ContainerConfiguration.Clone()), identifier, attachToParent); config?.Invoke(child); Swap.SwapValue(ref this.childContainerStore.ChildContainers, (t1, t2, _, _, childStore) => childStore.AddOrUpdate(t1, t2, false, false), identifier, child, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); return child; } /// public IStashboxContainer? GetChildContainer(object identifier) => this.childContainerStore.ChildContainers.GetOrDefaultByValue(identifier); /// public IStashboxContainer Configure(Action config) { this.ThrowIfDisposed(); Shield.EnsureNotNull(config, "The config parameter cannot be null!"); config.Invoke(this.containerConfigurator); this.containerConfigurator.ContainerConfiguration.ConfigurationChangedEvent? .Invoke(this.containerConfigurator.ContainerConfiguration); this.ContainerContext.RootScope.InvalidateDelegateCache(); return this; } /// public IEnumerable> GetRegistrationMappings() { this.ThrowIfDisposed(); return this.ContainerContext.RegistrationRepository.GetRegistrationMappings(); } /// public IEnumerable GetRegistrationDiagnostics() { this.ThrowIfDisposed(); foreach (var reg in this.ContainerContext.RegistrationRepository.GetRegistrationMappings()) yield return new RegistrationDiagnosticsInfo(reg.Key, reg.Value.ImplementationType, reg.Value.Name); } private void ThrowIfDisposed([CallerMemberName] string caller = "") { if (this.disposed == 1) Shield.ThrowDisposedException(this.GetType().FullName, caller); } /// public void Dispose() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) return; this.RemoveSelfFromParentChildContainers(); this.DisposeChildContainers(); this.rootScope.Dispose(); } #if HAS_ASYNC_DISPOSABLE /// public async ValueTask DisposeAsync() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) return; this.RemoveSelfFromParentChildContainers(); await this.DisposeChildContainersAsync().ConfigureAwait(false); await this.rootScope.DisposeAsync().ConfigureAwait(false); } private async ValueTask DisposeChildContainersAsync() { foreach (var childContainer in this.childContainerStore.ChildContainers.Walk()) { if (childContainer.Value is StashboxContainer { shouldDisposeWithParent: true } container) await container.DisposeAsync().ConfigureAwait(false); } } #endif private void DisposeChildContainers() { foreach (var childContainer in this.childContainerStore.ChildContainers.Walk()) { if (childContainer.Value is StashboxContainer { shouldDisposeWithParent: true } container) container.Dispose(); } } private void RemoveSelfFromParentChildContainers() { if (this.directParentChildContainerStore != null) Swap.SwapValue(ref this.directParentChildContainerStore.ChildContainers, (t1, _, _, _, childRepo) => childRepo.Remove(t1, false), this.containerId, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder); } private static int ReserveContainerId() => Interlocked.Increment(ref globalContainerId); } ================================================ FILE: src/Utils/Constants.cs ================================================ using Stashbox.Resolution; using System; using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; namespace Stashbox.Utils; internal static class Constants { public static readonly ParameterExpression ResolutionScopeParameter = TypeCache.Type.AsParameter("scope"); public static readonly ParameterExpression RequestContextParameter = TypeCache.Type.AsParameter("request"); public static readonly MethodInfo AddDisposalMethod = TypeCache.Type.GetMethod(nameof(IResolutionScope.AddDisposableTracking))!; public static readonly MethodInfo AddRequestContextAwareDisposalMethod = TypeCache.Type.GetMethod(nameof(IResolutionScope.AddRequestContextAwareDisposableTracking))!; public static readonly MethodInfo GetOrAddScopedObjectMethod = TypeCache.Type.GetMethod(nameof(IResolutionScope.GetOrAddScopedObject))!; public static readonly MethodInfo AddWithFinalizerMethod = TypeCache.Type.GetMethod(nameof(IResolutionScope.AddWithFinalizer))!; public static readonly MethodInfo AddWithAsyncInitializerMethod = TypeCache.Type.GetMethod(nameof(IResolutionScope.AddWithAsyncInitializer))!; public static readonly MethodInfo GetOrAddInstanceMethod = TypeCache.Type.GetMethod(nameof(IInternalRequestContext.GetOrAddInstance))!; public static readonly MethodInfo ResolveMethod = TypeCache.Type.GetMethod(nameof(IDependencyResolver.Resolve), [ TypeCache.Type, TypeCache.Type, TypeCache.Type, TypeCache.Type ])!; public static readonly MethodInfo BeginScopeMethod = TypeCache.Type.GetMethod(nameof(IDependencyResolver.BeginScope))!; public const MethodImplOptions Inline = (MethodImplOptions)256; public const byte DelegatePlaceholder = 0; public const ResolutionBehavior DefaultResolutionBehavior = ResolutionBehavior.Default; public const int DefaultResolutionBehaviorInt = (int)DefaultResolutionBehavior; } ================================================ FILE: src/Utils/Data/ExpandableArray.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; namespace Stashbox.Utils.Data; internal class ExpandableArray : IEnumerable { private const int InitialSize = 8; public int Length; protected TItem[]? Repository; public ExpandableArray() { } public ExpandableArray(ExpandableArray initial) { this.Repository = initial.AsArray(); this.Length = this.Repository.Length; } public ExpandableArray(IEnumerable initial) : this(initial.CastToArray()) { } public ExpandableArray(TItem[] initial) { this.Repository = initial; this.Length = this.Repository.Length; } public void Add(TItem item) { var index = this.EnsureSize(); this.Repository![index] = item; } public void AddOrKeep(TItem item) { if (this.ContainsReference(item)) return; var index = this.EnsureSize(); this.Repository![index] = item; } public void AddRange(IEnumerable items) => this.AddRange(items.CastToArray()); public void AddRange(TItem[] items) { var index = this.EnsureSize(items.Length); Array.Copy(items, 0, this.Repository!, index, items.Length); } public TItem this[int i] => this.Repository![i]; public TItem[] AsArray() { if (this.Length == 0) return TypeCache.EmptyArray(); Array.Resize(ref this.Repository, this.Length); return this.Repository; } public int IndexOf(TItem element) { var length = this.Length; if (length == 1) return ReferenceEquals(this.Repository![0], element) ? 0 : -1; for (var i = 0; i < length; i++) if (ReferenceEquals(this.Repository![i], element)) return i; return -1; } public bool ContainsReference(TItem element) { var length = this.Length; if (length == 1) return ReferenceEquals(this.Repository![0], element); for (var i = 0; i < length; i++) if (ReferenceEquals(this.Repository![i], element)) return true; return false; } public bool Contains(TItem element) { var length = this.Length; if (length == 1) return Equals(this.Repository![0], element); for (var i = 0; i < length; i++) if (Equals(this.Repository![i], element)) return true; return false; } protected int EnsureSize(int increaseAmount = 1) { if (this.Length == 0) this.Repository = new TItem[increaseAmount > InitialSize ? increaseAmount : InitialSize]; this.Length += increaseAmount; if (this.Repository!.Length >= this.Length) return this.Length - increaseAmount; var newSize = this.Repository.Length * 2; var desiredSize = this.Length > newSize ? this.Length : newSize; Array.Resize(ref this.Repository, desiredSize); return this.Length - increaseAmount; } public TItem First() => this.Repository![0]; public IEnumerator GetEnumerator() { var length = this.Length; for (var i = 0; i < length; i++) yield return this.Repository![i]; } IEnumerator IEnumerable.GetEnumerator() => this.GetEnumerator(); } internal class ExpandableArray : ExpandableArray> { public ExpandableArray() { } public ExpandableArray(ExpandableArray initial) : base(initial) { } [MethodImpl(Constants.Inline)] public TItem? GetOrDefaultByValue(TKey key) { var length = this.Length; for (var i = 0; i < length; i++) { ref readonly var item = ref this.Repository![i]; if (Equals(item.Key, key)) return item.Value; } return default; } [MethodImpl(Constants.Inline)] public TItem? GetOrDefaultByRef(TKey key) { var length = this.Length; for (var i = 0; i < length; i++) { ref readonly var item = ref this.Repository![i]; if (ReferenceEquals(item.Key, key)) return item.Value; } return default; } public void AddOrKeep(TKey item, TItem value) { if (this.ContainsReference(item)) return; var index = this.EnsureSize(); this.Repository![index] = new ReadOnlyKeyValue(item, value); } public void AddOrUpdate(TKey key, TItem value) { var index = this.IndexOf(key); if (index >= 0) { this.Repository![index] = new ReadOnlyKeyValue(key, value); return; } index = this.EnsureSize(); this.Repository![index] = new ReadOnlyKeyValue(key, value); } public int IndexAndValueOf(TKey key, out TItem? value) { value = default; var length = this.Length; for (var i = 0; i < length; i++) { ref readonly var item = ref Repository![i]; if (!ReferenceEquals(item.Key, key)) continue; value = item.Value; return i; } return -1; } public int IndexOf(TKey key) { var length = this.Length; if (length == 1) return ReferenceEquals(this.Repository![0].Key, key) ? 0 : -1; for (var i = 0; i < length; i++) { ref readonly var item = ref Repository![i]; if (ReferenceEquals(item.Key, key)) return i; } return -1; } public bool ContainsReference(TKey key) { var length = this.Length; if (length == 1) return ReferenceEquals(this.Repository![0].Key, key); for (var i = 0; i < length; i++) { ref readonly var item = ref Repository![i]; if (ReferenceEquals(item.Key, key)) return true; } return false; } } ================================================ FILE: src/Utils/Data/HashTree.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; namespace Stashbox.Utils.Data; [DebuggerTypeProxy(typeof(HashTreeDebugView<,>))] internal sealed class HashTree where TKey : class { private class Node { public readonly int StoredHash; public readonly TKey StoredKey; public TValue StoredValue; public Node? Left; public Node? Right; public int Height; public ExpandableArray? Collisions; public Node(TKey key, TValue value, int hash) { this.StoredValue = value; this.StoredKey = key; this.StoredHash = hash; this.Height = 1; } } private Node? root; public void Add(TKey key, TValue value) { this.root = Add(this.root, key, key.GetHashCode(), value); } [MethodImpl(Constants.Inline)] public TValue? GetOrDefault(TKey key) { if (this.root == null) return default; var node = root; var hash = key.GetHashCode(); while (node != null && node.StoredHash != hash) node = hash < node.StoredHash ? node.Left : node.Right; return node != null && Equals(key, node.StoredKey) ? node.StoredValue : node?.Collisions == null ? default : node.Collisions.GetOrDefaultByValue(key); } private static int CalculateHeight(Node node) { if (node is { Left: not null, Right: not null }) return 1 + (node.Left.Height > node.Right.Height ? node.Left.Height : node.Right.Height); if (node.Left == null && node.Right == null) return 1; return 1 + (node.Left?.Height ?? node.Right!.Height); } private static int GetBalance(Node node) { if (node is { Left: not null, Right: not null }) return node.Left.Height - node.Right.Height; if (node.Left == null && node.Right == null) return 0; return node.Left?.Height ?? node.Right!.Height * -1; } private static Node RotateLeft(Node node) { var current = node.Right; var left = current!.Left; current.Left = node; node.Right = left; current.Height = CalculateHeight(current); node.Height = CalculateHeight(node); return current; } private static Node RotateRight(Node node) { var current = node.Left; var right = current!.Right; current.Right = node; node.Left = right; current.Height = CalculateHeight(current); node.Height = CalculateHeight(node); return current; } private static Node Add(Node? node, TKey key, int hash, TValue value) { if (node == null) return new Node(key, value, hash); if (node.StoredHash == hash) { CheckCollisions(node, key, value); return node; } if (node.StoredHash > hash) node.Left = Add(node.Left, key, hash, value); else node.Right = Add(node.Right, key, hash, value); node.Height = CalculateHeight(node); var balance = GetBalance(node); switch (balance) { case >= 2 when GetBalance(node.Left!) == -1: node.Left = RotateLeft(node.Left!); node = RotateRight(node); break; case >= 2: node = RotateRight(node); break; case <= -2 when GetBalance(node.Right!) == 1: node.Right = RotateRight(node.Right!); node = RotateLeft(node); break; case <= -2: node = RotateLeft(node); break; } return node; } private static void CheckCollisions(Node node, TKey key, TValue value) { if (Equals(key, node.StoredKey)) node.StoredValue = value; else { node.Collisions ??= []; node.Collisions.Add(new ReadOnlyKeyValue(key, value)); } } public IEnumerable Walk() { if (this.root == null) yield break; switch (this.root.Height) { case 1: yield return this.root.StoredValue; break; case 2: if (this.root.Left != null) yield return this.root.Left.StoredValue; yield return this.root.StoredValue; if (this.root.Right != null) yield return this.root.Right.StoredValue; break; default: { var nodes = new Node[this.root.Height - 2]; var currentNode = this.root; var index = -1; while (true) { if (currentNode != null) { if (currentNode.Height == 2) { if (currentNode.Left != null) yield return currentNode.Left.StoredValue; yield return currentNode.StoredValue; if (currentNode.Right != null) yield return currentNode.Right.StoredValue; if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } else if (currentNode.Height == 1) { yield return currentNode.StoredValue; if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } else { nodes[++index] = currentNode; currentNode = currentNode.Left; } } else { if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } } break; } } } } internal class HashTreeDebugView where TKey : class { private readonly HashTree tree; public HashTreeDebugView(HashTree tree) { this.tree = tree; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public TValue[] Items => tree.Walk().ToArray(); } ================================================ FILE: src/Utils/Data/Immutable/ImmutableBucket.cs ================================================ using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Runtime.CompilerServices; namespace Stashbox.Utils.Data.Immutable; [DebuggerTypeProxy(typeof(ImmutableBucketDebugView<>))] internal class ImmutableBucket { public static readonly ImmutableBucket Empty = new(TypeCache.EmptyArray()); public readonly int Length; public readonly TValue[] Repository; public TValue this[int i] => this.Repository[i]; public ImmutableBucket(TValue value) : this([value]) { } public ImmutableBucket(TValue[] repository) { this.Repository = repository; this.Length = repository.Length; } internal ImmutableBucket Add(TValue value) { if (this.Length == 0) return new ImmutableBucket([value]); var newRepository = new TValue[this.Length + 1]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[this.Length] = value; return new ImmutableBucket(newRepository); } internal ImmutableBucket Insert(int index, TValue value) { if (index > this.Length - 1) throw new IndexOutOfRangeException(); if (this.Length == 0) return new ImmutableBucket([value]); var newRepository = new TValue[this.Length + 1]; Array.Copy(this.Repository, newRepository, index); newRepository[index] = value; Array.Copy(this.Repository, index, newRepository, index + 1, this.Length - index); return new ImmutableBucket(newRepository); } internal ImmutableBucket ReplaceAt(int index, TValue value) { if (index > this.Length - 1) throw new IndexOutOfRangeException(); if (this.Length == 0) return new ImmutableBucket([value]); var newRepository = new TValue[this.Length]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[index] = value; return new ImmutableBucket(newRepository); } public ImmutableBucket AddIfNotExist(TValue value) { if (this.Length == 0) return this; var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (ReferenceEquals(item, value)) break; count--; } return count != -1 ? this : this.Add(value); } public ImmutableBucket Remove(TValue value) { if (this.Length == 0) return this; var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (ReferenceEquals(item, value)) break; count--; } if (count == -1) return this; var newRepository = new TValue[this.Length - 1]; Array.Copy(this.Repository, newRepository, count); Array.Copy(this.Repository, count + 1, newRepository, count, this.Length - 1 - count); return new ImmutableBucket(newRepository); } } internal class ImmutableBucketDebugView { private readonly ImmutableBucket bucket; public ImmutableBucketDebugView(ImmutableBucket bucket) { this.bucket = bucket; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public TValue[] Items => bucket.Repository; } internal class ImmutableBucket : IEnumerable { public static readonly ImmutableBucket Empty = new(TypeCache.EmptyArray>()); public readonly int Length; public readonly ReadOnlyKeyValue[] Repository; private ImmutableBucket(ReadOnlyKeyValue[] repository) { this.Repository = repository; this.Length = repository.Length; } public ImmutableBucket Add(TKey key, TValue value) { if (this.Length == 0) return new ImmutableBucket([new ReadOnlyKeyValue(key, value)]); var newRepository = new ReadOnlyKeyValue[this.Length + 1]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[this.Length] = new ReadOnlyKeyValue(key, value); return new ImmutableBucket(newRepository); } public ImmutableBucket AddOrUpdate(TKey key, TValue value, bool byRef) { if (this.Length == 0) return new ImmutableBucket([new ReadOnlyKeyValue(key, value)]); var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (byRef && ReferenceEquals(item.Key, key) || !byRef && Equals(item.Key, key)) break; count--; } if (count == -1) return this.Add(key, value); var newRepository = new ReadOnlyKeyValue[this.Length]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[count] = new ReadOnlyKeyValue(key, value); return new ImmutableBucket(newRepository); } public ImmutableBucket ReplaceIfExists(TKey key, Func updateDelegate, bool byRef) { if (this.Length == 0) return this; var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (byRef && ReferenceEquals(item.Key, key) || !byRef && Equals(item.Key, key)) break; count--; } if (count == -1) return this; var newRepository = new ReadOnlyKeyValue[this.Length]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[count] = new ReadOnlyKeyValue(key, updateDelegate(newRepository[count].Value)); return new ImmutableBucket(newRepository); } public ImmutableBucket ReplaceIfExists(TKey key, TValue value, bool byRef, Func? update = null) { if (this.Length == 0) return this; var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (byRef && ReferenceEquals(item.Key, key) || !byRef && Equals(item.Key, key)) break; count--; } if (count == -1) return this; var old = this.Repository[count].Value; var @new = update == null ? value : update(old, value); if (ReferenceEquals(@new, old)) return this; var newRepository = new ReadOnlyKeyValue[this.Length]; Array.Copy(this.Repository, newRepository, this.Length); newRepository[count] = new ReadOnlyKeyValue(key, @new); return new ImmutableBucket(newRepository); } [MethodImpl(Constants.Inline)] public TValue? GetOrDefaultByValue(TKey key) { var length = this.Repository.Length; for (var i = 0; i < length; i++) { ref readonly var item = ref this.Repository[i]; if (Equals(item.Key, key)) return item.Value; } return default; } [MethodImpl(Constants.Inline)] public TValue? GetOrDefaultByRef(TKey key) { var length = this.Repository.Length; for (var i = 0; i < length; i++) { ref readonly var item = ref this.Repository[i]; if (ReferenceEquals(item.Key, key)) return item.Value; } return default; } public ImmutableBucket Remove(TKey key, bool byRef) { if (this.Length == 0) return this; var count = this.Length - 1; while (count >= 0) { ref readonly var item = ref this.Repository[count]; if (byRef && ReferenceEquals(item.Key, key) || !byRef && Equals(item.Key, key)) break; count--; } if (count == -1) return this; var newRepository = new ReadOnlyKeyValue[this.Length - 1]; Array.Copy(this.Repository, newRepository, count); Array.Copy(this.Repository, count + 1, newRepository, count, this.Length - 1 - count); return new ImmutableBucket(newRepository); } public ImmutableBucket RemoveFirst() { switch (this.Length) { case 0: return this; case 1: return new ImmutableBucket([]); } var newRepository = new ReadOnlyKeyValue[this.Length - 1]; Array.Copy(this.Repository, 1, newRepository, 0, this.Length - 1); return new ImmutableBucket(newRepository); } public IEnumerator GetEnumerator() { for (var i = 0; i < this.Length; i++) yield return this.Repository[i].Value; } IEnumerator IEnumerable.GetEnumerator() { return this.Repository.GetEnumerator(); } } ================================================ FILE: src/Utils/Data/Immutable/ImmutableLinkedList.cs ================================================ #nullable disable namespace Stashbox.Utils.Data.Immutable; internal class ImmutableLinkedList { public static readonly ImmutableLinkedList Empty = new(); public readonly TValue Value; public readonly ImmutableLinkedList Next; private ImmutableLinkedList(ImmutableLinkedList next, TValue value) { this.Value = value; this.Next = next; } private ImmutableLinkedList() { } public ImmutableLinkedList Add(TValue value) => new(this, value); } ================================================ FILE: src/Utils/Data/Immutable/ImmutableTree.cs ================================================ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; namespace Stashbox.Utils.Data.Immutable; [DebuggerTypeProxy(typeof(ImmutableTreeDebugView<>))] internal sealed class ImmutableTree { public static readonly ImmutableTree Empty = new(); private readonly int storedHash; private readonly TValue? storedValue; private readonly ImmutableTree? leftNode; private readonly ImmutableTree? rightNode; private readonly int height; public readonly bool IsEmpty = true; private ImmutableTree(int hash, TValue value, ImmutableTree left, ImmutableTree right) { this.storedHash = hash; this.leftNode = left; this.rightNode = right; this.storedValue = value; this.IsEmpty = false; this.height = 1 + (left.height > right.height ? left.height : right.height); } private ImmutableTree(int hash, TValue value) { this.storedHash = hash; this.leftNode = Empty; this.rightNode = Empty; this.storedValue = value; this.IsEmpty = false; this.height = 1; } private ImmutableTree() { } [MethodImpl(Constants.Inline)] public TValue? GetOrDefault(int key) { if (this.IsEmpty) return default; var node = this; while (!node!.IsEmpty && node.storedHash != key) node = key < node.storedHash ? node.leftNode : node.rightNode; return !node.IsEmpty ? node.storedValue : default; } public ImmutableTree AddOrUpdate(int key, TValue value, Func? updateDelegate = null) { if (this.IsEmpty) return new ImmutableTree(key, value); if (key == this.storedHash) return updateDelegate != null ? new ImmutableTree(key, updateDelegate(this.storedValue!, value), this.leftNode!, this.rightNode!) : this; return key < this.storedHash ? this.height == 1 ? new ImmutableTree(this.storedHash, this.storedValue!, new ImmutableTree(key, value), this.rightNode!) : Balance(this.storedHash, this.storedValue!, this.leftNode!.AddOrUpdate(key, value, updateDelegate), this.rightNode!) : this.height == 1 ? new ImmutableTree(this.storedHash, this.storedValue!, this.leftNode!, new ImmutableTree(key, value)) : Balance(this.storedHash, this.storedValue!, this.leftNode!, this.rightNode!.AddOrUpdate(key, value, updateDelegate)); } public ImmutableTree Remove(int key) { if (this.IsEmpty) return this; if (key != this.storedHash) return key < this.storedHash ? Balance(this.storedHash, this.storedValue!, this.leftNode!.Remove(key), this.rightNode!) : Balance(this.storedHash, this.storedValue!, this.leftNode!, this.rightNode!.Remove(key)); if (this.height == 1) return Empty; if (this.rightNode!.IsEmpty) return this.leftNode!; if (this.leftNode!.IsEmpty) return this.rightNode!; var next = this.rightNode; while (!next.leftNode!.IsEmpty) next = next.leftNode; return new ImmutableTree(next.storedHash, next.storedValue!, this.leftNode!, this.rightNode!.Remove(next.storedHash)); } private static ImmutableTree Balance(int hash, TValue value, ImmutableTree left, ImmutableTree right) { var balance = left.height - right.height; return balance switch { >= 2 => left.leftNode!.height - left.rightNode!.height == -1 ? RotateLeftRight(hash, value, left, right) : RotateRight(hash, value, left, right), <= -2 => right.leftNode!.height - right.rightNode!.height == 1 ? RotateRightLeft(hash, value, left, right) : RotateLeft(hash, value, left, right), _ => new ImmutableTree(hash, value, left, right) }; } private static ImmutableTree RotateRight(int hash, TValue value, ImmutableTree left, ImmutableTree right) { var r = new ImmutableTree(hash, value, left.rightNode!, right); return new ImmutableTree(left.storedHash, left.storedValue!, left.leftNode!, r); } private static ImmutableTree RotateLeft(int hash, TValue value, ImmutableTree left, ImmutableTree right) { var l = new ImmutableTree(hash, value, left, right.leftNode!); return new ImmutableTree(right.storedHash, right.storedValue!, l, right.rightNode!); } private static ImmutableTree RotateRightLeft(int hash, TValue value, ImmutableTree left, ImmutableTree right) { var l = new ImmutableTree(hash, value, left, right.leftNode!.leftNode!); var r = new ImmutableTree(right.storedHash, right.storedValue!, right.leftNode.rightNode!, right.rightNode!); return new ImmutableTree(right.leftNode.storedHash, right.leftNode.storedValue!, l, r); } private static ImmutableTree RotateLeftRight(int hash, TValue value, ImmutableTree left, ImmutableTree right) { var l = new ImmutableTree(left.storedHash, left.storedValue!, left.leftNode!, left.rightNode!.leftNode!); var r = new ImmutableTree(hash, value, left.rightNode.rightNode!, right); return new ImmutableTree(left.rightNode.storedHash, left.rightNode.storedValue!, l, r); } public override string ToString() => this.IsEmpty ? "empty" : $"{this.storedHash} : {this.storedValue}"; public IEnumerable> Walk() { if (this.IsEmpty) yield break; var nodes = new ImmutableTree[this.height]; var currentNode = this; var index = -1; while (!currentNode!.IsEmpty || index != -1) { if (!currentNode.IsEmpty) { nodes[++index] = currentNode; currentNode = currentNode.leftNode; } else { currentNode = nodes[index--]; yield return new ReadOnlyKeyValue(currentNode.storedHash, currentNode.storedValue!); currentNode = currentNode.rightNode; } } } } internal class ImmutableTreeDebugView { private readonly ImmutableTree tree; public ImmutableTreeDebugView(ImmutableTree tree) { this.tree = tree; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public ReadOnlyKeyValue[] Items => tree.Walk().ToArray(); } [DebuggerTypeProxy(typeof(ImmutableTreeDebugView<,>))] internal sealed class ImmutableTree where TKey : class { public static readonly ImmutableTree Empty = new(); private readonly int height; private readonly int storedHash; private readonly TKey? storedKey; private readonly TValue? storedValue; private readonly ImmutableTree? leftNode; private readonly ImmutableTree? rightNode; private readonly ImmutableBucket? collisions; public readonly bool IsEmpty = true; private ImmutableTree(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { this.collisions = collisions; this.storedKey = key; this.storedHash = hash; this.leftNode = left; this.rightNode = right; this.storedValue = value; this.IsEmpty = false; this.height = 1 + (left.height > right.height ? left.height : right.height); } private ImmutableTree() { } private ImmutableTree(int hash, TKey key, TValue value) { this.storedKey = key; this.storedHash = hash; this.leftNode = Empty; this.rightNode = Empty; this.storedValue = value; this.IsEmpty = false; this.height = 1; } public ImmutableTree AddOrUpdate(TKey key, TValue value, bool byRef, Func? updateDelegate = null) => this.AddOrUpdate(byRef ? RuntimeHelpers.GetHashCode(key) : key.GetHashCode(), key, value, byRef, updateDelegate, false); public ImmutableTree AddOrUpdate(TKey key, TValue value, bool byRef, bool forceUpdate) => this.AddOrUpdate(byRef ? RuntimeHelpers.GetHashCode(key) : key.GetHashCode(), key, value, byRef, null, forceUpdate); public ImmutableTree UpdateIfExists(TKey key, bool byRef, Func updateDelegate) => this.UpdateIfExists(byRef ? RuntimeHelpers.GetHashCode(key) : key.GetHashCode(), key, byRef, updateDelegate); public ImmutableTree UpdateIfExists(TKey key, TValue value, bool byRef) => this.UpdateIfExists(byRef ? RuntimeHelpers.GetHashCode(key) : key.GetHashCode(), key, byRef, value); [MethodImpl(Constants.Inline)] public TValue? GetOrDefaultByValue(TKey key) { if (this.IsEmpty) return default; var hash = key.GetHashCode(); var node = this; while (!node!.IsEmpty && node.storedHash != hash) node = hash < node.storedHash ? node.leftNode : node.rightNode; return !node.IsEmpty && Equals(key, node.storedKey) ? node.storedValue : node.collisions == null ? default : node.collisions.GetOrDefaultByValue(key); } [MethodImpl(Constants.Inline)] public TValue? GetOrDefaultByRef(TKey key) { if (this.IsEmpty) return default; var hash = RuntimeHelpers.GetHashCode(key); var node = this; while (!node!.IsEmpty && node.storedHash != hash) node = hash < node.storedHash ? node.leftNode : node.rightNode; return !node.IsEmpty && ReferenceEquals(key, node.storedKey) ? node.storedValue : node.collisions == null ? default : node.collisions.GetOrDefaultByRef(key); } public ImmutableTree Remove(TKey key, bool byRef) => this.Remove(byRef ? RuntimeHelpers.GetHashCode(key) : key.GetHashCode(), key, byRef); private ImmutableTree AddOrUpdate(int hash, TKey key, TValue value, bool byRef, Func? updateDelegate, bool forceUpdate) { if (this.IsEmpty) return new ImmutableTree(hash, key, value); if (hash == this.storedHash) return this.CheckCollision(hash, key, value, byRef, updateDelegate, forceUpdate); if (hash < this.storedHash) { if (this.height == 1) return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, new ImmutableTree(hash, key, value), this.rightNode!, this.collisions!); var left = this.leftNode!.AddOrUpdate(hash, key, value, byRef, updateDelegate, forceUpdate); return ReferenceEquals(left, this.leftNode) ? this : Balance(this.storedHash, this.storedKey!, this.storedValue!, left, this.rightNode!, this.collisions!); } if (this.height == 1) return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, new ImmutableTree(hash, key, value), this.collisions!); var right = this.rightNode!.AddOrUpdate(hash, key, value, byRef, updateDelegate, forceUpdate); return ReferenceEquals(right, this.rightNode) ? this : Balance(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, right, this.collisions!); } private ImmutableTree UpdateIfExists(int hash, TKey key, bool byRef, Func updateDelegate) { if (this.IsEmpty) return this; if (hash == this.storedHash) return this.ReplaceInCollisionsIfExist(hash, key, byRef, updateDelegate); if (hash < this.storedHash) { var left = this.leftNode!.UpdateIfExists(hash, key, byRef, updateDelegate); if (ReferenceEquals(left, this.leftNode)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, left, this.rightNode!, this.collisions!); } var right = this.rightNode!.UpdateIfExists(hash, key, byRef, updateDelegate); if (ReferenceEquals(right, this.rightNode)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, right, this.collisions!); } private ImmutableTree UpdateIfExists(int hash, TKey key, bool byRef, TValue value) { if (this.IsEmpty) return this; if (hash == this.storedHash) return this.ReplaceInCollisionsIfExist(hash, key, value, byRef); if (hash < this.storedHash) { var left = this.leftNode!.UpdateIfExists(hash, key, byRef, value); if (ReferenceEquals(left, this.leftNode)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, left, this.rightNode!, this.collisions!); } var right = this.rightNode!.UpdateIfExists(hash, key, byRef, value); if (ReferenceEquals(right, this.rightNode)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, right, this.collisions!); } private ImmutableTree Remove(int hash, TKey key, bool byRef) { if (this.IsEmpty) return this; if (hash != this.storedHash) return hash < this.storedHash ? Balance(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!.Remove(hash, key, byRef), this.rightNode!, this.collisions!) : Balance(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!.Remove(hash, key, byRef), this.collisions!); if (byRef && ReferenceEquals(key, this.storedKey) || !byRef && Equals(key, this.storedKey)) { if (this.collisions is { Length: > 0 }) { var first = this.collisions.Repository[0]; return new ImmutableTree(hash, first.Key, first.Value, this.leftNode!, this.rightNode!, this.collisions.RemoveFirst()); } if (this.height == 1) return Empty; if (this.rightNode!.IsEmpty) return this.leftNode!; if (this.leftNode!.IsEmpty) return this.rightNode!; var next = this.rightNode; while (!next.leftNode!.IsEmpty) next = next.leftNode; return new ImmutableTree(next.storedHash, next.storedKey!, next.storedValue!, this.leftNode!, this.rightNode!.Remove(next.storedHash, next.storedKey!, byRef), next.collisions!); } if (this.collisions != null) return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!, this.collisions.Remove(key, byRef)); return this; } private ImmutableTree CheckCollision(int hash, TKey key, TValue value, bool byRef, Func? updateDelegate, bool forceUpdate) { if (byRef && ReferenceEquals(key, this.storedKey) || !byRef && Equals(key, this.storedKey)) { if (forceUpdate) return new ImmutableTree(hash, key, value, this.leftNode!, this.rightNode!, this.collisions!); if (updateDelegate == null) return this; var newValue = updateDelegate(this.storedValue!, value); return ReferenceEquals(newValue, this.storedValue) ? this : new ImmutableTree(hash, key, newValue, this.leftNode!, this.rightNode!, this.collisions!); } if (this.collisions == null) return new ImmutableTree(hash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!, ImmutableBucket.Empty.Add(key, value)); var collision = byRef ? this.collisions.GetOrDefaultByRef(key) : this.collisions.GetOrDefaultByValue(key); var @new = collision == null || updateDelegate == null || forceUpdate ? value : updateDelegate(collision, value); if (ReferenceEquals(@new, collision)) return this; return new ImmutableTree(hash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!, this.collisions.AddOrUpdate(key, @new, byRef)); } private ImmutableTree ReplaceInCollisionsIfExist(int hash, TKey key, bool byRef, Func updateDelegate) { if (ReferenceEquals(key, this.storedKey)) return new ImmutableTree(hash, key, updateDelegate(this.storedValue!), this.leftNode!, this.rightNode!, this.collisions!); if (this.collisions == null) return this; var currentCollisions = this.collisions.ReplaceIfExists(key, updateDelegate, byRef); if (ReferenceEquals(currentCollisions, this.collisions)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!, currentCollisions); } private ImmutableTree ReplaceInCollisionsIfExist(int hash, TKey key, TValue value, bool byRef) { if (ReferenceEquals(key, this.storedKey)) return new ImmutableTree(hash, key, value, this.leftNode!, this.rightNode!, this.collisions!); if (this.collisions == null) return this; var currentCollisions = this.collisions.ReplaceIfExists(key, value, byRef); if (ReferenceEquals(currentCollisions, this.collisions)) return this; return new ImmutableTree(this.storedHash, this.storedKey!, this.storedValue!, this.leftNode!, this.rightNode!, currentCollisions); } private static ImmutableTree Balance(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { var balance = left.height - right.height; return balance switch { >= 2 => left.leftNode!.height - left.rightNode!.height == -1 ? RotateLeftRight(hash, key, value, left, right, collisions) : RotateRight(hash, key, value, left, right, collisions), <= -2 => right.leftNode!.height - right.rightNode!.height == 1 ? RotateRightLeft(hash, key, value, left, right, collisions) : RotateLeft(hash, key, value, left, right, collisions), _ => new ImmutableTree(hash, key, value, left, right, collisions) }; } private static ImmutableTree RotateRight(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { var r = new ImmutableTree(hash, key, value, left.rightNode!, right, collisions); return new ImmutableTree(left.storedHash, left.storedKey!, left.storedValue!, left.leftNode!, r, left.collisions!); } private static ImmutableTree RotateLeft(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { var l = new ImmutableTree(hash, key, value, left, right.leftNode!, collisions); return new ImmutableTree(right.storedHash, right.storedKey!, right.storedValue!, l, right.rightNode!, right.collisions!); } private static ImmutableTree RotateRightLeft(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { var l = new ImmutableTree(hash, key, value, left, right.leftNode!.leftNode!, collisions); var r = new ImmutableTree(right.storedHash, right.storedKey!, right.storedValue!, right.leftNode.rightNode!, right.rightNode!, right.collisions!); return new ImmutableTree(right.leftNode.storedHash, right.leftNode.storedKey!, right.leftNode.storedValue!, l, r, right.leftNode.collisions!); } private static ImmutableTree RotateLeftRight(int hash, TKey key, TValue value, ImmutableTree left, ImmutableTree right, ImmutableBucket collisions) { var l = new ImmutableTree(left.storedHash, left.storedKey!, left.storedValue!, left.leftNode!, left.rightNode!.leftNode!, left.collisions!); var r = new ImmutableTree(hash, key, value, left.rightNode.rightNode!, right, collisions); return new ImmutableTree(left.rightNode.storedHash, left.rightNode.storedKey!, left.rightNode.storedValue!, l, r, left.rightNode.collisions!); } public override string ToString() => this.IsEmpty ? "empty" : $"{this.storedKey} : {this.storedValue}"; public IEnumerable> Walk() { if (this.IsEmpty) yield break; var nodes = new ImmutableTree[this.height]; var currentNode = this; var index = -1; while (!currentNode!.IsEmpty || index != -1) { if (!currentNode.IsEmpty) { nodes[++index] = currentNode; currentNode = currentNode.leftNode; } else { currentNode = nodes[index--]; yield return new ReadOnlyKeyValue(currentNode.storedKey!, currentNode.storedValue!); if (currentNode.collisions != null) foreach (var keyValue in currentNode.collisions.Repository) yield return keyValue; currentNode = currentNode.rightNode; } } } } internal class ImmutableTreeDebugView where TKey : class { private readonly ImmutableTree tree; public ImmutableTreeDebugView(ImmutableTree tree) { this.tree = tree; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public ReadOnlyKeyValue[] Items => tree.Walk().ToArray(); } [DebuggerTypeProxy(typeof(ImmutableRefTreeDebugView<>))] internal sealed class ImmutableRefTree where TValue : class { public static readonly ImmutableRefTree Empty = new(); private readonly int height; private readonly int storedHash; private readonly TValue? storedValue; private readonly ImmutableRefTree? leftNode; private readonly ImmutableRefTree? rightNode; private readonly ImmutableBucket? collisions; public readonly bool IsEmpty = true; private ImmutableRefTree(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { this.collisions = collisions; this.storedHash = hash; this.leftNode = left; this.rightNode = right; this.storedValue = value; this.IsEmpty = false; this.height = 1 + (left.height > right.height ? left.height : right.height); } private ImmutableRefTree() { } private ImmutableRefTree(int hash, TValue value) { this.storedHash = hash; this.leftNode = Empty; this.rightNode = Empty; this.storedValue = value; this.IsEmpty = false; this.height = 1; } public ImmutableRefTree AddOrSkip(TValue value) => this.AddOrUpdate(RuntimeHelpers.GetHashCode(value), value); public ImmutableRefTree Remove(TValue value) => this.Remove(RuntimeHelpers.GetHashCode(value), value); private ImmutableRefTree AddOrUpdate(int hash, TValue value) { if (this.IsEmpty) return new ImmutableRefTree(hash, value); if (hash == this.storedHash) return this.CheckCollision(hash, value); if (hash < this.storedHash) { if (this.height == 1) return new ImmutableRefTree(this.storedHash, this.storedValue!, new ImmutableRefTree(hash, value), this.rightNode!, this.collisions!); var left = this.leftNode!.AddOrUpdate(hash, value); return ReferenceEquals(left, this.leftNode) ? this : Balance(this.storedHash, this.storedValue!, left, this.rightNode!, this.collisions!); } if (this.height == 1) return new ImmutableRefTree(this.storedHash, this.storedValue!, this.leftNode!, new ImmutableRefTree(hash, value), this.collisions!); var right = this.rightNode!.AddOrUpdate(hash, value); return ReferenceEquals(right, this.rightNode) ? this : Balance(this.storedHash, this.storedValue!, this.leftNode!, right, this.collisions!); } private ImmutableRefTree Remove(int hash, TValue value) { if (this.IsEmpty) return this; if (hash != this.storedHash) return hash < this.storedHash ? Balance(this.storedHash, this.storedValue!, this.leftNode!.Remove(hash, value), this.rightNode!, this.collisions!) : Balance(this.storedHash, this.storedValue!, this.leftNode!, this.rightNode!.Remove(hash, value), this.collisions!); if (ReferenceEquals(value, this.storedValue)) { if (this.height == 1) return Empty; if (this.rightNode!.IsEmpty) return this.leftNode!; if (this.leftNode!.IsEmpty) return this.rightNode!; var next = this.rightNode; while (!next.leftNode!.IsEmpty) next = next.leftNode; return new ImmutableRefTree(next.storedHash, next.storedValue!, this.leftNode!, this.rightNode!.Remove(next.storedHash, next.storedValue!), next.collisions!); } if (this.collisions != null) return new ImmutableRefTree(this.storedHash, this.storedValue!, this.leftNode!, this.rightNode!, this.collisions.Remove(value)); return this; } private ImmutableRefTree CheckCollision(int hash, TValue value) { if (ReferenceEquals(value, this.storedValue)) return this; if (this.collisions == null) return new ImmutableRefTree(hash, value, this.leftNode!, this.rightNode!, ImmutableBucket.Empty.Add(value)); return new ImmutableRefTree(hash, value, this.leftNode!, this.rightNode!, this.collisions.AddIfNotExist(value)); } private static ImmutableRefTree Balance(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { var balance = left.height - right.height; return balance switch { >= 2 => left.leftNode!.height - left.rightNode!.height == -1 ? RotateLeftRight(hash, value, left, right, collisions) : RotateRight(hash, value, left, right, collisions), <= -2 => right.leftNode!.height - right.rightNode!.height == 1 ? RotateRightLeft(hash, value, left, right, collisions) : RotateLeft(hash, value, left, right, collisions), _ => new ImmutableRefTree(hash, value, left, right, collisions) }; } private static ImmutableRefTree RotateRight(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { var r = new ImmutableRefTree(hash, value, left.rightNode!, right, collisions); return new ImmutableRefTree(left.storedHash, left.storedValue!, left.leftNode!, r, left.collisions!); } private static ImmutableRefTree RotateLeft(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { var l = new ImmutableRefTree(hash, value, left, right.leftNode!, collisions); return new ImmutableRefTree(right.storedHash, right.storedValue!, l, right.rightNode!, right.collisions!); } private static ImmutableRefTree RotateRightLeft(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { var l = new ImmutableRefTree(hash, value, left, right.leftNode!.leftNode!, collisions); var r = new ImmutableRefTree(right.storedHash, right.storedValue!, right.leftNode.rightNode!, right.rightNode!, right.collisions!); return new ImmutableRefTree(right.leftNode.storedHash, right.leftNode.storedValue!, l, r, right.leftNode.collisions!); } private static ImmutableRefTree RotateLeftRight(int hash, TValue value, ImmutableRefTree left, ImmutableRefTree right, ImmutableBucket collisions) { var l = new ImmutableRefTree(left.storedHash, left.storedValue!, left.leftNode!, left.rightNode!.leftNode!, left.collisions!); var r = new ImmutableRefTree(hash, value, left.rightNode.rightNode!, right, collisions); return new ImmutableRefTree(left.rightNode.storedHash, left.rightNode.storedValue!, l, r, left.rightNode.collisions!); } public override string ToString() => this.IsEmpty ? "empty" : $"{this.storedValue}"; public IEnumerable Walk() { if (this.IsEmpty) yield break; var nodes = new ImmutableRefTree[this.height]; var currentNode = this; var index = -1; while (!currentNode!.IsEmpty || index != -1) { if (!currentNode.IsEmpty) { nodes[++index] = currentNode; currentNode = currentNode.leftNode; } else { currentNode = nodes[index--]; yield return currentNode.storedValue!; if (currentNode.collisions != null) foreach (var keyValue in currentNode.collisions.Repository) yield return keyValue; currentNode = currentNode.rightNode; } } } } internal class ImmutableRefTreeDebugView where TValue : class { private readonly ImmutableRefTree tree; public ImmutableRefTreeDebugView(ImmutableRefTree tree) { this.tree = tree; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public TValue[] Items => tree.Walk().ToArray(); } ================================================ FILE: src/Utils/Data/Pair.cs ================================================ namespace Stashbox.Utils.Data; internal class Pair { public T1 I1; public T2 I2; public Pair(T1 item1, T2 item2) { this.I1 = item1; this.I2 = item2; } } ================================================ FILE: src/Utils/Data/Stack.cs ================================================ using System; using System.Collections.Generic; using System.Linq; #nullable disable namespace Stashbox.Utils.Data; internal class Stack : ExpandableArray { public static Stack FromEnumerable(IEnumerable enumerable) => new(enumerable); public Stack() { } public Stack(IEnumerable initial) : base(initial.CastToArray()) { } public TValue Pop() { if (this.Length == 0) return default; var result = base.Repository[this.Length - 1]; base.Repository[--this.Length] = default; return result; } public TValue Front() => this.Length == 0 ? default : base.Repository[this.Length - 1]; public void PushBack(TValue item) { if (this.Length == 0) { this.Length = 1; this.Repository = [item]; return; } this.EnsureSize(); var newArray = new TValue[this.Length]; newArray[0] = item; Array.Copy(this.Repository, 0, newArray, 1, this.Length - 1); this.Repository = newArray; } public TValue PeekBack() => this.Length == 0 ? default : this.Repository[0]; public void ReplaceBack(TValue value) => this.Repository[0] = value; } ================================================ FILE: src/Utils/Data/Tree.cs ================================================ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; namespace Stashbox.Utils.Data; [DebuggerTypeProxy(typeof(TreeDebugView<>))] internal sealed class Tree { private class Node { public readonly int StoredKey; public TValue StoredValue; public Node? Left; public Node? Right; public int Height; public Node(int key, TValue value) { this.StoredValue = value; this.StoredKey = key; this.Height = 1; } } private Node? root; public bool IsEmpty => this.root == null; public void Add(int key, TValue value) { this.root = Add(this.root, key, value); } [MethodImpl(Constants.Inline)] public TValue? GetOrDefault(int key) { if (this.root == null) return default; var node = root; while (node != null && node.StoredKey != key) node = key < node.StoredKey ? node.Left : node.Right; return node == null ? default : node.StoredValue; } private static int CalculateHeight(Node node) { if (node is { Left: not null, Right: not null }) return 1 + (node.Left.Height > node.Right.Height ? node.Left.Height : node.Right.Height); if (node.Left == null && node.Right == null) return 1; return 1 + (node.Left?.Height ?? node.Right!.Height); } private static int GetBalance(Node node) { if (node is { Left: not null, Right: not null }) return node.Left.Height - node.Right.Height; if (node.Left == null && node.Right == null) return 0; return node.Left?.Height ?? node.Right!.Height * -1; } private static Node RotateLeft(Node node) { var current = node.Right; var left = current!.Left; current.Left = node; node.Right = left; node.Height = CalculateHeight(node); current.Height = CalculateHeight(current); return current; } private static Node RotateRight(Node node) { var current = node.Left; var right = current!.Right; current.Right = node; node.Left = right; node.Height = CalculateHeight(node); current.Height = CalculateHeight(current); return current; } private static Node Add(Node? node, int key, TValue value) { if (node == null) return new Node(key, value); if (node.StoredKey == key) { node.StoredValue = value; return node; } if (node.StoredKey > key) node.Left = Add(node.Left, key, value); else node.Right = Add(node.Right, key, value); node.Height = CalculateHeight(node); var balance = GetBalance(node); switch (balance) { case >= 2 when GetBalance(node.Left!) == -1: node.Left = RotateLeft(node.Left!); node = RotateRight(node); break; case >= 2: node = RotateRight(node); break; case <= -2 when GetBalance(node.Right!) == 1: node.Right = RotateRight(node.Right!); node = RotateLeft(node); break; case <= -2: node = RotateLeft(node); break; } return node; } public IEnumerable Walk() { if (this.root == null) yield break; switch (this.root.Height) { case 1: yield return this.root.StoredValue; break; case 2: if (this.root.Left != null) yield return this.root.Left.StoredValue; yield return this.root.StoredValue; if (this.root.Right != null) yield return this.root.Right.StoredValue; break; default: { var nodes = new Node[this.root.Height - 2]; var currentNode = this.root; var index = -1; while (true) { if (currentNode != null) { if (currentNode.Height == 2) { if (currentNode.Left != null) yield return currentNode.Left.StoredValue; yield return currentNode.StoredValue; if (currentNode.Right != null) yield return currentNode.Right.StoredValue; if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } else if (currentNode.Height == 1) { yield return currentNode.StoredValue; if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } else { nodes[++index] = currentNode; currentNode = currentNode.Left; } } else { if (index == -1) break; currentNode = nodes[index--]; yield return currentNode.StoredValue; currentNode = currentNode.Right; } } break; } } } } internal class TreeDebugView { private readonly Tree tree; public TreeDebugView(Tree tree) { this.tree = tree; } [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] public TValue[] Items => tree.Walk().ToArray(); } ================================================ FILE: src/Utils/Shield.cs ================================================ using Stashbox.Exceptions; using System; namespace Stashbox.Utils; /// /// Represents a utility class for input validation. /// public static class Shield { /// /// Checks the value of the given object and throws an ArgumentNullException if it == null. /// /// Type of the object. /// The object to be checked. /// The name of the parameter to be checked. public static void EnsureNotNull(T obj, string parameterName) { if (obj == null) throw new ArgumentNullException(parameterName); } /// /// Checks the value of the given object and throws an ArgumentNullException with the given message if it == null. /// /// Type of the object. /// The object to be checked. /// The name of the parameter to be checked. /// The message to be shown in the exception. public static void EnsureNotNull(T obj, string parameterName, string message) { if (obj == null || obj.Equals(default)) throw new ArgumentNullException(parameterName, message); } /// /// Checks the value of the given string and throws an ArgumentException if it == null or empty. /// /// The string to be checked. /// The name of the parameter to be checked. public static void EnsureNotNullOrEmpty(string obj, string parameterName) { if (string.IsNullOrWhiteSpace(obj)) throw new ArgumentException(string.Empty, parameterName); } /// /// Checks two integers and throws an ArgumentException if the actual is lesser than the reference. /// /// The actual value. /// The reference value. public static void EnsureGreaterThan(int actual, int reference) { if (reference >= actual) throw new ArgumentException($"The given number is less or equal than: {reference}"); } /// /// Checks a bool condition and throws an ArgumentException if it is false. /// /// The condition. /// Exception message. public static void EnsureTrue(bool condition, string message) { if (!condition) throw new ArgumentException(message); } /// /// Checks the type of the given object and throws an ArgumentException if it doesn't matches with the given type parameter. /// /// The type parameter. /// The object to be checked. public static void EnsureTypeOf(object obj) { if (obj is not TType) throw new ArgumentException($"{nameof(obj)} is not {TypeCache.Type}.", nameof(obj)); } internal static void EnsureTypeMapIsValid(Type serviceType, Type implementationType) { EnsureIsResolvable(implementationType); if (!implementationType.Implements(serviceType)) throw new InvalidRegistrationException(implementationType, $"The type {implementationType} does not implement the service type {serviceType}."); } internal static void EnsureIsResolvable(Type implementationType) { if (!implementationType.IsResolvableType()) throw new InvalidRegistrationException(implementationType, $"The type {implementationType} could not be resolved. It's probably an interface, abstract class or primitive type."); } internal static void ThrowDisposedException(string? name, string caller) => throw new ObjectDisposedException(name, $"The member '{caller}' was called on '{name}' but it has been disposed already."); } ================================================ FILE: src/Utils/Swap.cs ================================================ using System; using System.Threading; namespace Stashbox.Utils; internal static class Swap { private const int MinimumSwapThreshold = 50; public static bool SwapValue(ref TValue refValue, Func valueFactory, T1 t1, T2 t2, T3 t3, T4 t4) where TValue : class { var currentValue = Volatile.Read(ref refValue); var newValue = valueFactory(t1, t2, t3, t4, currentValue); var original = Interlocked.CompareExchange(ref refValue, newValue, currentValue); if (ReferenceEquals(original, currentValue)) return !ReferenceEquals(newValue, currentValue); return RepeatSwap(ref refValue, valueFactory, t1, t2, t3, t4); } private static bool RepeatSwap(ref TValue refValue, Func valueFactory, T1 t1, T2 t2, T3 t3, T4 t4) where TValue : class { var wait = new SpinWait(); var desiredThreshold = Environment.ProcessorCount * 6; var swapThreshold = desiredThreshold <= MinimumSwapThreshold ? MinimumSwapThreshold : desiredThreshold; while (true) { var currentValue = Volatile.Read(ref refValue); var newValue = valueFactory(t1, t2, t3, t4, currentValue); var original = Interlocked.CompareExchange(ref refValue, newValue, currentValue); if (ReferenceEquals(original, currentValue)) return !ReferenceEquals(newValue, currentValue); if (wait.Count > swapThreshold) throw new InvalidOperationException("Swap quota exceeded."); wait.SpinOnce(); } } } ================================================ FILE: src/Utils/TypeCache.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Stashbox.Expressions.Compile; namespace Stashbox.Utils; internal static class TypeCache { public static readonly Type Type = typeof(TType); } internal static class TypeCache { public static readonly Type EmptyType = TypeCache.Type; public static readonly Type FuncType = typeof(Func<>); public static readonly Type[] EmptyTypes = EmptyArray(); public static readonly Type VoidType = typeof(void); public static readonly Type EnumerableType = typeof(IEnumerable<>); public static readonly Type NullableType = typeof(Nullable<>); public static readonly Type ExpressionEmitterType = typeof(ExpressionEmitter); public static T[] EmptyArray() => InternalArrayHelper.Empty; private static class InternalArrayHelper { #if NET5_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER public static readonly T[] Empty = Array.Empty(); #else public static readonly T[] Empty = new T[0]; #endif } } ================================================ FILE: src/stashbox.csproj ================================================  net461;netstandard2.0;netstandard2.1;net5.0;net6.0;net7.0;net8.0;net9.0;net10.0 Stashbox Stashbox Stashbox Stashbox Peter Csajtai Peter Csajtai Stashbox Copyright © Peter Csajtai 2026 MIT https://z4kn4fein.github.io/stashbox icon.png https://github.com/z4kn4fein/stashbox git README.md Stashbox di dependency-injection ioc dotnet netstandard Stashbox is a lightweight, fast, and portable dependency injection framework for .NET-based solutions. true true true ../sn.snk 1.0.0 1.0.0 false latest true true true snupkg https://github.com/z4kn4fein/stashbox/blob/master/CHANGELOG.md Debug;Release;Benchmark enable CA1032 Stashbox.Benchmark Stashbox.Benchmark Stashbox.Benchmark true Stashbox .NET Standard 2.0 HAS_ASYNC_DISPOSABLE Stashbox .NET Standard 2.1 HAS_ASYNC_DISPOSABLE Stashbox .NET 4.6.1 HAS_ASYNC_DISPOSABLE Stashbox .NET 5.0 HAS_ASYNC_DISPOSABLE Stashbox .NET 6.0 HAS_ASYNC_DISPOSABLE Stashbox .NET 7.0 HAS_ASYNC_DISPOSABLE;HAS_REQUIRED Stashbox .NET 8.0 HAS_ASYNC_DISPOSABLE;HAS_REQUIRED Stashbox .NET 9.0 HAS_ASYNC_DISPOSABLE;HAS_REQUIRED Stashbox .NET 10.0 HAS_ASYNC_DISPOSABLE;HAS_REQUIRED ================================================ FILE: stashbox.sln ================================================  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stashbox", "src\stashbox.csproj", "{21317234-0499-4178-BCA5-558FBA2CB9AC}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "stashbox.tests", "test\stashbox.tests.csproj", "{C7625187-F112-4928-8B73-2EA175E32204}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "testassembly", "test\testassembly\testassembly.csproj", "{FE845F24-9A71-4FB4-88F7-EE36598C3D90}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|Any CPU.Build.0 = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|x64.ActiveCfg = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|x64.Build.0 = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|x86.ActiveCfg = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Debug|x86.Build.0 = Debug|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|Any CPU.ActiveCfg = Release|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|Any CPU.Build.0 = Release|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|x64.ActiveCfg = Release|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|x64.Build.0 = Release|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|x86.ActiveCfg = Release|Any CPU {21317234-0499-4178-BCA5-558FBA2CB9AC}.Release|x86.Build.0 = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|Any CPU.Build.0 = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|x64.ActiveCfg = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|x64.Build.0 = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|x86.ActiveCfg = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Debug|x86.Build.0 = Debug|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|Any CPU.ActiveCfg = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|Any CPU.Build.0 = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|x64.ActiveCfg = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|x64.Build.0 = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|x86.ActiveCfg = Release|Any CPU {C7625187-F112-4928-8B73-2EA175E32204}.Release|x86.Build.0 = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|Any CPU.Build.0 = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|x64.ActiveCfg = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|x64.Build.0 = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|x86.ActiveCfg = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Debug|x86.Build.0 = Debug|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|Any CPU.ActiveCfg = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|Any CPU.Build.0 = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|x64.ActiveCfg = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|x64.Build.0 = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|x86.ActiveCfg = Release|Any CPU {FE845F24-9A71-4FB4-88F7-EE36598C3D90}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A91FC5AF-2866-45D7-91FF-1C6ADE67E230} EndGlobalSection GlobalSection(Performance) = preSolution HasPerformanceSessions = true EndGlobalSection GlobalSection(Performance) = preSolution HasPerformanceSessions = true EndGlobalSection EndGlobal ================================================ FILE: test/ActivateTests.cs ================================================ using Stashbox.Attributes; using System; using Xunit; namespace Stashbox.Tests; public class ActivateTests { [Fact] public void ActivateTests_Full() { var inst = new StashboxContainer().Register().Activate(); Assert.NotNull(inst.Test); Assert.NotNull(inst.Test2); Assert.True(inst.InjectionMethodCalled); } [Fact] public void ActivateTests_Full_DepOverride() { var test = new Test(); var inst = new StashboxContainer().Activate(test); Assert.Same(test, inst.Test); Assert.Same(test, inst.Test2); Assert.True(inst.InjectionMethodCalled); } [Fact] public void ActivateTests_Fail() { Assert.Throws(() => new StashboxContainer().Activate()); } [Fact] public void ActivateTests_Fail_On_Scope() { Assert.Throws(() => new StashboxContainer().BeginScope().Activate()); } interface ITest; class Test : ITest; class Test1 { public bool InjectionMethodCalled { get; set; } public Test Test { get; set; } [Dependency] public Test Test2 { get; set; } public Test1(Test test) { this.Test = test; } [InjectionMethod] public void InjectMethod() { this.InjectionMethodCalled = true; } } } ================================================ FILE: test/AssemblyTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Lifetime; using System.Linq; using TestAssembly; using Xunit; namespace Stashbox.Tests; public class AssemblyTests { [Fact] public void LoadTestAssembly() { using var container = new StashboxContainer() .RegisterAssembly(typeof(ITA_T1).Assembly); var regs = container.GetRegistrationDiagnostics().ToArray(); Assert.Equal(34, regs.Length); } [Fact] public void LoadTestAssembly_Just_Interfaces_And_Self() { using var container = new StashboxContainer() .RegisterAssembly(typeof(ITA_T1).Assembly, serviceTypeSelector: Rules.ServiceRegistrationFilters.Interfaces); var regs = container.GetRegistrationDiagnostics().ToArray(); Assert.Equal(32, regs.Length); } [Fact] public void LoadTestAssembly_Just_Abstarct_And_Self() { using var container = new StashboxContainer() .RegisterAssembly(typeof(ITA_T1).Assembly, serviceTypeSelector: Rules.ServiceRegistrationFilters.AbstractClasses); var regs = container.GetRegistrationDiagnostics().ToArray(); Assert.Equal(18, regs.Length); } [Fact] public void LoadTestAssembly_Just_Abstarct_Without_Self() { using var container = new StashboxContainer() .RegisterAssembly(typeof(ITA_T1).Assembly, serviceTypeSelector: Rules.ServiceRegistrationFilters.AbstractClasses, registerSelf: false); var regs = container.GetRegistrationDiagnostics().ToArray(); Assert.Equal(2, regs.Length); } [Fact] public void RegistersTests_RegisterAssembly() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining(); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().ToArray(); Assert.Equal(34, regs.Length); } [Fact] public void RegistersTests_RegisterAssembly_AsSelf() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining(); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().ToArray(); Assert.Equal(34, regs.Length); Assert.Contains(regs, reg => reg.Key == typeof(TA_T1)); } [Fact] public void RegistersTests_RegisterAssemblies() { using var container = new StashboxContainer(); var assembly1 = typeof(ITA_T1).Assembly; var assembly2 = typeof(IStashboxContainer).Assembly; container.RegisterAssemblies(new[] { assembly1, assembly2 }, t => t.FullName.Contains("TestAssembly") || t == typeof(StashboxContainer)); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings(); Assert.Contains(regs, r => r.Key == typeof(IStashboxContainer)); Assert.Contains(regs, r => r.Key == typeof(ITA_T1)); } [Fact] public void RegistersTests_RegisterAssemblies_AsSelf() { using var container = new StashboxContainer(); var assembly1 = typeof(ITA_T1).Assembly; var assembly2 = typeof(IStashboxContainer).Assembly; container.RegisterAssemblies(new[] { assembly1, assembly2 }); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings(); Assert.Contains(regs, r => r.Key == typeof(StashboxContainer)); Assert.Contains(regs, r => r.Key == typeof(TA_T1)); } [Fact] public void RegistersTests_RegisterAssembly_Selector() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining(type => type == typeof(TA_T1)); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings(); Assert.Contains(regs, reg => reg.Key == typeof(ITA_T1)); Assert.Contains(regs, reg => reg.Key == typeof(TA_T1)); } [Fact] public void RegistersTests_RegisterAssembly_Configurator() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining(configurator: context => { if (context.HasServiceType()) context.WithScopedLifetime(); }); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .Where(r => r.Value.Lifetime is ScopedLifetime).ToArray(); Assert.True(regs.Length > 0); } [Fact] public void RegistersTests_RegisterAssembly_Configurator_AsSelf() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining(configurator: context => { if (context.ImplementationType == typeof(TA_T1)) context.WithScopedLifetime(); }); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .Where(r => r.Value.Lifetime is ScopedLifetime).ToArray(); Assert.True(regs.Length > 0); } } ================================================ FILE: test/AsyncDisposeTests.cs ================================================ using System; using System.Threading; using System.Threading.Tasks; using Xunit; #if HAS_ASYNC_DISPOSABLE namespace Stashbox.Tests { public class AsyncDisposeTests { [Fact] public async Task Async_Dispose_Works() { AsyncDisposable disposable; { await using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register(); disposable = container.Resolve(); } Assert.True(disposable.Disposed); } [Fact] public async Task Async_Dispose_Multiple() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register(); var disposable = container.Resolve(); await container.DisposeAsync(); await container.DisposeAsync(); Assert.True(disposable.Disposed); } [Fact] public async Task Async_Dispose_Multiple_Scoped() { await using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .RegisterScoped(); var scope = container.BeginScope(); var disposable = scope.Resolve(); await scope.DisposeAsync(); await scope.DisposeAsync(); Assert.True(disposable.Disposed); } [Fact] public async Task Async_Dispose_Works_On_Scoped() { AsyncDisposable disposable; await using var container = new StashboxContainer() .RegisterScoped(); { await using var scope = container.BeginScope(); disposable = scope.Resolve(); } Assert.True(disposable.Disposed); } [Fact] public async Task Async_Dispose_Works_On_Singleton() { AsyncDisposable disposable; { await using var container = new StashboxContainer() .RegisterSingleton(); { await using var scope = container.BeginScope(); disposable = scope.Resolve(); } Assert.False(disposable.Disposed); } Assert.True(disposable.Disposed); } [Fact] public async Task Disposes_Either_Normal_And_Async() { AsyncDisposable asyncDisposable; Disposable disposable; { await using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register() .Register(); asyncDisposable = container.Resolve(); disposable = container.Resolve(); } Assert.True(asyncDisposable.Disposed); Assert.True(disposable.Disposed); } [Fact] public async Task Prefers_Async_Disposable_Over_Normal() { A disposable; { await using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register(); disposable = container.Resolve(); } Assert.True(disposable.Disposed); } [Fact] public async Task Async_Dispose_Works_On_Dependencies() { B b; C c; { await using var container = new StashboxContainer(c => c .WithDisposableTransientTracking() .WithUnknownTypeResolution()) .Register() .Register(); b = container.Resolve(); c = container.Resolve(); } Assert.True(b.Disposed); Assert.True(b.Disposable.Disposed); Assert.True(c.Disposed); Assert.True(c.Disposable.Disposed); } class A : AsyncDisposable, IDisposable { public void Dispose() { throw new InvalidOperationException("should not be called."); } } class B : AsyncDisposable { public Disposable Disposable { get; } public B(Disposable disposable) { Disposable = disposable; } } class C : Disposable { public AsyncDisposable Disposable { get; } public C(AsyncDisposable disposable) { Disposable = disposable; } } class AsyncDisposable : IAsyncDisposable { private int disposed; public bool Disposed => this.disposed == 1; public ValueTask DisposeAsync() { if (Interlocked.CompareExchange(ref this.disposed, 1, 0) != 0) throw new ObjectDisposedException("object already disposed"); return new ValueTask(Task.CompletedTask); } } class Disposable : IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (Disposed) throw new ObjectDisposedException("object already disposed"); Disposed = true; } } } } #endif ================================================ FILE: test/AttributeTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Utils; using System; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class AttributeTest { [Fact] public void AttributeTests_Resolve() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WithName("test1")); container.Register(context => context.WithName("test11")); container.Register(context => context.WithName("test12")); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test22")); container.Register(); container.Register(); var test1 = container.Resolve(); var test2 = container.Resolve("test2"); var test3 = container.Resolve(); var test4 = container.Resolve>(); Assert.NotNull(test1); Assert.NotNull(test2); Assert.NotNull(test3); Assert.NotNull(test4.Value); Assert.True(test3.MethodInvoked); Assert.True(test3.MethodInvoked2); Assert.IsType(test3.test1); Assert.IsType(test3.test2); } [Fact] public void AttributeTests_Named_Resolution() { var container = new StashboxContainer(); container.Register(context => context.WithName("test1")); container.Register(context => context.WithName("test11")); container.Register(context => context.WithName("test12")); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test22")); var test1 = container.Resolve("test1"); var test11 = container.Resolve("test11"); var test12 = container.Resolve("test12"); var test2 = container.Resolve("test2"); var test22 = container.Resolve("test22"); Assert.IsType(test1); Assert.IsType(test11); Assert.IsType(test12); Assert.IsType(test2); Assert.IsType(test22); } [Fact] public void AttributeTests_Parallel_Resolve() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WithName("test1")); container.Register(context => context.WithName("test11")); container.Register(context => context.WithName("test12")); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test22")); container.Register(); Parallel.For(0, 50000, (i) => { var test1 = container.Resolve(); var test2 = container.Resolve("test2"); var test3 = container.Resolve(); Assert.NotNull(test1); Assert.NotNull(test2); Assert.NotNull(test3); Assert.True(test3.MethodInvoked); Assert.IsType(test3.test1); Assert.IsType(test3.test2); }); } [Fact] public void AttributeTests_Parallel_Lazy_Resolve() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WithName("test1")); container.Register(context => context.WithName("test11")); container.Register(context => context.WithName("test12")); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test22")); container.Register(); container.Register(); Parallel.For(0, 50000, (i) => { var test1 = container.Resolve>(); var test2 = container.Resolve>("test2"); var test3 = container.Resolve>(); var test4 = container.Resolve>(); Assert.NotNull(test1.Value); Assert.NotNull(test2.Value); Assert.NotNull(test3.Value); Assert.NotNull(test4.Value); Assert.True(test3.Value.MethodInvoked); Assert.True(test4.Value.MethodInvoked); Assert.IsType(test3.Value.test1); Assert.IsType(test3.Value.test2); Assert.IsType(test4.Value.test1.Value); Assert.IsType(test4.Value.test2.Value); }); } [Fact] public void AttributeTests_InjectionMethod_WithoutMembers() { var inst = new StashboxContainer() .Register() .Resolve(); Assert.True(inst.MethodInvoked); } [Fact] public void AttributeTests_InjectionMethod_Private() { var inst = new StashboxContainer() .Register() .Resolve(); Assert.True(inst.MethodInvoked); } interface ITest1; interface ITest2 { ITest1 test1 { get; set; } } interface ITest3 { ITest2 test2 { get; set; } ITest1 test1 { get; set; } bool MethodInvoked { get; set; } bool MethodInvoked2 { get; set; } } interface ITest4 { Lazy test2 { get; set; } Lazy test1 { get; set; } bool MethodInvoked { get; set; } } class Test1 : ITest1; class Test11 : ITest1; class Test12 : ITest1; class Test22 : ITest2 { public ITest1 test1 { get; set; } } class Test2 : ITest2 { public ITest1 test1 { get; set; } public Test2([Dependency("test11")] ITest1 test1) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureTypeOf(test1); } } class Test3 : ITest3 { [Dependency("test11")] public ITest1 test1 { get; set; } [Dependency("test22")] public ITest2 test2 { get; set; } [InjectionMethod] public void MethodTest([Dependency("test22")] ITest2 test2) { Shield.EnsureNotNull(test2, nameof(test2)); this.MethodInvoked = true; } [InjectionMethod] public void MethodTest2([Dependency("test22")] ITest2 test2) { Shield.EnsureNotNull(test2, nameof(test2)); this.MethodInvoked2 = true; } public Test3([Dependency("test12")] ITest1 test12, [Dependency("test2")] ITest2 test2) { Shield.EnsureNotNull(test12, nameof(test12)); Shield.EnsureNotNull(test2, nameof(test2)); Shield.EnsureTypeOf(test12); Shield.EnsureTypeOf(test2); } public bool MethodInvoked { get; set; } public bool MethodInvoked2 { get; set; } } class Test4 : ITest4 { [Dependency("test11")] public Lazy test1 { get; set; } [Dependency("test22")] public Lazy test2 { get; set; } [InjectionMethod] public void MethodTest([Dependency("test22")] Lazy test2) { Shield.EnsureNotNull(test2.Value, nameof(test2.Value)); this.MethodInvoked = true; } public Test4([Dependency("test12")] Lazy test1, [Dependency("test2")] Lazy test2) { Shield.EnsureNotNull(test1.Value, nameof(test1.Value)); Shield.EnsureNotNull(test2.Value, nameof(test2.Value)); Shield.EnsureTypeOf(test1.Value); Shield.EnsureTypeOf(test2.Value); } public bool MethodInvoked { get; set; } } class Test5 { [InjectionMethod] public void MethodTest() { this.MethodInvoked = true; } public bool MethodInvoked { get; set; } } class Test6 { [InjectionMethod] private void MethodTest() { this.MethodInvoked = true; } public bool MethodInvoked { get; set; } } } ================================================ FILE: test/BuildUpTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Utils; using System; using Xunit; namespace Stashbox.Tests; public class BuildUpTests { [Fact] public void BuildUpTests_BuildUp_Simple() { using var container = new StashboxContainer(); ITest inst = new Test(); var built = container.BuildUp(inst); Assert.Same(inst, built); } [Fact] public void BuildUpTests_BuildUp() { using var container = new StashboxContainer(); container.Register(); var test1 = new Test1(); container.WireUp(test1); var test2 = new Test2(); var inst = container.BuildUp(test2); Assert.Equal(test2, inst); Assert.NotNull(inst); Assert.NotNull(inst.Test1); Assert.IsType(inst); Assert.IsType(inst.Test1); Assert.IsType(inst.Test1.Test); } [Fact] public void BuildUpTests_BuildUp_As_InterfaceType() { using var container = new StashboxContainer(); container.Register().Register(); var test3 = new Test3(); var inst = (Test3)container.BuildUp(test3); Assert.NotNull(inst.Test); } [Fact] public void BuildUpTests_BuildUp_Scoped() { using var container = new StashboxContainer(); container.RegisterScoped(); var test1 = new Test1(); using (var scope = container.BeginScope()) { scope.BuildUp(test1); Assert.False(test1.Test.Disposed); } Assert.True(test1.Test.Disposed); using (var scope = container.BeginScope()) { scope.BuildUp(test1); Assert.False(test1.Test.Disposed); } Assert.True(test1.Test.Disposed); } interface ITest : IDisposable { bool Disposed { get; } } interface ITest1 { ITest Test { get; } } interface ITest3; class Test : ITest { public void Dispose() { if (this.Disposed) throw new ObjectDisposedException("disposed"); this.Disposed = true; } public bool Disposed { get; private set; } } class Test1 : ITest1 { [Dependency] public ITest Test { get; set; } [InjectionMethod] public void Init() { Shield.EnsureNotNull(this.Test, nameof(this.Test)); } } class Test2 { [Dependency] public ITest1 Test1 { get; set; } } class Test3 : ITest3 { [Dependency] public ITest Test { get; set; } } } ================================================ FILE: test/CanResolveTests.cs ================================================ using Stashbox.Resolution; using System; using System.Collections.Generic; using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests; public class CanResolveTests { [Fact] public void CanResolveTests_ExplicitService() { using var container = new StashboxContainer() .Register(); Assert.True(container.CanResolve()); Assert.False(container.CanResolve()); } [Fact] public void CanResolveTests_OpenGeneric() { using var container = new StashboxContainer() .Register(typeof(IB<>), typeof(B<>)); Assert.False(container.CanResolve(typeof(IB<>))); Assert.True(container.CanResolve>()); } [Fact] public void CanResolveTests_ClosedGeneric() { using var container = new StashboxContainer() .Register(typeof(IB), typeof(B)); Assert.True(container.CanResolve>()); } [Fact] public void CanResolveTests_Wrapper() { using var container = new StashboxContainer() .Register(); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>()); } [Fact] public void CanResolveTests_Enumerable() { using var container = new StashboxContainer() .Register(); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>>()); } [Fact] public void CanResolveTests_ExplicitService_Scope() { using var scope = new StashboxContainer() .Register().BeginScope(); Assert.True(scope.CanResolve()); Assert.False(scope.CanResolve()); } [Fact] public void CanResolveTests_OpenGeneric_Scope() { using var scope = new StashboxContainer() .Register(typeof(IB<>), typeof(B<>)).BeginScope(); Assert.False(scope.CanResolve(typeof(IB<>))); Assert.True(scope.CanResolve>()); } [Fact] public void CanResolveTests_ClosedGeneric_Scope() { using var scope = new StashboxContainer() .Register(typeof(IB), typeof(B)).BeginScope(); Assert.True(scope.CanResolve>()); } [Fact] public void CanResolveTests_Wrapper_Scope() { using var scope = new StashboxContainer() .Register().BeginScope(); Assert.True(scope.CanResolve>()); Assert.True(scope.CanResolve>()); Assert.True(scope.CanResolve>()); } [Fact] public void CanResolveTests_Enumerable_Scope() { using var scope = new StashboxContainer() .Register().BeginScope(); Assert.True(scope.CanResolve>()); Assert.True(scope.CanResolve>>()); } [Fact] public void CanResolveTests_Special_Types() { using var scope = new StashboxContainer(); Assert.True(scope.CanResolve()); Assert.True(scope.CanResolve()); Assert.True(scope.CanResolve()); } [Fact] public void CanResolveTests_WithExceptionOverEmptyCollection() { var container = new StashboxContainer(c => c.WithExceptionOverEmptyCollection()).Register(); Assert.False(container.CanResolve()); Assert.Null(container.ResolveOrDefault()); var ex = Assert.Throws(() => container.Resolve()); Assert.Contains("Constructor Void .ctor(Byte[]) found with unresolvable parameter: (System.Byte[])payload.", ex.Message); } private interface IA; private class A : IA; private interface IB; private class B : IB; #pragma warning disable CS9113 // Parameter is unread. private class C(byte[] payload); #pragma warning restore CS9113 // Parameter is unread. } ================================================ FILE: test/ChildContainerTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Resolution; using Stashbox.Tests.Utils; using Stashbox.Utils.Data.Immutable; using Xunit; namespace Stashbox.Tests; public class ChildContainerTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Dispose_Parent_Disposes_Child(CompilerType compilerType) { var test = new Test(); IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); var child = container.CreateChildContainer(); child.RegisterInstance(test); container.Dispose(); container.Dispose(); Assert.True(test.Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Dispose_Parent_Not_Disposes_Child(CompilerType compilerType) { var test = new Test(); IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); var child = container.CreateChildContainer(attachToParent: false); child.RegisterInstance(test); container.Dispose(); container.Dispose(); Assert.False(test.Disposed); child.Dispose(); child.Dispose(); Assert.True(test.Disposed); } #if HAS_ASYNC_DISPOSABLE [Theory] [ClassData(typeof(CompilerTypeTestData))] public async Task ChildContainerTests_Dispose_Parent_Disposes_Child_Async(CompilerType compilerType) { var test = new Test(); IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); var child = container.CreateChildContainer(attachToParent: true); child.RegisterInstance(test); await container.DisposeAsync(); await container.DisposeAsync(); Assert.True(test.Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public async Task ChildContainerTests_Dispose_Parent_Not_Disposes_Child_Async(CompilerType compilerType) { var test = new Test(); IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); var child = container.CreateChildContainer(attachToParent: false); child.RegisterInstance(test); await container.DisposeAsync(); await container.DisposeAsync(); Assert.False(test.Disposed); child.Dispose(); child.Dispose(); Assert.True(test.Disposed); } #endif [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_ResolveAll_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)).Register().Register(); var child = container.CreateChildContainer().Register().Register(); Assert.Equal(4, child.ResolveAll().Count()); Assert.Equal(2, child.ResolveAll(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Current).Count()); Assert.Equal(2, child.ResolveAll(dependencyOverrides: [new object()], ResolutionBehavior.Current).Count()); Assert.Equal(2, child.ResolveAll(name: null, ResolutionBehavior.Current).Count()); Assert.Equal(2, child.ResolveAll(typeof(IT), name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent).Count()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_ResolveAll_Scope_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)).Register().Register(); var child = container.CreateChildContainer().Register().Register(); Assert.Equal(4, child.BeginScope().ResolveAll().Count()); Assert.Equal(2, child.BeginScope().ResolveAll(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Current).Count()); Assert.Equal(2, child.BeginScope().ResolveAll(dependencyOverrides: [new object()], ResolutionBehavior.Current).Count()); Assert.Equal(2, child.BeginScope().ResolveAll(name: null, ResolutionBehavior.Current).Count()); Assert.Equal(2, child.BeginScope().ResolveAll(typeof(IT), name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent).Count()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Dependency_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .Register(c => c.WithInitializer((inst, _) => inst.Init("parent"))) .Register(c => c.WithInitializer((inst, _) => inst.Init("parent"))); var child = container.CreateChildContainer() .Register() .Register() .Register(c => c.WithInitializer((inst, _) => inst.Init("child"))) .Register(c => c.WithInitializer((inst, _) => inst.Init("child"))); Assert.IsType(child.Resolve().Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Current).Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Parent).Dep); Assert.Equal(4, child.Resolve().Dep.Count()); Assert.Equal(2, child.Resolve(ResolutionBehavior.Current).Dep.Count()); Assert.Equal(2, child.Resolve(ResolutionBehavior.Parent).Dep.Count()); var deps = child.Resolve().Dep.Select(t => t.GetType()).ToArray(); Assert.Contains(typeof(T1), deps); Assert.Contains(typeof(T2), deps); Assert.Contains(typeof(T3), deps); Assert.Contains(typeof(T4), deps); deps = child.Resolve(ResolutionBehavior.Current).Dep.Select(t => t.GetType()).ToArray(); Assert.Contains(typeof(T3), deps); Assert.Contains(typeof(T4), deps); deps = child.Resolve(ResolutionBehavior.Parent).Dep.Select(t => t.GetType()).ToArray(); Assert.Contains(typeof(T1), deps); Assert.Contains(typeof(T2), deps); Assert.Equal("child", child.Resolve().ID); Assert.Equal("child", child.Resolve(ResolutionBehavior.Current).ID); Assert.Equal("parent", child.Resolve(ResolutionBehavior.Parent).ID); Assert.Equal("child", child.Resolve().ID); Assert.Equal("child", child.Resolve(ResolutionBehavior.Current).ID); Assert.Equal("parent", child.Resolve(ResolutionBehavior.Parent).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Decorator_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("parent"))); var child = container.CreateChildContainer() .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("child"))); Assert.IsType(((T6)child.Resolve()).Dep); Assert.IsType(((T6)child.Resolve(dependencyOverrides: [new object()], ResolutionBehavior.Current)).Dep); Assert.IsType(((T6)child.Resolve(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent)).Dep); Assert.Equal("child", ((T6)child.Resolve()).ID); Assert.Equal("child", ((T6)child.Resolve(name: null, ResolutionBehavior.Current)).ID); Assert.Equal("parent", ((T6)child.Resolve(typeof(IT), ResolutionBehavior.Parent)).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Scope_Decorator_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("parent"))); var child = container.CreateChildContainer() .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("child"))); Assert.IsType(((T6)child.BeginScope().Resolve()).Dep); Assert.IsType(((T6)child.BeginScope().Resolve(dependencyOverrides: [new object()], ResolutionBehavior.Current)).Dep); Assert.IsType(((T6)child.BeginScope().Resolve(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent)).Dep); Assert.Equal("child", ((T6)child.BeginScope().Resolve()).ID); Assert.Equal("child", ((T6)child.BeginScope().Resolve(name: null, ResolutionBehavior.Current)).ID); Assert.Equal("parent", ((T6)child.BeginScope().Resolve(typeof(IT), ResolutionBehavior.Parent)).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_ResolveOrDefault_Decorator_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("parent"))); var child = container.CreateChildContainer() .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("child"))); Assert.IsType(((T6)child.ResolveOrDefault()).Dep); Assert.IsType(((T6)child.ResolveOrDefault(dependencyOverrides: [new object()], ResolutionBehavior.Current)).Dep); Assert.IsType(((T6)child.ResolveOrDefault(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent)).Dep); Assert.Equal("child", ((T6)child.ResolveOrDefault()).ID); Assert.Equal("child", ((T6)child.ResolveOrDefault(name: null, ResolutionBehavior.Current)).ID); Assert.Equal("parent", ((T6)child.ResolveOrDefault(typeof(IT), ResolutionBehavior.Parent)!).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_ResolveOrDefault_Scope_Decorator_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("parent"))); var child = container.CreateChildContainer() .Register() .Register() .RegisterDecorator(c => c.WithInitializer((inst, _) => inst.Init("child"))); Assert.IsType(((T6)child.BeginScope().ResolveOrDefault()).Dep); Assert.IsType(((T6)child.BeginScope().ResolveOrDefault(dependencyOverrides: [new object()], ResolutionBehavior.Current)).Dep); Assert.IsType(((T6)child.BeginScope().ResolveOrDefault(name: null, dependencyOverrides: [new object()], ResolutionBehavior.Parent)).Dep); Assert.Equal("child", ((T6)child.BeginScope().ResolveOrDefault()).ID); Assert.Equal("child", ((T6)child.BeginScope().ResolveOrDefault(name: null, ResolutionBehavior.Current)).ID); Assert.Equal("parent", ((T6)child.BeginScope().ResolveOrDefault(typeof(IT), ResolutionBehavior.Parent)!).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Decorator_Child_Service_Parent(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(); var child = container.CreateChildContainer() .RegisterDecorator(); Assert.IsType(((T6)child.Resolve()).Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Parent)); Assert.Throws(() => child.Resolve(ResolutionBehavior.Current)); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Decorator_Parent_Service_Child(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .RegisterDecorator(); var child = container.CreateChildContainer() .Register(); Assert.IsType(((T6)child.Resolve()).Dep); Assert.Throws(() => child.Resolve(ResolutionBehavior.Parent)); Assert.IsType(child.Resolve(ResolutionBehavior.Current)); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Parent_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(); var child = container.CreateChildContainer() .Register(); Assert.IsType(child.Resolve().Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Parent)); Assert.Throws(() => child.Resolve(ResolutionBehavior.Parent)); Assert.Throws(() => child.Resolve(ResolutionBehavior.Current)); Assert.IsType(child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency).Dep); Assert.Throws(() => child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency)); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Parent_Enumerable_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(); var child = container.CreateChildContainer() .Register(); Assert.IsType(child.Resolve().Dep.First()); Assert.Empty(child.Resolve(ResolutionBehavior.Current).Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency).Dep.First()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Parent_Enumerable_Select_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register().Register(c => c.WithInitializer((d, _) => d.Init("parent"))); var child = container.CreateChildContainer() .Register().Register(c => c.WithInitializer((d, _) => d.Init("child"))); Assert.Equal(2, child.Resolve().Dep.Count()); Assert.Equal("child", child.Resolve().ID); Assert.Single(child.Resolve(ResolutionBehavior.Parent).Dep); Assert.Equal("parent", child.Resolve(ResolutionBehavior.Parent).ID); Assert.Single(child.Resolve(ResolutionBehavior.Current).Dep); Assert.Equal("child", child.Resolve(ResolutionBehavior.Current).ID); Assert.Equal(2, child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency).Dep.Count()); Assert.Equal("child", child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency).ID); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_PreferCurrent_Enumerable_Select_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register().Register().Register().Register() .Register(c => c.WithInitializer((d, _) => d.Init("parent"))) .Register(c => c.WithInitializer((d, _) => d.Init("parent2"))); var child = container.CreateChildContainer() .Register().Register() .Register(c => c.WithInitializer((d, _) => d.Init("child"))) .Register(c => c.WithInitializer((d, _) => d.Init("child2"))); var inst = child.Resolve(ResolutionBehavior.Default | ResolutionBehavior.PreferEnumerableInCurrent); Assert.Equal(2, inst.Dep.Count()); Assert.Equal("child", inst.ID); Assert.IsType(inst.Dep.ElementAt(0)); Assert.IsType(inst.Dep.ElementAt(1)); var inst2 = child.Resolve(ResolutionBehavior.Default | ResolutionBehavior.PreferEnumerableInCurrent); Assert.Equal(2, inst2.Dep.Count()); Assert.Equal("child2", inst2.ID); Assert.IsType(inst2.Dep.ElementAt(0)); Assert.IsType(inst2.Dep.ElementAt(1)); child.Register(); inst2 = child.Resolve(ResolutionBehavior.Default | ResolutionBehavior.PreferEnumerableInCurrent); Assert.Single(inst2.Dep); Assert.Equal("child2", inst2.ID); Assert.IsType(inst2.Dep.ElementAt(0)); var child2 = child.CreateChildContainer(); child2.Register(c => c.WithInitializer((d, _) => d.Init("child3"))); inst2 = child2.Resolve(ResolutionBehavior.Default | ResolutionBehavior.PreferEnumerableInCurrent); Assert.Equal(3, inst2.Dep.Count()); Assert.Equal("child3", inst2.ID); Assert.IsType(inst2.Dep.ElementAt(0)); Assert.IsType(inst2.Dep.ElementAt(1)); Assert.IsType(inst2.Dep.ElementAt(2)); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Parent_Decorator_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register().RegisterDecorator(); var child = container.CreateChildContainer() .Register(); Assert.IsType(child.Resolve().Dep); Assert.IsType(child.Resolve(ResolutionBehavior.Parent)); Assert.Throws(() => child.Resolve(ResolutionBehavior.Parent)); Assert.Throws(() => child.Resolve(ResolutionBehavior.Current)); Assert.IsType(child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency).Dep); Assert.Throws(() => child.Resolve(ResolutionBehavior.Current | ResolutionBehavior.ParentDependency)); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ChildContainerTests_Resolve_Decorator_Enumerable_Parent_Current(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)).Register().Register().RegisterDecorator(); var child = container.CreateChildContainer().Register().Register().RegisterDecorator(); Assert.Equal(4, ((T5)child.Resolve()).Dep.Count()); Assert.Equal(2, ((T5)child.Resolve(ResolutionBehavior.Current)).Dep.Count()); Assert.Equal(2, ((T5)child.Resolve(ResolutionBehavior.Parent)).Dep.Count()); var deps = ((T5)child.Resolve()).Dep.SelectMany(t => ((T5)t).Dep.Select(td => td.GetType())).ToArray(); Assert.Contains(typeof(T1), deps); Assert.Contains(typeof(T2), deps); Assert.Contains(typeof(T3), deps); Assert.Contains(typeof(T4), deps); deps = ((T5)child.Resolve(ResolutionBehavior.Current)).Dep.Select(t => t.GetType()).ToArray(); Assert.Contains(typeof(T3), deps); Assert.Contains(typeof(T4), deps); deps = ((T5)child.Resolve(ResolutionBehavior.Parent)).Dep.Select(t => t.GetType()).ToArray(); Assert.Contains(typeof(T1), deps); Assert.Contains(typeof(T2), deps); } [Fact] public void ChildContainerTests_Get_NonExisting_Null() { var container = new StashboxContainer(); Assert.Null(container.GetChildContainer("A")); } [Fact] public void ChildContainerTests_Configure() { var container = new StashboxContainer(); container.Register(); container.CreateChildContainer("A", c => c.Register()); Assert.IsType(container.Resolve()); var tenant = container.GetChildContainer("A"); Assert.NotNull(tenant); Assert.IsType(tenant?.Resolve()); } [Fact] public void ChildContainerTests_Configure_Dep() { var container = new StashboxContainer(); container.Register(); container.Register(); container.CreateChildContainer("A", c => c.Register()); var tenant = container.GetChildContainer("A"); Assert.IsType(container.Resolve().Ia); Assert.IsType(tenant?.Resolve().Ia); } [Fact] public void ChildContainerTests_Configure_Validate_Root_Throws() { var container = new StashboxContainer(); container.Register(); container.CreateChildContainer("A", c => c.Register()); var exception = Assert.Throws(() => container.Validate()); Assert.Single(exception.InnerExceptions); } [Fact] public void ChildContainerTests_Configure_Validate_Root_And_Tenants_Throws() { var container = new StashboxContainer(); container.Register(); container.CreateChildContainer("A", c => c.Register()); var exception = Assert.Throws(() => container.Validate()); Assert.Equal(2, exception.InnerExceptions.Count); } [Fact] public void ChildContainerTests_Configure_Validate_Valid() { var container = new StashboxContainer(); container.Register(); container.CreateChildContainer("A", c => c.Register()); var exception = Record.Exception(() => container.Validate()); Assert.Null(exception); } [Fact] public void ChildContainerTests_Dispose() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.Register(); var tenant = container.CreateChildContainer("C", c => { }); var inst = (C)tenant.Resolve(); container.Dispose(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public void ChildContainerTests_Dispose_Tenant() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.CreateChildContainer("C", c => c.Register()); var tenant = container.GetChildContainer("C"); var inst = (C)tenant?.Resolve(); container.Dispose(); Assert.True(inst?.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant?.Resolve()); } [Fact] public void ChildContainerTests_Dispose_Multiple() { var container = new StashboxContainer(); container.Register(); Assert.Null(Record.Exception(() => container.Dispose())); Assert.Null(Record.Exception(() => container.Dispose())); } [Fact] public void ChildContainerTests_Dispose_Removes_Child() { var container = new StashboxContainer(); var a = container.CreateChildContainer("A"); container.CreateChildContainer("B"); var b = container.GetChildContainer("B"); Assert.Equal(2, container.ChildContainers.Count()); a.Dispose(); Assert.Single(container.ChildContainers); b?.Dispose(); Assert.Empty(container.ChildContainers); } [Fact] public void ChildContainerTests_Dispose_Parent() { var container = new StashboxContainer(); container.CreateChildContainer("A"); container.CreateChildContainer("B"); Assert.Equal(2, container.ChildContainers.Count()); container.Dispose(); Assert.Empty(container.ChildContainers); } #if HAS_ASYNC_DISPOSABLE [Fact] public async Task ChildContainerTests_Dispose_Async() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.Register(); var tenant = container.CreateChildContainer("C", c => { }); var inst = (C)tenant.Resolve(); await container.DisposeAsync(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public async Task MultitenantTests_Dispose_Tenant_Async() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.CreateChildContainer("C", c => c.Register()); var tenant = container.GetChildContainer("C"); var inst = (C)tenant?.Resolve(); await container.DisposeAsync(); Assert.True(inst?.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant?.Resolve()); } [Fact] public async Task MultitenantTests_Dispose_Multiple_Async() { var container = new StashboxContainer(); container.Register(); Assert.Null(await Record.ExceptionAsync(async () => await container.DisposeAsync())); Assert.Null(await Record.ExceptionAsync(async () => await container.DisposeAsync())); } [Fact] public async Task ChildContainerTests_Dispose_Removes_Child_Async() { var container = new StashboxContainer(); var a = container.CreateChildContainer("A"); container.CreateChildContainer("B"); var b = container.GetChildContainer("B"); Assert.Equal(2, container.ChildContainers.Count()); await a.DisposeAsync(); Assert.Single(container.ChildContainers); await b!.DisposeAsync(); Assert.Empty(container.ChildContainers); } #endif interface IT; interface IT2; class T1 : IT; class T2 : IT; class T3 : IT; class T4 : IT; class T11 : IT2; class T12 : IT2; class T13 : IT2; class T5 : IT { public IEnumerable Dep { get; } public T5(IEnumerable Dep) { this.Dep = Dep; } } class T6 : IT { public string ID { get; private set; } public IT Dep { get; } public T6(IT Dep) { this.Dep = Dep; } public void Init(string id) => ID = id; } class D1 { public string ID { get; private set; } public IT Dep { get; } public D1(IT Dep) { this.Dep = Dep; } public void Init(string id) => ID = id; } class D2 { public string ID { get; private set; } public IEnumerable Dep { get; } public D2(IEnumerable Dep) { this.Dep = Dep; } public void Init(string id) => ID = id; } class D2_2 { public string ID { get; private set; } public IEnumerable Dep { get; } public D2_2(IEnumerable Dep) { this.Dep = Dep; } public void Init(string id) => ID = id; } class Test : IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) { throw new ObjectDisposedException(nameof(Test)); } this.Disposed = true; } } interface IA; class A : IA; class B : IA; class C : IA, IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(C)); this.Disposed = true; } } class D { public D(IA ia) { Ia = ia; } public IA Ia { get; } } } ================================================ FILE: test/CircularDependencyTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Exceptions; using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class CircularDependencyTests { [Fact] public void CircularDependencyTests_StandardResolve() { using var container = new StashboxContainer(); container.Register(); var exception = Assert.Throws(container.Resolve); Assert.Equal(typeof(Test1), exception.Type); Assert.Contains("Circular dependency was detected while resolving", exception.Message); } [Fact] public void CircularDependencyTests_StandardResolve_Exception_Override() { using var container = new StashboxContainer(c => c.OverrideResolutionFailedExceptionWith()); container.Register(); var exception = Assert.Throws(container.Resolve); Assert.Contains("Circular dependency was detected while resolving", exception.Message); } [Fact] public void CircularDependencyTests_StandardResolve_Parallel_ShouldNotThrow() { using var container = new StashboxContainer(); container.Register(); Parallel.For(0, 5000, i => { container.Resolve(); }); } [Fact] public void CircularDependencyTests_DependencyProperty() { using var container = new StashboxContainer(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_InjectionMethod() { using var container = new StashboxContainer(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Generic_StandardResolve() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); Assert.Throws(container.Resolve>); } [Fact] public void CircularDependencyTests_Generic_DependencyProperty() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test2<,>)); Assert.Throws(container.Resolve>); } [Fact] public void CircularDependencyTests_Generic_InjectionMethod() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test3<,>)); Assert.Throws(container.Resolve>); } [Fact] public void CircularDependencyTests_Func() { using var container = new StashboxContainer(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Lazy() { using var container = new StashboxContainer(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Tuple() { using var container = new StashboxContainer(); container.Register(c => c.WithMetadata(new object())); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Enumerable() { using var container = new StashboxContainer(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Runtime() { using var container = new StashboxContainer(); container.Register(config => config.WithFactory(d => new Test1(d))); Assert.Throws(container.Resolve); } [Fact] public void CircularDependencyTests_Runtime_Parameterized_Factory() { using var container = new StashboxContainer(); container.Register(config => config.WithFactory(t => (Test1)t)); Assert.Throws(container.Resolve); } #if HAS_ASYNC_DISPOSABLE [Fact] public async Task CircularDependencyTests_Runtime_Async() { await using var container = new StashboxContainer(); container.Register(config => config.WithFactory(t => new Test1(t))); Assert.Throws(container.Resolve); } [Fact] public async Task CircularDependencyTests_Runtime_Async_Parameterized_Factory() { await using var container = new StashboxContainer(); container.Register(config => config.WithFactory(t => (Test1)t)); Assert.Throws(container.Resolve); } #endif interface ITest1; interface ITest2; interface ITest3; interface ITest1; class Test4 : ITest1; class Test1 : ITest1 { public Test1(ITest1 test1) { } } class Test2 : ITest1 { [Dependency] public ITest1 Test1 { get; set; } } class Test3 : ITest1 { [InjectionMethod] public void Inject(ITest1 test1) { } } class Test1 : ITest1 { public Test1(ITest1 test1) { } } class Test2 : ITest1 { [Dependency] public ITest1 Test1 { get; set; } } class Test3 : ITest1 { [InjectionMethod] public void Inject(ITest1 test1) { } } class Test5 : ITest1 { public Test5(Func test1) { } } class Test6 : ITest1 { public Test6(Lazy test1) { } } class Test7 : ITest1 { public Test7(Tuple test1) { } } class Test8 : ITest1 { public Test8(IEnumerable test1) { } } } ================================================ FILE: test/CompilerTests/ConstantTests.cs ================================================ using System; using System.Linq.Expressions; using Xunit; namespace Stashbox.Tests.CompilerTests; public class ConstantTests { enum FakeEnum { Default } [Theory] [InlineData(2)] [InlineData(true)] [InlineData((byte)4)] [InlineData((char)5)] [InlineData((ushort)6)] [InlineData((uint)7)] [InlineData((ulong)8)] [InlineData((sbyte)9)] [InlineData((short)10)] [InlineData((long)11)] [InlineData("test")] [InlineData((float)12)] [InlineData((double)13)] [InlineData(typeof(object))] [InlineData(FakeEnum.Default)] public void Compile_Constant_Values(object expectedResult) { var func = expectedResult.AsConstant().ConvertTo(typeof(object)).AsLambda>().CompileFunc(); Assert.NotNull(func); Assert.Equal(expectedResult, func()); } } ================================================ FILE: test/CompilerTests/DefaultTests.cs ================================================ using System; using System.Linq.Expressions; using Xunit; namespace Stashbox.Tests.CompilerTests; public class DefaultTests { [Theory] [InlineData(typeof(int), default(int))] [InlineData(typeof(bool), default(bool))] [InlineData(typeof(byte), default(byte))] [InlineData(typeof(char), default(char))] [InlineData(typeof(ushort), default(ushort))] [InlineData(typeof(uint), default(uint))] [InlineData(typeof(ulong), default(ulong))] [InlineData(typeof(sbyte), default(sbyte))] [InlineData(typeof(short), default(short))] [InlineData(typeof(long), default(long))] [InlineData(typeof(string), default(string))] [InlineData(typeof(float), default(float))] [InlineData(typeof(double), default(double))] [InlineData(typeof(object), default)] public void Compile_Default_Values(Type type, object expectedResult) { var func = type.AsDefault().ConvertTo(typeof(object)).AsLambda>().CompileFunc(); Assert.NotNull(func); Assert.Equal(expectedResult, func()); } [Fact] public void Compile_DateTime_Default_Value() { var func = typeof(DateTime).AsDefault().AsLambda>().CompileFunc(); Assert.NotNull(func); Assert.Equal(default, func()); } [Fact] public void Compile_TimeSpan_Default_Value() { var func = typeof(TimeSpan).AsDefault().AsLambda>().CompileFunc(); Assert.NotNull(func); Assert.Equal(default, func()); } [Fact] public void Compile_Decimal_Default_Value() { var func = typeof(decimal).AsDefault().AsLambda>().CompileFunc(); Assert.NotNull(func); Assert.Equal(default, func()); } } ================================================ FILE: test/CompilerTests/NullableTests.cs ================================================ using System; using System.Linq.Expressions; using Xunit; namespace Stashbox.Tests.CompilerTests; public class NullableTests { [Fact] public void Compile_Nullable_Primitive() { Expression> expr = () => 5; var func = expr.CompileFunc(); Assert.Equal(5, func().Value); } [Fact] public void Compile_Nullable_Primitive_Null() { Expression> expr = () => null; var func = expr.CompileFunc(); Assert.False(func().HasValue); } [Fact] public void Compile_Nullable_ValueType() { Expression> expr = () => TimeSpan.FromSeconds(1); var func = expr.CompileFunc(); Assert.Equal(TimeSpan.FromSeconds(1), func().Value); } [Fact] public void Compile_Nullable_ValueType_Null() { Expression> expr = () => null; var func = expr.CompileFunc(); Assert.False(func().HasValue); } } ================================================ FILE: test/ComplexResolution.cs ================================================ using Stashbox.Configuration; using Stashbox.Tests.Utils; using Xunit; namespace Stashbox.Tests; public class ComplexResolution { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_Complex_Resolution_Works(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithDisposableTransientTracking().WithCompiler(compilerType)) .Register() .Register() .Register() .Register() .RegisterScoped() .RegisterScoped() .RegisterScoped() .RegisterSingleton() .RegisterSingleton() .RegisterSingleton() .Register(c => c.WithFactory(r => new Func1(r.Resolve(), r.Resolve(), r.Resolve()))) .Register(c => c.WithFactory(r => new Func2(r.Resolve(), r.Resolve(), r.Resolve()))); { using var scope = container.BeginScope(); var t1 = scope.Resolve(); Assert.NotNull(t1); Assert.NotNull(t1.T2); Assert.NotNull(t1.Single1); Assert.NotNull(t1.Scoped1); Assert.NotNull(t1.Scoped2); Assert.NotNull(t1.T2.T3); Assert.NotNull(t1.T2.T4); Assert.NotNull(t1.T2.Single1); Assert.NotNull(t1.T2.Single2); Assert.NotNull(t1.T2.Scoped1); Assert.NotNull(t1.T2.Scoped3); Assert.NotNull(t1.Single1.Single2); Assert.NotNull(t1.Single1.Single3); Assert.NotNull(t1.Scoped1.T4); Assert.NotNull(t1.Scoped1.Single1); Assert.NotNull(t1.Scoped1.Scoped2); Assert.NotNull(t1.Scoped1.Func1); Assert.NotNull(t1.Scoped2.T4); Assert.NotNull(t1.Scoped2.Single2); Assert.NotNull(t1.Scoped2.Single3); Assert.NotNull(t1.Scoped2.Scoped3); Assert.NotNull(t1.Scoped2.Func2); Assert.NotNull(t1.T2.T3.T4); Assert.NotNull(t1.T2.T3.Scoped1); Assert.NotNull(t1.T2.T3.Scoped3); Assert.NotNull(t1.T2.T3.Single3); Assert.NotNull(t1.Scoped1.Func1.T4); Assert.NotNull(t1.Scoped1.Func1.Scoped3); Assert.NotNull(t1.Scoped1.Func1.Single3); } { using var scope1 = container.BeginScope(); var t2 = scope1.Resolve(); Assert.NotNull(t2); Assert.NotNull(t2.T3); Assert.NotNull(t2.T4); Assert.NotNull(t2.Single1); Assert.NotNull(t2.Single2); Assert.NotNull(t2.Scoped1); Assert.NotNull(t2.Scoped3); Assert.NotNull(t2.Single1.Single2); Assert.NotNull(t2.Single1.Single3); Assert.NotNull(t2.Scoped1.T4); Assert.NotNull(t2.Scoped1.Single1); Assert.NotNull(t2.Scoped1.Scoped2); Assert.NotNull(t2.Scoped1.Func1); Assert.NotNull(t2.T3.T4); Assert.NotNull(t2.T3.Scoped1); Assert.NotNull(t2.T3.Scoped3); Assert.NotNull(t2.T3.Single3); Assert.NotNull(t2.Scoped1.Func1.T4); Assert.NotNull(t2.Scoped1.Func1.Scoped3); Assert.NotNull(t2.Scoped1.Func1.Single3); } } class T1 { public T1(T2 t2, Scoped1 scoped1, Scoped2 scoped2, Single1 single1) { T2 = t2; Scoped1 = scoped1; Scoped2 = scoped2; Single1 = single1; } public T2 T2 { get; } public Scoped1 Scoped1 { get; } public Scoped2 Scoped2 { get; } public Single1 Single1 { get; } } class T2 { public T2(T3 t3, T4 t4, Scoped1 scoped1, Scoped3 scoped3, Single1 single1, Single2 single2) { T3 = t3; T4 = t4; Scoped1 = scoped1; Scoped3 = scoped3; Single1 = single1; Single2 = single2; } public T3 T3 { get; } public T4 T4 { get; } public Scoped1 Scoped1 { get; } public Scoped3 Scoped3 { get; } public Single1 Single1 { get; } public Single2 Single2 { get; } } class T3 { public T3(T4 t4, Scoped1 scoped1, Scoped3 scoped3, Single3 single3) { T4 = t4; Scoped1 = scoped1; Scoped3 = scoped3; Single3 = single3; } public T4 T4 { get; } public Scoped1 Scoped1 { get; } public Scoped3 Scoped3 { get; } public Single3 Single3 { get; } } class T4; class Scoped1 { public Scoped1(T4 t4, Scoped2 scoped2, Func1 func1, Single1 single1) { T4 = t4; Scoped2 = scoped2; Func1 = func1; Single1 = single1; } public T4 T4 { get; } public Scoped2 Scoped2 { get; } public Func1 Func1 { get; } public Single1 Single1 { get; } } class Scoped2 { public Scoped2(T4 t4, Scoped3 scoped3, Func2 func2, Single2 single2, Single3 single3) { T4 = t4; Scoped3 = scoped3; Func2 = func2; Single2 = single2; Single3 = single3; } public T4 T4 { get; } public Scoped3 Scoped3 { get; } public Func2 Func2 { get; } public Single2 Single2 { get; } public Single3 Single3 { get; } } class Scoped3; class Single1 { public Single1(Single2 single2, Single3 single3) { Single2 = single2; Single3 = single3; } public Single2 Single2 { get; } public Single3 Single3 { get; } } class Single2 { public Single2(Single3 single3) { Single3 = single3; } public Single3 Single3 { get; } } class Single3; class Func1 { public Func1(T4 t4, Scoped3 scoped3, Single3 single3) { T4 = t4; Scoped3 = scoped3; Single3 = single3; } public T4 T4 { get; } public Scoped3 Scoped3 { get; } public Single3 Single3 { get; } } class Func2 { public Func2(T4 t4, Single1 single1, Single2 single2) { T4 = t4; Single1 = single1; Single2 = single2; } public T4 T4 { get; } public Single1 Single1 { get; } public Single2 Single2 { get; } } } ================================================ FILE: test/CompositionTests.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using TestAssembly; using Xunit; namespace Stashbox.Tests; public class CompositionTests { [Fact] public void Composition_Test() { using var container = new StashboxContainer() .Register() .Register() .Register() .RegisterDecorator(c => c.AsImplementedTypes()); var a = container.Resolve(); Assert.IsType(a); Assert.IsType(((D1)a).A); Assert.IsType(((D1)a).B); Assert.IsType(((D1)a).C); var b = container.Resolve(); Assert.IsType(b); Assert.IsType(((D1)b).A); Assert.IsType(((D1)b).B); Assert.IsType(((D1)b).C); var c = container.Resolve(); Assert.IsType(c); Assert.IsType(((D1)c).A); Assert.IsType(((D1)c).B); Assert.IsType(((D1)c).C); } [Fact] public void Composition_Test_Generic() { using var container = new StashboxContainer() .Register(typeof(IG<>), typeof(G<>)) .Register(typeof(IG1<>), typeof(G1<>)) .RegisterDecorator(typeof(D4<>)); var m = container.ContainerContext.DecoratorRepository.GetRegistrationMappings(); var a = container.Resolve>(); Assert.IsType>(a); Assert.IsType>(((D4)a).A); Assert.IsType>(((D4)a).B); var b = container.Resolve>(); Assert.IsType>(a); Assert.IsType>(((D4)a).A); Assert.IsType>(((D4)a).B); } [Fact] public void Composition_Test_MultipleDecorators() { using var container = new StashboxContainer() .Register() .Register() .Register() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator(); var d = container.Resolve(); Assert.IsType(d); Assert.IsType(((D1)d).A); Assert.IsType(((AD)((D1)d).A).A); Assert.IsType(((D1)d).B); Assert.IsType(((BD)((D1)d).B).B); Assert.IsType(((D1)d).C); Assert.IsType(((CD)((D1)d).C).C); } [Fact] public void Composition_Test_MultipleDecorators_Factory() { using var container = new StashboxContainer() .Register() .Register() .Register() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator(c => c.WithFactory((a, a1, b, c) => { Assert.IsType(a); Assert.IsType(((AD)a).A); Assert.IsType(a1); Assert.IsType(((AD)a1).A); Assert.IsType(b); Assert.IsType(((BD)b).B); Assert.IsType(c); Assert.IsType(((CD)c).C); return new D1(a, b, c); })); container.Resolve(); } [Fact] public void Composition_Test_MultipleDecorators_Non_Generic_Factory() { using var container = new StashboxContainer() .Register() .Register() .Register() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator() .RegisterDecorator(typeof(D1), c => c.WithFactory((a, a1, b, c) => { Assert.IsType(a); Assert.IsType(((AD)a).A); Assert.IsType(a1); Assert.IsType(((AD)a1).A); Assert.IsType(b); Assert.IsType(((BD)b).B); Assert.IsType(c); Assert.IsType(((CD)c).C); return new D1(a, b, c); })); container.Resolve(); } [Fact] public void Composition_Test_Enumerable() { using var container = new StashboxContainer() .Register() .Register() .RegisterDecorator(); var d = container.Resolve(); Assert.IsType(d); Assert.IsAssignableFrom>(((D2)d).A); Assert.IsType(((D2)d).A.ElementAt(0)); Assert.IsType(((D2)d).A.ElementAt(1)); } [Fact] public void Composition_Test_Enumerable_MultipleDecorators() { using var container = new StashboxContainer() .Register() .Register() .RegisterDecorator() .RegisterDecorator(); var d = container.Resolve(); Assert.IsType(d); Assert.IsType(((D2)d).A.ElementAt(0)); Assert.IsType(((AD)((D2)d).A.ElementAt(0)).A); Assert.IsType(((D2)d).A.ElementAt(1)); Assert.IsType(((AD)((D2)d).A.ElementAt(1)).A); } [Fact] public void Composition_Test_Enumerable_Generic() { using var container = new StashboxContainer() .Register(typeof(IG<>), typeof(G<>)) .Register(typeof(IG<>), typeof(GG<>)) .RegisterDecorator(typeof(IG<>), typeof(D3<>)); var d = container.Resolve>(); Assert.IsType>(d); Assert.IsType>(((D3)d).G.ElementAt(0)); Assert.IsType>(((D3)d).G.ElementAt(1)); } [Fact] public void Composition_Named_From_DifferentAssembly() { using var container = new StashboxContainer() .ComposeAssembly(typeof(Composition).Assembly); container.RegisterFunc((name, resolver) => container.IsRegistered("Comp"), name: "IsTokenProviderRegistered"); var factory = container.Resolve>("IsTokenProviderRegistered"); Assert.True(factory("Comp")); } interface IA; interface IB; interface IC; interface IG; interface IG1; class A : IA; class AA : IA; class B : IB; class BB : IB; class C : IC; class CC : IC; class G : IG; class G1 : IG1; class GG/*WP*/ : IG; class D1 : IA, IB, IC { public D1(IA a, IB b, IC c) { A = a; B = b; C = c; } public IA A { get; } public IB B { get; } public IC C { get; } } class D2 : IA { public D2(IEnumerable a) { A = a; } public IEnumerable A { get; } } class D3 : IG { public D3(IEnumerable> g) { G = g; } public IEnumerable> G { get; } } class D4 : IG, IG1 { public D4(IG a, IG1 b) { A = a; B = b; } public IG A { get; } public IG1 B { get; } } class AD : IA { public AD(IA a) { A = a; } public IA A { get; } } class BD : IB { public BD(IB b) { B = b; } public IB B { get; } } class CD : IC { public CD(IC c) { C = c; } public IC C { get; } } } ================================================ FILE: test/ConditionalTests.cs ================================================ using Stashbox.Attributes; using System; using Xunit; namespace Stashbox.Tests; public class ConditionalTests { [Fact] public void ConditionalTests_ParentTypeCondition_First() { var container = new StashboxContainer(); container.Register(context => context.WhenDependantIs()); container.Register(); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_When_First() { var container = new StashboxContainer(); container.Register(context => context.When(type => type.ParentType == typeof(Test2))); container.Register(); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_First_NonGeneric() { var container = new StashboxContainer(); container.Register(context => context.WhenDependantIs(typeof(Test2))); container.Register(); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Second() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WhenDependantIs()); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Second_NonGeneric() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WhenDependantIs(typeof(Test2))); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Third() { var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(context => context.WhenDependantIs()); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Third_NonGeneric() { var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(context => context.WhenDependantIs(typeof(Test2))); container.Register(); var test2 = container.Resolve(); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Third_WithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(context => context.WhenDependantIs(name)); container.Register(name); var test2 = container.Resolve(name); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_ParentTypeCondition_Third_NonGenericWithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(context => context.WhenDependantIs(typeof(Test2), name)); container.Register(name); var test2 = container.Resolve(name); Assert.IsType(test2.test1); Assert.IsType(test2.test12); } [Fact] public void ConditionalTests_AttributeCondition_First() { var container = new StashboxContainer(); container.Register(context => context.WhenHas()); container.Register(); container.Register(context => context.WhenHas()); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_First_NonGeneric() { var container = new StashboxContainer(); container.Register(context => context.WhenHas(typeof(TestConditionAttribute))); container.Register(); container.Register(context => context.WhenHas(typeof(TestCondition2Attribute))); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Second() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WhenHas()); container.Register(context => context.WhenHas()); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Second_NonGeneric() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WhenHas(typeof(TestCondition2Attribute))); container.Register(context => context.WhenHas(typeof(TestConditionAttribute))); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Third() { var container = new StashboxContainer(); container.Register(context => context.WhenHas()); container.Register(context => context.WhenHas()); container.Register(); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Third_NonGeneric() { var container = new StashboxContainer(); container.Register(context => context.WhenHas(typeof(TestCondition2Attribute))); container.Register(context => context.WhenHas(typeof(TestConditionAttribute))); container.Register(); container.Register(); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Third_WithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(context => context.WhenHas().WithName(name)); container.Register(context => context.WhenHas().WithName("B")); container.Register(); container.Register(context => context.WithDependencyBinding(name)); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_AttributeCondition_Third_NonGeneric_WithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(context => context.WhenHas(typeof(TestCondition2Attribute)).WithName(name)); container.Register(context => context.WhenHas(typeof(TestConditionAttribute)).WithName("B")); container.Register(); container.Register(context => context.WithDependencyBinding(name)); var test3 = container.Resolve(); Assert.IsType(test3.test1); Assert.IsType(test3.test12); } [Fact] public void ConditionalTests_Combined() { var container = new StashboxContainer(); container.Register(context => context.WhenDependantIs().WhenDependantIs()); container.Register(); container.Register(); container.Register(); container.Register(); var t1 = container.Resolve(); var t2 = container.Resolve(); Assert.IsType(t1.Test); Assert.IsType(t2.Test); } [Fact] public void ConditionalTests_Combined_Common() { var container = new StashboxContainer(); container.Register(context => context.When(t => t.ParentType == typeof(Test4)).When(t => t.ParentType == typeof(Test5))); container.Register(); container.Register(); container.Register(); container.Register(); var t1 = container.Resolve(); var t2 = container.Resolve(); Assert.IsType(t1.Test); Assert.IsType(t2.Test); } [Fact] public void ConditionalTests_InResolutionPath() { var container = new StashboxContainer(); container.Register(context => context.WhenInResolutionPathOf()); container.Register(); container.Register(); var t = container.Resolve(); Assert.NotNull(((Test13)t.Test).Dummy); } [Fact] public void ConditionalTests_InResolutionPath_Attribute() { var container = new StashboxContainer(); container.Register(context => context.WhenResolutionPathHas()); container.Register(); container.Register(); var t = container.Resolve(); Assert.NotNull(((Test13)t.Test).Dummy); } [Fact] public void ConditionalTests_InResolutionPath_WithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(context => context.WhenInResolutionPathOf(name)); container.Register(name); container.Register(); var t = container.Resolve(name); Assert.NotNull(((Test13)t.Test).Dummy); } [Fact] public void ConditionalTests_InResolutionPath_Attribute_WithName() { var name = "A".ToLower(); var container = new StashboxContainer(); container.Register(context => context.WhenResolutionPathHas(name)); container.Register(context => context.WithDependencyBinding(name)); container.Register(name); var t = container.Resolve(); Assert.NotNull(((Test13)t.Test).Dummy); } interface ITest1; interface ITest2 { ITest1 test1 { get; set; } ITest1 test12 { get; set; } } class Dummy; class Test1 : ITest1; class Test11 : ITest1; class Test12 : ITest1; class Test13 : ITest1 { public Test13(Dummy dummy) { Dummy = dummy; } public Dummy Dummy { get; } } class Test2 : ITest2 { [Dependency] public ITest1 test1 { get; set; } public ITest1 test12 { get; set; } public Test2(ITest1 test12) { this.test12 = test12; } } class Test3 : ITest2 { [Dependency, TestCondition] public ITest1 test1 { get; set; } public ITest1 test12 { get; set; } public Test3([TestCondition2] ITest1 test12) { this.test12 = test12; } } class Test4 { public Test4(ITest1 test) { Test = test; } public ITest1 Test { get; } } class Test5 { public Test5(ITest1 test) { Test = test; } public ITest1 Test { get; } } class Test6 { public Test6([TestCondition]ITest1 test) { Test = test; } public ITest1 Test { get; } } [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter)] class TestConditionAttribute : Attribute; [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Parameter)] class TestCondition2Attribute : Attribute; } ================================================ FILE: test/ConfigurationTests.cs ================================================ using Stashbox.Configuration; using Xunit; namespace Stashbox.Tests; public class ConfigurationTests { [Fact] public void Ensure_Unknown_Type_Configurations_Working() { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution()); Assert.True(container.ContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled); Assert.Null(container.ContainerContext.ContainerConfiguration.UnknownTypeConfigurator); container.Configure(c => c.WithUnknownTypeResolution(co => { })); Assert.True(container.ContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled); Assert.NotNull(container.ContainerContext.ContainerConfiguration.UnknownTypeConfigurator); container.Configure(c => c.WithUnknownTypeResolution(enabled: false)); Assert.False(container.ContainerContext.ContainerConfiguration.UnknownTypeResolutionEnabled); } [Fact] public void Ensure_Auto_Member_Injection_Configurations_Working() { using var container = new StashboxContainer(c => c.WithAutoMemberInjection()); Assert.True(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionEnabled); Assert.Equal(Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, container.ContainerContext.ContainerConfiguration.AutoMemberInjectionRule); Assert.Null(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionFilter); container.Configure(c => c.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields)); Assert.True(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionEnabled); Assert.Equal(Rules.AutoMemberInjectionRules.PrivateFields, container.ContainerContext.ContainerConfiguration.AutoMemberInjectionRule); Assert.Null(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionFilter); container.Configure(c => c.WithAutoMemberInjection(filter: m => true)); Assert.True(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionEnabled); Assert.Equal(Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, container.ContainerContext.ContainerConfiguration.AutoMemberInjectionRule); Assert.NotNull(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionFilter); container.Configure(c => c.WithAutoMemberInjection(enabled: false)); Assert.False(container.ContainerContext.ContainerConfiguration.AutoMemberInjectionEnabled); } [Fact] public void Ensure_Feature_Configurations_Working() { using var container = new StashboxContainer(); container.Configure(c => c.WithDefaultValueInjection()); Assert.True(container.ContainerContext.ContainerConfiguration.DefaultValueInjectionEnabled); container.Configure(c => c.WithDefaultValueInjection(false)); Assert.False(container.ContainerContext.ContainerConfiguration.DefaultValueInjectionEnabled); container.Configure(c => c.WithDisposableTransientTracking()); Assert.True(container.ContainerContext.ContainerConfiguration.TrackTransientsForDisposalEnabled); container.Configure(c => c.WithDisposableTransientTracking(false)); Assert.False(container.ContainerContext.ContainerConfiguration.TrackTransientsForDisposalEnabled); container.Configure(c => c.WithLifetimeValidation()); Assert.True(container.ContainerContext.ContainerConfiguration.LifetimeValidationEnabled); container.Configure(c => c.WithLifetimeValidation(false)); Assert.False(container.ContainerContext.ContainerConfiguration.LifetimeValidationEnabled); container.Configure(c => c.WithExpressionCompiler(Rules.ExpressionCompilers.MicrosoftExpressionCompiler)); Assert.NotNull(container.ContainerContext.ContainerConfiguration.ExternalExpressionCompiler); container.Configure(c => c.WithExpressionCompiler(null)); Assert.Null(container.ContainerContext.ContainerConfiguration.ExternalExpressionCompiler); Assert.True(container.ContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled); container.Configure(c => c.WithNamedDependencyResolutionForUnNamedRequests()); Assert.True(container.ContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedRequestsEnabled); container.Configure(c => c.WithNamedDependencyResolutionForUnNamedRequests(false, false)); Assert.False(container.ContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedRequestsEnabled); Assert.False(container.ContainerContext.ContainerConfiguration.NamedDependencyResolutionForUnNamedCollectionRequestsEnabled); container.Configure(c => c.TreatParameterAndMemberNameAsDependencyName()); Assert.True(container.ContainerContext.ContainerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled); container.Configure(c => c.TreatParameterAndMemberNameAsDependencyName(false)); Assert.False(container.ContainerContext.ContainerConfiguration.TreatingParameterAndMemberNameAsDependencyNameEnabled); } [Fact] public void Ensure_Configuration_Change_Does_Not_Affect_Parent() { using var container = new StashboxContainer(c => c.WithLifetimeValidation()); Assert.True(container.ContainerContext.ContainerConfiguration.LifetimeValidationEnabled); using var child = container.CreateChildContainer(); child.Configure(c => c.WithLifetimeValidation(false)); Assert.True(container.ContainerContext.ContainerConfiguration.LifetimeValidationEnabled); Assert.False(child.ContainerContext.ContainerConfiguration.LifetimeValidationEnabled); } } ================================================ FILE: test/ConstructorSelectionTests.cs ================================================ using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests; public class ConstructorSelectionTests { [Fact] public void ConstructorSelectionTests_ArgTypes() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(context => context.WithConstructorByArgumentTypes(typeof(Dep), typeof(Dep2))); Assert.NotNull(container.Resolve()); } [Fact] public void ConstructorSelectionTests_Args() { using var container = new StashboxContainer(); var dep = new Dep(); var dep2 = new Dep2(); container.Register(context => context.WithConstructorByArguments(dep, dep2)); var test = container.Resolve(); Assert.Same(dep, test.Dep); Assert.Same(dep2, test.Dep2); } [Fact] public void ConstructorSelectionTests_ArgTypes_Throws_ResolutionFailed() { using var container = new StashboxContainer(); container.Register(context => context.WithConstructorByArgumentTypes(typeof(Dep), typeof(Dep2))); var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test), exception.Type); } [Fact] public void ConstructorSelectionTests_ArgTypes_Throws_MissingConstructor() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.WithConstructorByArgumentTypes())); } [Fact] public void ConstructorSelectionTests_Args_Throws_MissingConstructor() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.WithConstructorByArguments())); } [Fact] public void ConstructorSelectionTests_Args_Throws_MissingConstructor_OneParam() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.WithConstructorByArguments(new object()))); } [Fact] public void ConstructorSelectionTests_Args_Throws_MissingConstructor_MoreParams() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.WithConstructorByArguments(new object(), new object()))); } [Fact] public void ConstructorSelectionTests_Decorator_ArgTypes() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.RegisterDecorator(context => context.WithConstructorByArgumentTypes(typeof(Dep), typeof(Dep2))); var test = container.Resolve(); Assert.IsType(test); } [Fact] public void ConstructorSelectionTests_Decorator_Args() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var dep = new Dep(); var dep2 = new Dep2(); container.RegisterDecorator(context => context.WithConstructorByArguments(dep, dep2)); var test = container.Resolve(); Assert.Same(dep, ((DepDecorator)test).Dep); Assert.Same(dep2, ((DepDecorator)test).Dep2); } [Fact] public void ConstructorSelectionTests_Arg_ByInterface() { using var container = new StashboxContainer(); var arg = new Arg(); var arg1 = new Arg1(); container.Register(context => context.WithConstructorByArguments(arg, arg1)); var test = container.Resolve(); Assert.Same(arg, test.PArg); Assert.Same(arg1, test.PArg1); } class Dep; class Dep2; class Dep3; class DepDecorator : Dep { public Dep Dep { get; } public Dep2 Dep2 { get; } public DepDecorator(Dep dep) { Assert.True(false, "wrong constructor"); } public DepDecorator(Dep dep, Dep2 dep2) { this.Dep = dep; this.Dep2 = dep2; } public DepDecorator(Dep dep, Dep2 dep2, Dep3 dep3) { Assert.True(false, "wrong constructor"); } } class Test { public Dep Dep { get; } public Dep2 Dep2 { get; } public Test(Dep dep) { Assert.True(false, "wrong constructor"); } public Test(Dep dep, Dep2 dep2) { this.Dep = dep; this.Dep2 = dep2; } public Test(Dep dep, Dep2 dep2, Dep3 dep3) { Assert.True(false, "wrong constructor"); } } interface IArg; interface IArg1; class Arg : IArg; class Arg1 : IArg1; class Test1 { public IArg PArg { get; } public IArg1 PArg1 { get; } public Test1(IArg arg, IArg1 arg1) { this.PArg = arg; this.PArg1 = arg1; } } } ================================================ FILE: test/ContainerTests.cs ================================================ using Moq; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Lifetime; using Stashbox.Resolution; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using Xunit; namespace Stashbox.Tests; public class ContainerTests { [Fact] public void ContainerTests_ChildContainer() { var container = new StashboxContainer(); container.Register(); container.Register(); var child = container.CreateChildContainer(); child.Register(); var test3 = child.Resolve(); Assert.NotNull(test3); Assert.IsType(test3); Assert.Equal(container.ContainerContext, child.ContainerContext.ParentContext); } [Fact] public void ContainerTests_ChildContainer_ResolveFromParent() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer(); var test1 = child.Resolve(); Assert.NotNull(test1); Assert.IsType(test1); } [Fact] public void ContainerTests_Validate_MissingDependency() { using var container = new StashboxContainer(); container.Register(); var agr = Assert.Throws(() => container.Validate()); Assert.IsType(agr.InnerExceptions[0]); } [Fact] public void ContainerTests_Validate_CircularDependency() { using var container = new StashboxContainer(); container.Register(); container.Register(); var agr = Assert.Throws(() => container.Validate()); Assert.IsType(agr.InnerExceptions[0]); } [Fact] public void ContainerTests_Validate_Ok() { using var container = new StashboxContainer(); container.Register(); container.Register(); container.Validate(); } [Fact] public void ContainerTests_Validate_Skips_Open_Generic() { using var container = new StashboxContainer(); container.Register(typeof(TestOpenGeneric<>)); container.Validate(); } [Fact] public void ContainerTests_Validate_Throws_When_No_Public_Constructor_Found() { using var container = new StashboxContainer(); container.Register(); var agr = Assert.Throws(() => container.Validate()); Assert.IsType(agr.InnerExceptions[0]); } [Fact] public void ContainerTests_Validate_Throws_Multiple() { using var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var agr = Assert.Throws(() => container.Validate()); Assert.Equal(3, agr.InnerExceptions.Count); Assert.Equal(3, agr.InnerExceptions.Count(e => e is ResolutionFailedException)); } [Fact] public void ContainerTests_Ensure_Validate_Does_Not_Build_Singletons() { using var container = new StashboxContainer(); container.RegisterSingleton(); container.Validate(); var reg = container.GetRegistrationMappings().First(r => r.Key == typeof(Test1)); var t = new Test1(); var res = container.ContainerContext.RootScope.GetOrAddScopedObject(reg.Value.RegistrationId, (s, r) => t, null, typeof(Test1)); Assert.Same(t, res); } [Fact] public void ContainerTests_CheckRegistration() { using var container = new StashboxContainer(); container.Register(); var reg = container.GetRegistrationMappings().FirstOrDefault(r => r.Key == typeof(ITest1)); Assert.Equal(typeof(Test1), reg.Value.ImplementationType); reg = container.GetRegistrationMappings().FirstOrDefault(r => r.Value.ImplementationType == typeof(Test1)); Assert.Equal(typeof(Test1), reg.Value.ImplementationType); } [Fact] public void ContainerTests_CanResolve() { var container = new StashboxContainer(); container.Register(); container.Register(); var child = container.CreateChildContainer(); Assert.True(container.CanResolve()); Assert.True(container.CanResolve(typeof(ITest2))); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>()); Assert.True(container.CanResolve>()); Assert.True(child.CanResolve()); Assert.True(child.CanResolve(typeof(ITest2))); Assert.True(child.CanResolve>()); Assert.True(child.CanResolve>()); Assert.True(child.CanResolve>()); Assert.True(child.CanResolve>()); Assert.False(container.CanResolve()); Assert.False(container.CanResolve("test")); Assert.False(container.CanResolve(typeof(ITest1), "test")); } [Fact] public void ContainerTests_IsRegistered() { var container = new StashboxContainer(); container.Register(); container.Register(context => context.WithName("test")); var child = container.CreateChildContainer(); Assert.True(container.IsRegistered()); Assert.True(container.IsRegistered("test")); Assert.True(container.IsRegistered(typeof(ITest1))); Assert.False(container.IsRegistered>()); Assert.False(container.IsRegistered>()); Assert.False(container.IsRegistered>()); Assert.False(child.IsRegistered()); Assert.False(child.IsRegistered(typeof(ITest1))); Assert.False(child.IsRegistered>()); } [Fact] public void ContainerTests_IsRegistered_VariableName() { var name = "name".ToLower(); var container = new StashboxContainer(); container.Register(c => c.WithName("name")); Assert.True(container.IsRegistered(name)); } [Fact] public void ContainerTests_IsRegistered_OpenGeneric() { var container = new StashboxContainer() .Register() .Register(typeof(TestOpenGeneric<>)); Assert.True(container.IsRegistered(typeof(TestOpenGeneric<>))); Assert.False(container.IsRegistered>()); } [Fact] public void ContainerTests_ResolverTest() { var container = new StashboxContainer(); container.RegisterResolver(new TestResolver()); var inst = container.Resolve(); Assert.IsType(inst); } [Fact] public void ContainerTests_ResolverTest_SupportsMany() { var container = new StashboxContainer(); container.RegisterResolver(new TestResolver2()); var inst = container.Resolve>(); Assert.IsType(inst.First()); } [Fact] public void ContainerTests_ResolverTest_Null_Context_DoesNot_Break() { var container = new StashboxContainer(); var mockResolver = new Mock(); mockResolver.Setup(r => r.CanUseForResolution(It.IsAny(), It.IsAny())).Returns(true); mockResolver.Setup(r => r.GetExpression(It.IsAny(), It.IsAny(), It.IsAny())).Returns(null); container.RegisterResolver(mockResolver.Object); Assert.Throws(() => container.Resolve()); mockResolver.Verify(r => r.GetExpression(It.IsAny(), It.IsAny(), It.IsAny()), Times.Once); } [Fact] public void ContainerTests_UnknownType_Config() { var container = new StashboxContainer(config => config .WithUnknownTypeResolution(context => context.WithSingletonLifetime())); container.Resolve(); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().ToArray(); Assert.Single(regs); Assert.Equal(typeof(Test1), regs[0].Key); Assert.True(regs[0].Value.Lifetime is SingletonLifetime); } [Fact] public void ContainerTests_ChildContainer_Singleton() { var container = new StashboxContainer(); container.RegisterSingleton(); var child = container.CreateChildContainer(); child.RegisterSingleton(); var test = child.Resolve(); Assert.NotNull(test); Assert.IsType(test); } [Fact] public void ContainerTests_ChildContainer_Scoped() { var container = new StashboxContainer(); container.RegisterScoped(); var child = container.CreateChildContainer(); child.Register(); var test = child.BeginScope().Resolve(); Assert.NotNull(test); Assert.IsType(test); } [Fact] public void ContainerTests_ConfigurationChanged() { ContainerConfiguration newConfig = null; var container = new StashboxContainer(config => config.OnContainerConfigurationChanged(nc => newConfig = nc)); container.Configure(config => config.WithUnknownTypeResolution()); Assert.True(newConfig.UnknownTypeResolutionEnabled); } [Fact] public void ContainerTests_ConfigurationChange_Null() { Assert.Throws(() => new StashboxContainer().Configure(null)); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Throws() { var exception = Assert.Throws(() => new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException)) .Register().Register()); Assert.Equal(typeof(S), exception.Type); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Does_Not_Throw_On_Replace() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException)); container.Register().Register(c => c.ReplaceExisting()); Assert.Single(container.GetRegistrationDiagnostics().Where(r => r.ServiceType == typeof(S))); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Does_Not_Throw_On_Replace_Only_If_Exists() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException)); container.Register().Register(c => c.ReplaceOnlyIfExists()); Assert.Single(container.GetRegistrationDiagnostics().Where(r => r.ServiceType == typeof(S))); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Does_Not_Throw_On_Different_Implementation() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ThrowException)); container.Register().Register(); Assert.Equal(2, container.GetRegistrationDiagnostics().Count(r => r.ServiceType == typeof(ITest1))); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Skip() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.SkipDuplications)) .Register(c => c.WithInitializer((s, r) => s.Id = 0)).Register(c => c.WithInitializer((s, r) => s.Id = 1)); var regs = container.GetRegistrationMappings(); Assert.Single(regs); Assert.Equal(0, container.Resolve().Id); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Preserve() { var regs = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)) .Register().Register().GetRegistrationDiagnostics(); Assert.Equal(2, regs.Count()); } [Fact] public void ContainerTests_Diagnostics_Print() { var regs = new StashboxContainer() .Register("t1").Register().GetRegistrationDiagnostics().Select(d => d.ToString()).OrderBy(s => s).ToArray(); Assert.Equal("ITest1 => Test1, name: t1", regs[0]); Assert.Equal("Test1 => Test1, name: null", regs[1]); } [Fact] public void ContainerTests_Diagnostics_Generic_Print() { var regs = new StashboxContainer() .Register(typeof(TestOpenGeneric<>), c => c.WithName("t1")) .Register(typeof(TestOpenGeneric<>)).GetRegistrationDiagnostics() .Select(d => d.ToString()).OrderBy(s => s) .ToArray(); Assert.Equal("TestOpenGeneric<> => TestOpenGeneric<>, name: null", regs[0]); Assert.Equal("TestOpenGeneric<> => TestOpenGeneric<>, name: t1", regs[1]); } [Fact] public void ContainerTests_Cache_Diag() { using var container = new StashboxContainer() .Register("t1").Register().Register(); container.Resolve(); container.Resolve("t1"); container.Resolve(); container.Resolve(ResolutionBehavior.Current); var cache = container.GetDelegateCacheEntries().OrderBy(c => c.ServiceType.FullName).ToArray(); Assert.Equal(3, cache.Length); Assert.Equal(typeof(ITest1), cache[0].ServiceType); Assert.Equal(typeof(Test1), cache[1].ServiceType); Assert.Equal(typeof(Test1), cache[2].ServiceType); Assert.IsType(cache[0].CachedDelegate?.Invoke(container.ContainerContext.RootScope, null)); Assert.IsType(cache[1].CachedDelegate?.Invoke(container.ContainerContext.RootScope, null)); Assert.IsType(cache[2].CachedDelegate?.Invoke(container.ContainerContext.RootScope, null)); Assert.Single(cache[0].NamedCacheEntries ?? new NamedCacheEntry[]{}); Assert.Null(cache[1].NamedCacheEntries); Assert.Null(cache[2].NamedCacheEntries); Assert.Equal("t1", cache[0].NamedCacheEntries?.First().Name); Assert.Equal(ResolutionBehavior.Default, cache[0].ResolutionBehavior); Assert.Equal(ResolutionBehavior.Current, cache[1].ResolutionBehavior); Assert.Equal(ResolutionBehavior.Default, cache[2].ResolutionBehavior); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Preserve_Cache_Invalidates() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)) .Register(); var a = container.Resolve(); container.Register(); a = container.Resolve(); Assert.IsType(a); } [Fact] public void ContainerTests_Configuration_DuplicatedBehavior_Replace() { using var container = new StashboxContainer(c => c.WithRegistrationBehavior(Rules.RegistrationBehavior.ReplaceExisting)) .Register(c => c.WithInitializer((s, r) => s.Id = 0)).Register(c => c.WithInitializer((s, r) => s.Id = 1)); var regs = container.GetRegistrationMappings(); Assert.Single(regs); Assert.Equal(1, container.Resolve().Id); } [Fact] public void ContainerTests_Throws_When_TypeMap_Invalid() { using var container = new StashboxContainer(); Assert.Equal(typeof(Test2), Assert.Throws(() => container.Register(typeof(ITest1), typeof(Test2))).Type); Assert.Equal(typeof(Test2), Assert.Throws(() => container.RegisterDecorator(typeof(ITest1), typeof(Test2))).Type); Assert.Equal(typeof(Test2), Assert.Throws(() => container.RegisterDecorator(typeof(Test2))).Type); Assert.Equal(typeof(Test2), Assert.Throws(() => container.ReMapDecorator(typeof(Test2))).Type); } [Fact] public void ContainerTests_ChildContainer_Rebuild_Singletons_In_Child() { using var container = new StashboxContainer(); container.RegisterSingleton(); var a = container.Resolve(); var b = container.Resolve(); using var child = container.CreateChildContainer(); var c = child.Resolve(); Assert.Same(a, b); Assert.Same(a, c); Assert.Same(b, c); child.Configure(c => c.WithReBuildSingletonsInChildContainer()); c = child.Resolve(); Assert.Same(a, b); Assert.NotSame(a, c); Assert.NotSame(b, c); } [Fact] public void ContainerTests_ChildContainer_Rebuild_Singletons_In_Child_Deps() { using var container = new StashboxContainer(c => c.WithReBuildSingletonsInChildContainer()); container.Register(); container.RegisterSingleton(); var a = container.Resolve(); Assert.IsType(a.Test1); using var child = container.CreateChildContainer(); child.Register(); var b = child.Resolve(); Assert.IsType(b.Test1); } [Fact] public void ContainerTests_ChildContainer_Rebuild_Singletons_In_Child_Deps_Config_On_Child() { using var container = new StashboxContainer(); container.Register(); container.RegisterSingleton(); var a = container.Resolve(); Assert.IsType(a.Test1); using var child = container.CreateChildContainer(c => c.WithReBuildSingletonsInChildContainer()); child.Register(); var b = child.Resolve(); Assert.IsType(b.Test1); } [Fact] public void ContainerTests_Throws_Disposed_Exceptions() { var container = new StashboxContainer(); var scope = container.BeginScope(); scope.Dispose(); Assert.Throws(() => scope.Activate(this.GetType())); Assert.Throws(() => scope.BeginScope()); Assert.Throws(() => scope.BuildUp(new object())); Assert.Throws(() => scope.CanResolve(this.GetType())); Assert.Throws(() => scope.GetService(this.GetType())); Assert.Throws(() => scope.PutInstanceInScope(this.GetType())); Assert.Throws(() => scope.Resolve(this.GetType())); Assert.Throws(() => scope.ResolveAll(this.GetType())); Assert.Throws(() => scope.ResolveFactory(this.GetType())); Assert.ThrowsAsync(async () => await scope.InvokeAsyncInitializers()); container.Dispose(); Assert.Throws(() => container.Activate(this.GetType())); Assert.Throws(() => container.BeginScope()); Assert.Throws(() => container.BuildUp(new object())); Assert.Throws(() => container.CanResolve(this.GetType())); Assert.Throws(() => container.ComposeAssemblies(new[] { this.GetType().Assembly })); Assert.Throws(() => container.ComposeAssembly(this.GetType().Assembly)); Assert.Throws(() => container.ComposeBy(this.GetType())); Assert.Throws(() => container.Configure(c => { })); Assert.Throws(() => container.CreateChildContainer()); Assert.Throws(() => container.GetRegistrationMappings()); Assert.Throws(() => container.GetService(this.GetType())); Assert.Throws(() => container.IsRegistered(this.GetType())); Assert.Throws(() => container.PutInstanceInScope(this.GetType())); Assert.Throws(() => container.Register(this.GetType())); Assert.Throws(() => container.RegisterAssemblies(new[] { this.GetType().Assembly })); Assert.Throws(() => container.RegisterAssembly(this.GetType().Assembly)); Assert.Throws(() => container.RegisterAssemblyContaining()); Assert.Throws(() => container.RegisterDecorator(this.GetType(), this.GetType())); Assert.Throws(() => container.RegisterFunc(r => new Test1())); Assert.Throws(() => container.RegisterInstance(new object())); Assert.Throws(() => container.RegisterInstances(new object())); Assert.Throws(() => container.RegisterResolver(null)); Assert.Throws(() => container.RegisterScoped()); Assert.Throws(() => container.RegisterSingleton()); Assert.Throws(() => container.RegisterTypes(new[] { this.GetType() })); Assert.Throws(() => container.RegisterTypesAs(this.GetType().Assembly)); Assert.Throws(() => container.ReMap()); Assert.Throws(() => container.ReMapDecorator(this.GetType(), this.GetType())); Assert.Throws(() => container.Resolve(this.GetType())); Assert.Throws(() => container.ResolveOrDefault(this.GetType())); Assert.Throws(() => container.ResolveAll(this.GetType())); Assert.Throws(() => container.ResolveFactory(this.GetType())); Assert.Throws(() => container.ResolveFactoryOrDefault(this.GetType())); Assert.Throws(() => container.Validate()); Assert.Throws(() => container.WireUp(new object())); Assert.ThrowsAsync(async () => await container.InvokeAsyncInitializers()); } interface ITest1; interface ITest2; interface ITest3; interface ITest5 { Func Func { get; } Lazy Lazy { get; } IEnumerable Enumerable { get; } Tuple Tuple { get; } } class Test1 : ITest1; class Test11 : ITest1; class Test2 : ITest2 { public Test2(ITest1 test1) { Test1 = test1; } public ITest1 Test1 { get; } } class Test3 : ITest3 { public Test3(ITest1 test1, ITest2 test2) { } } class Test4 : ITest1 { public Test4(ITest3 test3) { } } class Test5 : ITest5 { public Test5(Func func, Lazy lazy, IEnumerable enumerable, Tuple tuple) { Func = func; Lazy = lazy; Enumerable = enumerable; Tuple = tuple; } public Func Func { get; } public Lazy Lazy { get; } public IEnumerable Enumerable { get; } public Tuple Tuple { get; } } class TestResolver : IServiceResolver { public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionInfo) { return typeInfo.Type == typeof(ITest1); } public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionInfo) { return new ServiceContext(Expression.Constant(new Test1()), null); } } class TestResolver2 : IEnumerableSupportedResolver { public bool CanUseForResolution(TypeInformation typeInfo, ResolutionContext resolutionInfo) { return typeInfo.Type == typeof(ITest1); } public ServiceContext GetExpression( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionInfo) { return new ServiceContext(Expression.Constant(new Test1()), null); } public IEnumerable GetExpressionsForEnumerableRequest( IResolutionStrategy resolutionStrategy, TypeInformation typeInfo, ResolutionContext resolutionInfo) { return new ServiceContext[] { new ServiceContext(Expression.Constant(new Test1()), null) }; } } class S { public int Id { get; set; } } class TestOpenGeneric; class NoPublicConstructor { protected NoPublicConstructor() { } } } ================================================ FILE: test/DataTests/TreeTests.cs ================================================ using System; using System.Collections.Generic; using Stashbox.Tests.Utils; using Stashbox.Utils.Data; using Stashbox.Utils.Data.Immutable; using Xunit; namespace Stashbox.Tests.DataTests; public class TreeTests { [Fact] public void Test_Collision_Check() { var key1 = new H1(); var key2 = new H2(); var a = new A(); var b = new B(); var c = new C(); var imTree = ImmutableTree>.Empty; var tree = new HashTree>(); imTree = imTree.AddOrUpdate(key1, [a], false); imTree = imTree.AddOrUpdate(key2, [b], false); tree.Add(key1, [a]); tree.Add(key2, [b]); Assert.Equal([a], imTree.GetOrDefaultByValue(key1)); Assert.Equal([b], imTree.GetOrDefaultByValue(key2)); Assert.Equal([a], tree.GetOrDefault(key1)); Assert.Equal([b], tree.GetOrDefault(key2)); imTree = imTree.AddOrUpdate(key2, [c], false, (list, n) => [..list, ..n]); Assert.Equal([a], imTree.GetOrDefaultByValue(key1)); Assert.Equal([b, c], imTree.GetOrDefaultByValue(key2)); imTree = imTree.AddOrUpdate(key2, [b], false, true); Assert.Equal([a], imTree.GetOrDefaultByValue(key1)); Assert.Equal([b], imTree.GetOrDefaultByValue(key2)); imTree = imTree.UpdateIfExists(key2, false, list => [..list, c]); Assert.Equal([a], imTree.GetOrDefaultByValue(key1)); Assert.Equal([b, c], imTree.GetOrDefaultByValue(key2)); imTree = imTree.UpdateIfExists(key2, [b], false); Assert.Equal([a], imTree.GetOrDefaultByValue(key1)); Assert.Equal([b], imTree.GetOrDefaultByValue(key2)); } [Fact] public void Test_Ref_Collision_Check() { var (key1, key2) = TypeGen.GetCollidingTypes(); var a = new A(); var b = new B(); var c = new C(); var imTree = ImmutableTree>.Empty; var tree = new HashTree>(); imTree = imTree.AddOrUpdate(key1, [a], true); imTree = imTree.AddOrUpdate(key2, [b], true); tree.Add(key1, [a]); tree.Add(key2, [b]); Assert.Equal([a], imTree.GetOrDefaultByRef(key1)); Assert.Equal([b], imTree.GetOrDefaultByRef(key2)); Assert.Equal([a], tree.GetOrDefault(key1)); Assert.Equal([b], tree.GetOrDefault(key2)); imTree = imTree.AddOrUpdate(key2, [c], true, (list, n) => [..list, ..n]); Assert.Equal([a], imTree.GetOrDefaultByRef(key1)); Assert.Equal([b, c], imTree.GetOrDefaultByRef(key2)); imTree = imTree.AddOrUpdate(key2, [b], true, true); Assert.Equal([a], imTree.GetOrDefaultByRef(key1)); Assert.Equal([b], imTree.GetOrDefaultByRef(key2)); imTree = imTree.UpdateIfExists(key2, true, list => [..list, c]); Assert.Equal([a], imTree.GetOrDefaultByRef(key1)); Assert.Equal([b, c], imTree.GetOrDefaultByRef(key2)); imTree = imTree.UpdateIfExists(key2, [b], true); Assert.Equal([a], imTree.GetOrDefaultByRef(key1)); Assert.Equal([b], imTree.GetOrDefaultByRef(key2)); } [Fact] public void Test_Remove_Keep_Collisions() { var key1 = new H1(); var key2 = new H2(); var key3 = new H3(); var a = new A(); var b = new B(); var c = new C(); var imTree = ImmutableTree.Empty; imTree = imTree.AddOrUpdate(key1, a, false); imTree = imTree.AddOrUpdate(key2, b, false); imTree = imTree.AddOrUpdate(key3, c, false); imTree = imTree.Remove(key1, false); Assert.False(imTree.IsEmpty); Assert.Null(imTree.GetOrDefaultByValue(key1)); Assert.Equal(b, imTree.GetOrDefaultByValue(key2)); Assert.Equal(c, imTree.GetOrDefaultByValue(key3)); imTree = imTree.Remove(key2, false); Assert.False(imTree.IsEmpty); Assert.Null(imTree.GetOrDefaultByValue(key1)); Assert.Null(imTree.GetOrDefaultByValue(key2)); Assert.Equal(c, imTree.GetOrDefaultByValue(key3)); imTree = imTree.Remove(key3, false); Assert.True(imTree.IsEmpty); Assert.Null(imTree.GetOrDefaultByValue(key1)); Assert.Null(imTree.GetOrDefaultByValue(key2)); Assert.Null(imTree.GetOrDefaultByValue(key3)); } [Fact] public void Test_Collision_Remove() { var key1 = new H1(); var key2 = new H2(); var key3 = new H3(); var a = new A(); var b = new B(); var c = new C(); var imTree = ImmutableTree.Empty; imTree = imTree.AddOrUpdate(key1, a, false); imTree = imTree.AddOrUpdate(key2, b, false); imTree = imTree.AddOrUpdate(key3, c, false); imTree = imTree.Remove(key2, false); Assert.False(imTree.IsEmpty); Assert.Equal(a, imTree.GetOrDefaultByValue(key1)); Assert.Equal(c, imTree.GetOrDefaultByValue(key3)); Assert.Null(imTree.GetOrDefaultByValue(key2)); imTree = imTree.Remove(key1, false); Assert.False(imTree.IsEmpty); Assert.Null(imTree.GetOrDefaultByValue(key1)); Assert.Equal(c, imTree.GetOrDefaultByValue(key3)); Assert.Null(imTree.GetOrDefaultByValue(key2)); imTree = imTree.Remove(key3, false); Assert.True(imTree.IsEmpty); Assert.Null(imTree.GetOrDefaultByValue(key1)); Assert.Null(imTree.GetOrDefaultByValue(key3)); Assert.Null(imTree.GetOrDefaultByValue(key2)); } private class A; private class B; private class C; private class H1 { public override int GetHashCode() { return 1; } } private class H2 { public override int GetHashCode() { return 1; } } private class H3 { public override int GetHashCode() { return 1; } } } ================================================ FILE: test/DecoratorTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Lifetime; using Stashbox.Registration; using Stashbox.Tests.Utils; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class DecoratorTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(typeof(TestDecorator1)); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple3(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1)); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple_Lazy(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve>(); Assert.NotNull(test.Value); Assert.IsType(test.Value); Assert.NotNull(test.Value.Test); Assert.IsType(test.Value.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple_Func(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve>(); Assert.NotNull(test()); Assert.IsType(test()); Assert.NotNull(test().Test); Assert.IsType(test().Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Simple_Enumerable(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.RegisterDecorator(); var test = container.Resolve>(); Assert.NotNull(test); Assert.IsAssignableFrom>(test); var arr = test.ToArray(); Assert.NotNull(arr[0]); Assert.IsType(arr[0]); Assert.NotNull(arr[1]); Assert.IsType(arr[1]); Assert.NotNull(arr[0].Test); Assert.IsType(arr[0].Test); Assert.NotNull(arr[1].Test); Assert.IsType(arr[1].Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Decorator_Holds_Lazy(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Decorator_Holds_Func(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Decorator_Holds_Enumerable(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); Assert.NotNull(test.Test); Assert.IsType(test.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Dependency(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Inject_Member_With_Config(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(config => config.WithDependencyBinding("Test")); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Inject_Member_With_Config_Non_Generic_Implementation(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(typeof(TestDecorator3Attributeless), config => config.WithDependencyBinding("Test")); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_AutoMemberInjection_Throw_When_Member_Unresolvable(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(context => context.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)); Assert.Throws(() => container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_AutoMemberInjection_InjectionParameter(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(context => context .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess) .WithInjectionParameter("Name", "test")); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); Assert.Equal("test", ((TestDecorator4)test).Name); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_AutoMemberInjection_InjectionParameter_Fluent(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(context => context .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess) .WithInjectionParameter("Name", "test")); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); Assert.Equal("test", ((TestDecorator4)test).Name); } [Fact] public void DecoratorTests_ConstructorSelection_LeastParameters() { using var container = new StashboxContainer(); container.Register(); container.RegisterDecorator(context => context.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters)); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.Null(test.Test); } [Fact] public void DecoratorTests_ConstructorSelection_MostParameters() { using var container = new StashboxContainer(); container.Register(); container.RegisterDecorator(context => context.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferMostParameters)); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Multiple(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var test = container.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); Assert.NotNull(test.Test.Test); Assert.IsType(test.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Multiple_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterScoped(); container.RegisterDecorator(); container.RegisterDecorator(); using var child = container.BeginScope(); var test = child.Resolve(); Assert.NotNull(test); Assert.IsType(test); Assert.NotNull(test.Test); Assert.IsType(test.Test); Assert.NotNull(test.Test.Test); Assert.IsType(test.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_OpenGeneric(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(typeof(ITest1<>), typeof(Test1<>)); container.RegisterDecorator(typeof(ITest1<>), typeof(TestDecorator1<>)); var test = container.Resolve>(); Assert.NotNull(test); Assert.IsType>(test); Assert.NotNull(test.Test); Assert.IsType>(test.Test); } [Fact] public void DecoratorTests_DecoratorDependency_Null() { using var container = new StashboxContainer(); container.Register(); container.RegisterDecorator(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void DecoratorTests_DecoreteeDependency_Null() { using var container = new StashboxContainer(); container.Register(); container.RegisterDecorator(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Disposed(CompilerType compilerType) { IDisp test; using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking().WithCompiler(compilerType))) { container.Register(); container.RegisterDecorator(); test = container.Resolve(); Assert.NotNull(test); Assert.NotNull(test.Test); } Assert.True(test.Disposed); Assert.True(test.Test.Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Disposed_OnlyDecoreteeDisposal(CompilerType compilerType) { IDisp test; using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking().WithCompiler(compilerType))) { container.Register(context => context.WithoutDisposalTracking()); container.RegisterDecorator(); test = container.Resolve(); Assert.NotNull(test); Assert.NotNull(test.Test); } Assert.True(test.Disposed); Assert.False(test.Test.Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Disposed_BothDisposal(CompilerType compilerType) { IDisp test; using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking().WithCompiler(compilerType))) { container.Register(context => context.WithoutDisposalTracking()); container.RegisterDecorator(context => context.WithoutDisposalTracking()); test = container.Resolve(); Assert.NotNull(test); Assert.NotNull(test.Test); } Assert.False(test.Disposed); Assert.False(test.Test.Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_ReplaceDecorator(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); container.RegisterDecorator(context => context.ReplaceExisting()); test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_RemapDecorator(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); Assert.IsType(test.Test.Test); container.ReMapDecorator(); test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_RemapDecorator_V2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); Assert.IsType(test.Test.Test); container.ReMapDecorator(typeof(TestDecorator3)); test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_RemapDecorator_WithConfig(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); Assert.IsType(test.Test.Test); container.ReMapDecorator(typeof(ITest1), typeof(TestDecorator3), context => context.WithoutDisposalTracking()); test = container.Resolve(); Assert.IsType(test); Assert.IsType(test.Test); } [Fact] public void DecoratorTests_Service_ImplementationType() { using var container = new StashboxContainer(); container.RegisterDecorator(context => { Assert.Equal(typeof(ITest1), context.ServiceType); Assert.Equal(typeof(TestDecorator1), context.ImplementationType); }); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("t1"); container.Register("t2"); container.RegisterDecorator(); container.RegisterDecorator(c => c.WhenDecoratedServiceIs()); var t1 = container.Resolve("t1"); var t2 = container.Resolve("t2"); Assert.IsType(t1); Assert.IsType(t1.Test); Assert.IsType(t2); Assert.IsType(t2.Test); Assert.IsType(t2.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Enumerable(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(c => c.WhenDecoratedServiceIs()); var t = container.Resolve(); Assert.IsType(t[0]); Assert.IsType(t[0].Test); Assert.IsType(t[1]); Assert.IsType(t[1].Test); Assert.IsType(t[1].Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Named(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("t1"); container.Register("t2"); container.RegisterDecorator(); container.RegisterDecorator(c => c.When(t => t.DependencyName.Equals("t2"))); var t1 = container.Resolve("t1"); var t2 = container.Resolve("t2"); Assert.IsType(t1); Assert.IsType(t1.Test); Assert.IsType(t2); Assert.IsType(t2.Test); Assert.IsType(t2.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Named_Short(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("t1"); container.Register("t2"); container.RegisterDecorator(); container.RegisterDecorator(c => c.WhenDecoratedServiceIs("t2")); var t1 = container.Resolve("t1"); var t2 = container.Resolve("t2"); Assert.IsType(t1); Assert.IsType(t1.Test); Assert.IsType(t2); Assert.IsType(t2.Test); Assert.IsType(t2.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Parent(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithAutoMemberInjection().WithUnknownTypeResolution().WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(c => c.When(t => t.ParentType == typeof(TestHolder2))); var t1 = container.Resolve(); var t2 = container.Resolve(); Assert.IsType(t1.Test1); Assert.IsType(t1.Test1.Test); Assert.IsType(t2.Test1); Assert.IsType(t2.Test1.Test); Assert.IsType(t2.Test1.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Attribute(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithAutoMemberInjection().WithUnknownTypeResolution().WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WhenHas()); container.RegisterDecorator(c => c.WhenHas()); var t1 = container.Resolve(); var t2 = container.Resolve(); Assert.IsType(t1.Test1); Assert.IsType(t1.Test1.Test); Assert.IsType(t1.Test1.Test.Test); Assert.IsType(t2.Test1); Assert.IsType(t2.Test1.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Attribute_Multiple(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithAutoMemberInjection().WithUnknownTypeResolution().WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WhenHas()); container.RegisterDecorator(c => c.WhenHas()); container.RegisterDecorator(c => c.WhenHas().WhenHas()); var t = container.Resolve(); Assert.IsType(t.Test1); Assert.IsType(t.Test1.Test); Assert.IsType(t.Test1.Test.Test); Assert.IsType(t.Test11); Assert.IsType(t.Test11.Test); Assert.IsType(t.Test11.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Conditional_Attribute_Multiple_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithAutoMemberInjection().WithUnknownTypeResolution().WithCompiler(compilerType)); container.RegisterScoped(); container.RegisterDecorator(c => c.WhenHas().WithTransientLifetime()); container.RegisterDecorator(c => c.WhenHas().WithTransientLifetime()); container.RegisterDecorator(c => c.WhenHas().WhenHas().WithTransientLifetime()); var t = container.Resolve(); Assert.IsType(t.Test1); Assert.IsType(t.Test1.Test); Assert.IsType(t.Test1.Test.Test); Assert.IsType(t.Test11); Assert.IsType(t.Test11.Test); Assert.IsType(t.Test11.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Different_Lifetime(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(); container.RegisterDecorator(c => c.WithScopedLifetime()); using var scope1 = container.BeginScope(); var t1 = scope1.Resolve(); using var scope2 = scope1.BeginScope(); var t2 = scope2.Resolve(); Assert.NotSame(t1, t2); Assert.Same(t1.Test, t2.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Inheriting_Lifetime(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(); container.RegisterDecorator(); var t1 = container.Resolve(); var t2 = container.Resolve(); Assert.Same(t1, t2); Assert.Same(t1.Test, t2.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Different_Decoretees(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(((TestDecorator10)t).Test1); Assert.IsType(t.Test2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Different_Decoretees_Indirect(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test2); Assert.IsType(((T3)t.Test2).Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Different_Decoretees_Indirect2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(); container.RegisterDecorator(); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test2); Assert.IsType(t.Test2.Test2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NamedScope(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(c => c.InNamedScope("A")); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); using var scope = container.BeginScope("A"); t = scope.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); Assert.IsType(t.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NamedScope_Different_Decoretee(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.InNamedScope("A")); container.RegisterDecorator(); container.RegisterDecorator(c => c.InNamedScope("A")); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); using var scope = container.BeginScope("A"); t = scope.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); Assert.IsType(t.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator3), c => c.WithFactory(() => new TestDecorator3())); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator13), c => c.WithFactory(r => new TestDecorator13 { Name = "T4" })); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); Assert.IsType(t.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Generic(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WithFactory(() => new TestDecorator3())); container.RegisterDecorator(c => c.WithFactory(r => new TestDecorator13 { Name = "T4" })); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); Assert.IsType(t.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Target_Attribute(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("t1"); container.Register("t2"); container.RegisterDecorator(c => c.WhenDecoratedServiceHas()); container.RegisterDecorator(); var t = container.Resolve("t1"); Assert.IsType(t); Assert.IsType(t.Test); Assert.IsType(t.Test.Test); t = container.Resolve("t2"); Assert.IsType(t); Assert.IsType(t.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_InjectMember(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_InjectMember_AttributeLess(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WithDependencyBinding(d => d.Test)); var t = container.Resolve(); Assert.IsType(t); Assert.IsType(t.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_WithFinalizer(CompilerType compilerType) { var finalized = false; { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WithFinalizer(d => { finalized = true; })); container.Resolve(); } Assert.True(finalized); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param1(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(c => c.WithFactory(t1 => { Assert.IsType(t1); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param_NextDecorator(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(c => c.WithFactory(t1 => { Assert.IsType(t1); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.WithFactory((t1, t2) => { Assert.IsType(t1); Assert.IsType(t2); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param3(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.WithFactory((t1, t2, t3) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param4(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.WithFactory((t1, t2, t3, t4) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_Factory_Param5(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.WithFactory((t1, t2, t3, t4, t5) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); Assert.IsType(t5); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param1(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory(t1 => { Assert.IsType(t1); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param_NextDecorator(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.RegisterDecorator(); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory(t1 => { Assert.IsType(t1); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory((t1, t2) => { Assert.IsType(t1); Assert.IsType(t2); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param3(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory((t1, t2, t3) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param4(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory((t1, t2, t3, t4) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); return new TestDecorator1(t1); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void DecoratorTests_NonGeneric_Factory_Param5(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.AsImplementedTypes()); container.RegisterDecorator(typeof(ITest1), typeof(TestDecorator1), c => c.WithFactory((t1, t2, t3, t4, t5) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); Assert.IsType(t5); return new TestDecorator1(t1); })); container.Resolve(); } [Fact] public void DecoratorTests_Compositor_Works() { using var container = new StashboxContainer(); container.RegisterDecorator(c => c .WithInitializer((d, r) => { }) .WithSingletonLifetime() .WhenDecoratedServiceIs()); var registration = container.ContainerContext.DecoratorRepository.GetRegistrationMappings().First().Value; Assert.NotNull(registration.RegistrationOptions[RegistrationOption.Initializer]); Assert.Equal(Lifetimes.Singleton, registration.Lifetime); } [Fact] public void DecoratorTests_Compositor_ChildContainer() { using var container = new StashboxContainer(); container.RegisterScoped(); container.RegisterScoped(); container.RegisterDecorator(); var child = container.CreateChildContainer(); child.RegisterInstance(new Test13()); var a = child.Resolve(); Assert.NotEmpty(((TestDecorator8)a).Decoretees); } interface IT1; interface IT2; interface IT3; interface IT4; interface IT5; class TComp : IT1, IT2, IT3, IT4, IT5; interface ITest1 { ITest1 Test { get; } } interface ITest2 { ITest2 Test2 { get; } } interface IDecoratorDep; interface IDep; interface ITest1 { ITest1 Test { get; } } interface IDisp : IDisposable { IDisp Test { get; } bool Disposed { get; } } class Test1 : ITest1 { public ITest1 Test { get; } } class T2 : ITest2 { public ITest2 Test2 { get; } } class T3 : ITest2 { public ITest2 Test2 { get; } [Dependency] public ITest1 Test { get; set; } } class T4 : ITest1 { [Dependency] public ITest2 Test2 { get; set; } public ITest1 Test { get; set; } } class Test11 : ITest1 { public ITest1 Test { get; } } class Test12 : ITest1 { public ITest1 Test { get; } public Test12(IDep dep) { } } class Test13 : ITest1 { public ITest1 Test { get; } } class TestDisp : IDisp { public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(TestDisp)); this.Disposed = true; } public bool Disposed { get; private set; } public IDisp Test { get; } } class TestDispDecorator : IDisp { public TestDispDecorator(IDisp disp) { this.Test = disp; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(TestDisp)); this.Disposed = true; } public bool Disposed { get; private set; } public IDisp Test { get; } } class Test1 : ITest1 { public ITest1 Test { get; } } class TestDecorator1 : ITest1 { public ITest1 Test { get; } public TestDecorator1(ITest1 test1) { this.Test = test1; } } class TestDecorator1 : ITest1 { public ITest1 Test { get; } public TestDecorator1(ITest1 test1) { this.Test = test1; } } class TestDecorator2 : ITest1 { public ITest1 Test { get; } public TestDecorator2(ITest1 test1) { this.Test = test1; } } class TestDecorator3 : ITest1 { [Dependency] public ITest1 Test { get; set; } } class TestDecorator14 : ITest1 { [Dependency] public ITest1 Test { get; set; } } class TestDecorator3Attributeless : ITest1 { public ITest1 Test { get; set; } } class TestDecorator4 : ITest1 { public string Name { get; private set; } public ITest1 Test { get; private set; } } class TestDecorator5 : ITest1 { public ITest1 Test { get; } public TestDecorator5() { } public TestDecorator5(ITest1 test1) { this.Test = test1; } } class TestDecorator6 : ITest1 { public ITest1 Test { get; } public TestDecorator6(Lazy test1) { this.Test = test1.Value; } } class TestDecorator7 : ITest1 { public ITest1 Test { get; } public TestDecorator7(Func test1) { this.Test = test1(); } } class TestDecorator8 : ITest1 { public IEnumerable Decoretees { get; } public ITest1 Test { get; } public TestDecorator8(IEnumerable test1) { Decoretees = test1; this.Test = test1.First(); } } class TestDecorator9 : ITest1 { public ITest1 Test { get; } public TestDecorator9(ITest1 test1, IDecoratorDep dep) { this.Test = test1; } } class TestDecorator10 : ITest2 { public ITest2 Test2 { get; } public ITest1 Test1 { get; } public TestDecorator10(ITest2 test2, ITest1 test1) { this.Test2 = test2; this.Test1 = test1; } } class TestDecorator11 : ITest2 { public ITest2 Test2 { get; } public TestDecorator11(ITest2 test2) { this.Test2 = test2; } } class TestDecorator12 : ITest2 { public ITest2 Test2 { get; } public TestDecorator12(ITest1 test1) { this.Test2 = ((T4)test1.Test).Test2; } } class TestDecorator13 : ITest1 { public string Name { get; set; } [Dependency] public ITest1 Test { get; set; } } class TestHolder1 { [Decorator1, Decorator2] public ITest1 Test1 { get; set; } } class TestHolder2 { [Decorator2] public ITest1 Test1 { get; set; } } class TestHolder3 { [Decorator2, Decorator3] public ITest1 Test1 { get; set; } [Decorator1] public ITest1 Test11 { get; set; } } class Decorator1Attribute : Attribute; class Decorator2Attribute : Attribute; class Decorator3Attribute : Attribute; [Decorator1] class TestService : ITest1 { public ITest1 Test { get; } } } ================================================ FILE: test/DependencyBindingTests.cs ================================================ using Stashbox.Attributes; using Xunit; namespace Stashbox.Tests; public class DependencyBindingTests { [Fact] public void DependencyBindingTests_Bind_To_The_Same_Type() { var inst = new StashboxContainer() .Register("test1") .Register("test2") .Register("test3") .Register(ctx => ctx.WithDependencyBinding(typeof(ITest1), "test2")) .Resolve(); Assert.IsType(inst.Test1); Assert.IsType(inst.Test2); Assert.IsType(inst.Test3); } [Fact] public void DependencyBindingTests_Bind_To_Different_Types() { var inst = new StashboxContainer() .Register("test1") .Register("test2") .Register("test3") .Register(ctx => ctx .WithDependencyBinding("test1", "test1") .WithDependencyBinding("test2", "test2") .WithDependencyBinding("test3", "test3")) .Resolve(); Assert.IsType(inst.Test1); Assert.IsType(inst.Test2); Assert.IsType(inst.Test3); } [Fact] public void DependencyBindingTests_Override_Typed_Bindings() { var inst = new StashboxContainer() .Register("test1") .Register("test2") .Register("test3") .Register(ctx => ctx .WithDependencyBinding("test3", "test3") .WithDependencyBinding("test2")) .Resolve(); Assert.IsType(inst.Test1); Assert.IsType(inst.Test2); Assert.IsType(inst.Test3); } [Fact] public void DependencyBindingTests_Override_Typed_Bindings_Injection_Method() { var inst = new StashboxContainer() .Register("test1") .Register("test2") .Register("test3") .Register(ctx => ctx .WithDependencyBinding("test3", "test3") .WithDependencyBinding("test2")) .Resolve(); Assert.IsType(inst.Test1); Assert.IsType(inst.Test2); Assert.IsType(inst.Test3); } interface ITest1; class Test1 : ITest1; class Test11 : ITest1; class Test12 : ITest1; class Test { public ITest1 Test1 { get; } public ITest1 Test2 { get; } public ITest1 Test3 { get; } public Test(ITest1 test1, ITest1 test2, ITest1 test3) { this.Test1 = test1; this.Test2 = test2; this.Test3 = test3; } } class TestMethodInjection { public ITest1 Test1 { get; private set; } public ITest1 Test2 { get; private set; } public ITest1 Test3 { get; private set; } [InjectionMethod] public void Init(ITest1 test1, ITest1 test2, ITest1 test3) { this.Test1 = test1; this.Test2 = test2; this.Test3 = test3; } } } ================================================ FILE: test/DisposeOrderTests.cs ================================================ using System; using System.Collections.Generic; using Xunit; namespace Stashbox.Tests; public class DisposeOrderTests { [Fact] public void Ensure_Services_Are_Disposed_In_The_Right_Order() { var disposables = new List(); using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { var obj = container.Register() .Register() .Register() .Resolve(dependencyOverrides: [disposables]); Assert.NotNull(obj); } Assert.IsType(disposables[0]); Assert.IsType(disposables[1]); Assert.IsType(disposables[2]); Assert.IsType(disposables[3]); } [Fact] public void Ensure_Services_Are_Disposed_In_The_Right_Order_InScope() { var disposables = new List(); using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.Register() .Register() .Register(); using var scope = container.BeginScope(); var obj = scope.Resolve(dependencyOverrides: [disposables]); Assert.NotNull(obj); } Assert.IsType(disposables[0]); Assert.IsType(disposables[1]); Assert.IsType(disposables[2]); Assert.IsType(disposables[3]); } private class DisposableObj1 : IDisposable { private readonly IList disposables; public DisposableObj1(IList disposables) { this.disposables = disposables; } public void Dispose() { this.disposables.Add(this); } } private class DisposableObj2 : IDisposable { private readonly IList disposables; private readonly DisposableObj1 dObj; public DisposableObj2(IList disposables, DisposableObj1 dObj) { this.disposables = disposables; this.dObj = dObj; } public void Dispose() { this.disposables.Add(this); } } private class DisposableObj3 : IDisposable { private readonly IList disposables; private readonly DisposableObj2 dObj; private readonly DisposableObj1 dObj1; public DisposableObj3(IList disposables, DisposableObj2 dObj, DisposableObj1 dObj1) { this.disposables = disposables; this.dObj = dObj; this.dObj1 = dObj1; } public void Dispose() { this.disposables.Add(this); } } } ================================================ FILE: test/DisposeTests.cs ================================================ using Stashbox.Attributes; using System; using System.Collections.Generic; using System.Reflection; using Xunit; namespace Stashbox.Tests; public class DisposeTests { [Fact] public void DisposeTests_Singleton() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.RegisterSingleton(); test = container.Resolve(); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_Singleton_WithoutDisposal() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.Register(context => context.WithSingletonLifetime().WithoutDisposalTracking()); test = container.Resolve(); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_Instance() { ITest1 test = new Test1(); ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.RegisterInstance(test); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_Instance_WithoutDisposal() { ITest1 test = new Test1(); ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.RegisterInstance(test, withoutDisposalTracking: true); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_Instance_AsObject_WithoutDisposal() { object test = new Test1(); ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.RegisterInstance(test, typeof(ITest1), withoutDisposalTracking: true); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.False(((ITest1)test).Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_Instance_WithoutDisposal_Fluent() { ITest1 test = new Test1(); ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.Register(context => context.WithInstance(test).WithoutDisposalTracking()); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_WireUp() { ITest1 test = new Test1(); ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.WireUp(test); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.Register(); container.Register(); container.Register(); test = container.Resolve(); test2 = container.Resolve(); test3 = container.Resolve(); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_Scoped() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { ITest1 test4; ITest2 test5; Test3 test6; container.RegisterScoped(); container.RegisterScoped(); container.RegisterScoped(); container.RegisterScoped(); container.RegisterScoped("test"); container.RegisterScoped("test2"); using var scope = container.BeginScope(); test = scope.Resolve(); var a = scope.Resolve(); test2 = scope.Resolve(); test3 = scope.Resolve(); using (var child = container.BeginScope()) { test4 = (ITest1)child.Resolve(typeof(ITest1), "test"); test5 = child.Resolve(); test6 = child.Resolve(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_PutInScope_RootScope() { var test = new Test1(); using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.PutInstanceInScope(test); var test1 = container.Resolve(); var test2 = container.Resolve(typeof(ITest2)); var test3 = container.Resolve(); Assert.Same(test, test1); Assert.Same(test, ((ITest2)test2).Test1); Assert.Same(test, test3.Test1); } Assert.True(test.Disposed); } [Fact] public void DisposeTests_PutInScope_RootScope_WithoutDispose() { var test = new Test1(); using (IStashboxContainer container = new StashboxContainer()) { container.Register(); container.Register(); container.PutInstanceInScope(test, withoutDisposalTracking: true); var test1 = container.Resolve(); var test2 = container.Resolve(typeof(ITest2)); var test3 = container.Resolve(); Assert.Same(test, test1); Assert.Same(test, ((ITest2)test2).Test1); Assert.Same(test, test3.Test1); } Assert.False(test.Disposed); } [Fact] public void DisposeTests_PutInScope_Scoped() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); container.RegisterScoped(); var test = new Test1(); using (var child = container.BeginScope()) { child.PutInstanceInScope(test); var test1 = child.Resolve(); var test2 = child.Resolve(); var test3 = child.Resolve(); Assert.Same(test, test1); Assert.Same(test, test2.Test1); Assert.Same(test, test3.Test1); Assert.False(test.Disposed); } Assert.True(test.Disposed); var test4 = new Test1(); using (var child = container.BeginScope()) { child.PutInstanceInScope(test4); var test1 = child.Resolve(); var test2 = child.Resolve(); var test3 = child.Resolve(); Assert.Same(test4, test1); Assert.Same(test4, test2.Test1); Assert.Same(test4, test3.Test1); Assert.NotSame(test, test1); Assert.NotSame(test, test2.Test1); Assert.NotSame(test, test3.Test1); Assert.False(test4.Disposed); } Assert.True(test4.Disposed); } [Fact] public void DisposeTests_PutInScope_WithoutDispose() { using IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); var test = new Test1(); using (var child = container.BeginScope()) { child.PutInstanceInScope(test, withoutDisposalTracking: true); var test1 = child.Resolve(); var test2 = child.Resolve(); var test3 = child.Resolve(); Assert.Same(test, test1); Assert.Same(test, test2.Test1); Assert.Same(test, test3.Test1); } Assert.False(test.Disposed); } [Fact] public void DisposeTests_PutInScope_Named() { using IStashboxContainer container = new StashboxContainer(); container.Register(); var dummy1 = new Test1(); var dummy2 = new Test1(); using (var child = container.BeginScope()) { child.PutInstanceInScope(dummy1, true, "d1"); child.PutInstanceInScope(dummy2, true, "d2"); var test1 = child.Resolve("d2"); var test2 = child.Resolve(); Assert.Same(dummy2, test1); Assert.Same(dummy2, test2.Test1); } Assert.False(dummy1.Disposed); Assert.False(dummy2.Disposed); } [Fact] public void DisposeTests_Scoped_Factory() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { ITest1 test4; ITest2 test5; Test3 test6; container.RegisterScoped(); container.RegisterScoped(); container.RegisterScoped(); using var scope = container.BeginScope(); test = scope.Resolve>()(); test2 = scope.Resolve>()(); test3 = scope.Resolve>()(); using (var child = scope.BeginScope()) { test4 = child.Resolve>()(); test5 = child.Resolve>()(); test6 = child.Resolve>()(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_Scoped_WithoutDisposal() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer()) { ITest1 test4; ITest2 test5; Test3 test6; container.Register(context => context.WithScopedLifetime().WithoutDisposalTracking()); container.Register(context => context.WithScopedLifetime().WithoutDisposalTracking()); container.Register(context => context.WithScopedLifetime().WithoutDisposalTracking()); using var scope = container.BeginScope(); test = scope.Resolve(); test2 = scope.Resolve(); test3 = scope.Resolve(); using (var child = container.BeginScope()) { test4 = child.Resolve(); test5 = child.Resolve(); test6 = child.Resolve(); } Assert.False(test4.Disposed); Assert.False(test5.Test1.Disposed); Assert.False(test6.Test1.Disposed); } Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Scoped_Transient() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { ITest1 test4; ITest2 test5; Test3 test6; container.Register(); container.Register(); container.Register(); test = container.Resolve(); test2 = container.Resolve(); test3 = container.Resolve(); using (var child = container.BeginScope()) { test4 = child.Resolve(); test5 = child.Resolve(); test6 = child.Resolve(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Scoped_Transient_Factory() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { ITest1 test4; ITest2 test5; Test3 test6; container.Register(); container.Register(); container.Register(); test = container.Resolve>()(); test2 = container.Resolve>()(); test3 = container.Resolve>()(); using (var child = container.BeginScope()) { test4 = child.Resolve>()(); test5 = child.Resolve>()(); test6 = child.Resolve>()(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Scoped_Transient_TrackingDisabled() { ITest1 test; ITest2 test2; Test3 test3; using (IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { ITest1 test4; ITest2 test5; Test3 test6; container.Register(); container.Register(); container.Register(context => context.WithoutDisposalTracking()); test = container.Resolve(); test2 = container.Resolve(); test3 = container.Resolve(); using (var child = container.BeginScope()) { test4 = child.Resolve(); test5 = child.Resolve(); test6 = child.Resolve(); } Assert.False(test4.Disposed); Assert.False(test5.Test1.Disposed); Assert.False(test6.Test1.Disposed); } Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_ScopeOfScope_Transient() { using IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking()); container.Register(); container.Register(); container.Register(); ITest1 test; ITest2 test2; Test3 test3; using (var scope = container.BeginScope()) { test = scope.Resolve(); test2 = scope.Resolve(); test3 = scope.Resolve(); ITest1 test4; ITest2 test5; Test3 test6; using (var scope2 = scope.BeginScope()) { test4 = scope2.Resolve(); test5 = scope2.Resolve(); test6 = scope2.Resolve(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); Assert.False(test.Disposed); Assert.False(test2.Test1.Disposed); Assert.False(test3.Test1.Disposed); } Assert.True(test.Disposed); Assert.True(test2.Test1.Disposed); Assert.True(test3.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Scoped_Transient_ChildContainer() { IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking()); ITest1 test4; ITest2 test5; Test3 test6; container.Register(); container.Register(); container.Register(); using (var child = container.CreateChildContainer()) using (var scope = child.BeginScope()) { test4 = scope.Resolve(); test5 = scope.Resolve(); test6 = scope.Resolve(); } Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Scoped_Transient_Singleton() { var container = new StashboxContainer(config => config.WithDisposableTransientTracking()); container.Register(); container.Register(); container.RegisterSingleton(); ITest1 test4; ITest2 test5; Test3 test6; using (var child = container.BeginScope()) { test4 = child.Resolve(); test5 = child.Resolve(); test6 = child.Resolve(); Assert.False(test4.Disposed); Assert.False(test5.Test1.Disposed); Assert.False(test6.Test1.Disposed); } Assert.False(test4.Disposed); Assert.False(test5.Test1.Disposed); Assert.False(test6.Test1.Disposed); container.Dispose(); Assert.True(test4.Disposed); Assert.True(test5.Test1.Disposed); Assert.True(test6.Test1.Disposed); } [Fact] public void DisposeTests_TrackTransientDisposal_Implementation_Has_Disposable() { IStashboxContainer container = new StashboxContainer(config => config.WithDisposableTransientTracking()); ITest11 test1; container.Register(); using (var child = container.BeginScope()) { test1 = (ITest11)child.Resolve(typeof(ITest11)); } Assert.True(((Test4)test1).Disposed); } [Fact] public void DisposeTests_Instance_TrackTransient() { var test = new Test1(); using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.RegisterInstance(test); Assert.Same(test, container.Resolve()); } Assert.True(test.Disposed); } [Fact] public void DisposeTests_WireUp_TrackTransient() { ITest1 test = new Test1(); using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.WireUp(test); Assert.Same(test, container.Resolve()); } Assert.True(test.Disposed); } [Fact] public void DisposeTests_WireUp_TrackTransient_WithoutDisposal() { ITest1 test = new Test1(); using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.WireUp(test, withoutDisposalTracking: true); Assert.Same(test, container.Resolve()); } Assert.False(test.Disposed); } [Fact] public void DisposeTests_Factory() { ITest1 test; using (var container = new StashboxContainer()) { container.Register(context => context.WithFactory(() => new Test1())); test = container.Resolve(); } Assert.False(test.Disposed); } [Fact] public void DisposeTests_Factory_TrackTransient() { ITest1 test; using (var container = new StashboxContainer(config => config.WithDisposableTransientTracking())) { container.Register(context => context.WithFactory(() => new Test1())); test = container.Resolve(); } Assert.True(test.Disposed); } [Fact] public void DisposeTests_Factory_Scoped() { ITest1 test; using var container = new StashboxContainer() .Register(context => context.WithScopedLifetime().WithFactory(() => new Test1())); { using var scope = container.BeginScope(); test = scope.Resolve(); } Assert.True(test.Disposed); } [Fact] public void DisposeTests_Factory_Scoped_WithoutTracking() { ITest1 test; using var container = new StashboxContainer() .Register(context => context.WithScopedLifetime() .WithFactory(() => new Test1()) .WithoutDisposalTracking()); { using var scope = container.BeginScope(); test = scope.Resolve(); } Assert.False(test.Disposed); } [Fact] public void DisposeTests_Multiple_Dispose_Call() { var container = new StashboxContainer(); container.RegisterSingleton(); var test = container.Resolve(); container.Dispose(); container.Dispose(); Assert.True(test.Disposed); } [Fact] public void DisposeTests_Scoped_Multiple_Dispose_Call() { var container = new StashboxContainer(); container.RegisterScoped(); var scope = container.BeginScope(); var test = scope.Resolve(); scope.Dispose(); scope.Dispose(); Assert.True(test.Disposed); } [Fact] public void DisposeTests_Scope_Removed_From_Parent() { var container = new StashboxContainer(); container.RegisterScoped(); var scope = container.BeginScope(); var sub1 = scope.BeginScope(attachToParent: true); var sub2 = sub1.BeginScope(attachToParent: true); var type = scope.GetType(); var childScopesField = type.GetField("childScopes", BindingFlags.Instance | BindingFlags.NonPublic); var childScopesValue = childScopesField.GetValue(scope); var children = (IEnumerable)childScopesValue.GetType().GetMethod("Walk").Invoke(childScopesValue, null); Assert.Single(children); var t1 = sub1.Resolve(); var t2 = sub2.Resolve(); sub1.Dispose(); Assert.True(t1.Disposed); Assert.True(t2.Disposed); childScopesValue = childScopesField.GetValue(scope); children = (IEnumerable)childScopesValue.GetType().GetMethod("Walk").Invoke(childScopesValue, null); Assert.Empty(children); } interface ITest11; interface ITest12; interface ITest1 : ITest11, ITest12, IDisposable { bool Disposed { get; } } interface ITest2 { ITest1 Test1 { get; } } class Test1 : ITest1 { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) { throw new ObjectDisposedException(nameof(Test1)); } this.Disposed = true; } } class Test2 : ITest2 { public ITest1 Test1 { get; private set; } public Test2(ITest1 test1) { this.Test1 = test1; } } class Test3 { [Dependency] public ITest1 Test1 { get; set; } } class Test4 : ITest11, IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) { throw new ObjectDisposedException(nameof(Test4)); } this.Disposed = true; } } class Test5 { [Dependency("d2")] public ITest1 Test1 { get; set; } } } ================================================ FILE: test/EnumerableTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class EnumerableTests { [Fact] public void EnumerableTests_Resolve_Array_PreserveOrder() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve(); Assert.Equal(3, all.Length); } [Fact] public void EnumerableTests_Resolve_IList() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>(); Assert.Equal(3, all.Count); } [Fact] public void EnumerableTests_Resolve_ICollection() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>(); Assert.Equal(3, all.Count); } [Fact] public void EnumerableTests_Resolve_IReadonlyCollection() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>(); Assert.Equal(3, all.Count); } [Fact] public void EnumerableTests_Resolve_IReadOnlyList() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>(); Assert.Equal(3, all.Count); } [Fact] public void EnumerableTests_Resolve() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(context => context.WithName("enumerable")); container.Register(context => context.WithName("array")); container.Resolve("enumerable"); container.Resolve("array"); var all = container.Resolve>(); var all2 = container.ResolveAll(); Assert.Equal(2, all.Count()); Assert.Equal(2, all2.Count()); } [Fact] public void EnumerableTests_Resolve_Null() { IStashboxContainer container = new StashboxContainer(); var all = container.Resolve>(); var all2 = container.ResolveAll(); Assert.Empty(all); Assert.Empty(all2); } [Fact] public void EnumerableTests_Resolve_Scoped_Null() { IStashboxContainer container = new StashboxContainer(); var scope = container.BeginScope(); var all = scope.Resolve>(); var all2 = scope.ResolveAll(); Assert.Empty(all); Assert.Empty(all2); } [Fact] public void EnumerableTests_Resolve_Scoped() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var all = child.Resolve>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Parent() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var all = child.Resolve>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Parent_Null() { IStashboxContainer container = new StashboxContainer(); var child = container.CreateChildContainer(); var all = child.Resolve>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Scoped_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var all = child.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Parent_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var all = child.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Scoped_Lazy_Null() { IStashboxContainer container = new StashboxContainer(); var child = container.BeginScope(); var all = child.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Parent_Lazy_Null() { IStashboxContainer container = new StashboxContainer(); var child = container.CreateChildContainer(); var all = child.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Scoped_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var all = child.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Parent_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var all = child.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Scoped_Func_Null() { IStashboxContainer container = new StashboxContainer(); var child = container.BeginScope(); var all = child.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Parent_Func_Null() { IStashboxContainer container = new StashboxContainer(); var child = container.CreateChildContainer(); var all = child.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Lazy_Null() { IStashboxContainer container = new StashboxContainer(); var all = container.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_Resolve_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var all = container.Resolve>>(); Assert.Equal(3, all.Count()); } [Fact] public void EnumerableTests_Resolve_Func_Null() { IStashboxContainer container = new StashboxContainer(); var all = container.Resolve>>(); Assert.Empty(all); } [Fact] public void EnumerableTests_ResolveNonGeneric() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(context => context.WithName("enumerable")); container.Register(context => context.WithName("array")); container.Resolve("enumerable"); container.Resolve("array"); var all = container.Resolve>(); var all2 = (IEnumerable)container.ResolveAll(typeof(ITest2)); var all3 = container.ResolveAll(typeof(ITest2)); Assert.Equal(2, all.Count()); Assert.Equal(2, all2.Count()); Assert.Equal(2, all3.Count()); } [Fact] public void EnumerableTests_ResolveNonGeneric_Scoped() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(context => context.WithName("enumerable")); container.Register(context => context.WithName("array")); var scope = container.BeginScope(); scope.Resolve("enumerable"); scope.Resolve("array"); var all = scope.Resolve>(); var all2 = (IEnumerable)scope.ResolveAll(typeof(ITest2)); var all3 = scope.ResolveAll(typeof(ITest2)); Assert.Equal(2, all.Count()); Assert.Equal(2, all2.Count()); Assert.Equal(2, all3.Count()); } [Fact] public void EnumerableTests_Parallel_Resolve() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(context => context.WithName("enumerable")); container.Register(context => context.WithName("array")); Parallel.For(0, 10000, (i) => { container.Resolve("enumerable"); container.Resolve("array"); var all = container.Resolve>(); Assert.Equal(2, all.Count()); }); } [Fact] public void EnumerableTests_Parallel_Resolve_NonGeneric() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(context => context.WithName("enumerable")); container.Register(context => context.WithName("array")); Parallel.For(0, 10000, (i) => { container.Resolve("enumerable"); container.Resolve("array"); var all = container.Resolve>(); var all2 = (IEnumerable)container.ResolveAll(typeof(ITest2)); Assert.Equal(2, all2.Count()); Assert.Equal(2, all.Count()); }); } [Fact] public void EnumerableTests_Resolve_PreserveOrder() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var services = container.Resolve>().ToArray(); Assert.IsType(services[0]); Assert.IsType(services[1]); Assert.IsType(services[2]); } [Fact] public void EnumerableTests_ResolveAll_PreserveOrder() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var services = container.ResolveAll().ToArray(); Assert.IsType(services[0]); Assert.IsType(services[1]); Assert.IsType(services[2]); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Scoped() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var services = child.Resolve>().ToArray(); Assert.IsType(services[0]); Assert.IsType(services[1]); Assert.IsType(services[2]); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Parent() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var services = child.Resolve>().ToArray(); Assert.IsType(services[0]); Assert.IsType(services[1]); Assert.IsType(services[2]); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Scoped_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var services = child.Resolve>>().ToArray(); Assert.IsType(services[0].Value); Assert.IsType(services[1].Value); Assert.IsType(services[2].Value); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Parent_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var services = child.Resolve>>().ToArray(); Assert.IsType(services[0].Value); Assert.IsType(services[1].Value); Assert.IsType(services[2].Value); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Scoped_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.BeginScope(); var services = child.Resolve>>().ToArray(); Assert.IsType(services[0]()); Assert.IsType(services[1]()); Assert.IsType(services[2]()); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Parent_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var child = container.CreateChildContainer(); var services = child.Resolve>>().ToArray(); Assert.IsType(services[0]()); Assert.IsType(services[1]()); Assert.IsType(services[2]()); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var services = container.Resolve>>().ToArray(); Assert.IsType(services[0].Value); Assert.IsType(services[1].Value); Assert.IsType(services[2].Value); } [Fact] public void EnumerableTests_Resolve_PreserveOrder_Func() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var services = container.Resolve>>().ToArray(); Assert.IsType(services[0]()); Assert.IsType(services[1]()); Assert.IsType(services[2]()); } [Fact] public void EnumerableTests_Resolve_UniqueIds() { IStashboxContainer container = new StashboxContainer(config => config .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(); container.Register(); container.Register(); Assert.Equal(3, container.Resolve>().Count()); } [Fact] public void EnumerableTests_Resolve_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.Resolve>("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_Resolve_WithName_Single() { IStashboxContainer container = new StashboxContainer(); container.Register("t1"); container.Register("t2"); container.Register(); Assert.Single(container.Resolve>("t1")); } [Fact] public void EnumerableTests_Resolve_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.Resolve>("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.Resolve>("t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_ResolveAll_Generic_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.ResolveAll("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_ResolveAll_Generic_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.ResolveAll("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.ResolveAll("t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_ResolveAll_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_ResolveAll_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_ResolveAll_PerRequest_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register(c => c.WithName("t").WithPerRequestLifetime()); container.Register("t"); container.Register(); var instances = container.ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_Scope_Resolve_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().Resolve>("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_Scope_Resolve_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().Resolve>("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.BeginScope().Resolve>("t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_Scope_ResolveAll_Generic_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().ResolveAll("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_Scope_ResolveAll_Generic_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().ResolveAll("t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.BeginScope().ResolveAll("t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_Scope_ResolveAll_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_Scope_ResolveAll_WithName_FromCache() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var instances = container.BeginScope().ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); var instances2 = container.BeginScope().ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances2.Length); Assert.IsType(instances2[0]); Assert.IsType(instances2[1]); } [Fact] public void EnumerableTests_Scope_ResolveAll_PerRequest_WithName() { IStashboxContainer container = new StashboxContainer(); container.Register(c => c.WithName("t").WithPerRequestLifetime()); container.Register("t"); container.Register(); var instances = container.BeginScope().ResolveAll(typeof(ITest1), "t").ToArray(); Assert.Equal(2, instances.Length); Assert.IsType(instances[0]); Assert.IsType(instances[1]); } [Fact] public void EnumerableTests_ResolveAll_WithName_WithOverrides() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var inst = container.ResolveAll(typeof(ITest3), "t", [new Test1()]).OfType().ToArray(); Assert.Equal(2, inst.Length); Assert.IsType(inst[0].Test); Assert.IsType(inst[1].Test); } [Fact] public void EnumerableTests_ResolveAll_Generic_WithName_WithOverrides() { IStashboxContainer container = new StashboxContainer(); container.Register("t"); container.Register("t"); container.Register(); var inst = container.ResolveAll("t", [new Test1()]).ToArray(); Assert.Equal(2, inst.Length); Assert.IsType(inst[0].Test); Assert.IsType(inst[1].Test); } interface ITest1; interface ITest2; interface ITest3 { ITest1 Test { get; } } class Test1 : ITest1; class Test11 : ITest1; class Test12 : ITest1; class Test2 : ITest2 { public Test2(IEnumerable tests) { Shield.EnsureNotNull(tests, nameof(tests)); Assert.Equal(3, tests.Count()); } } class Test22 : ITest2 { public Test22(ITest1[] tests) { Shield.EnsureNotNull(tests, nameof(tests)); Assert.Equal(3, tests.Length); } } class Test31 : ITest3 { public ITest1 Test { get; } public Test31(ITest1 test) { this.Test = test; } } class Test32 : ITest3 { public ITest1 Test { get; } public Test32(ITest1 test) { this.Test = test; } } class Test33 : ITest3 { public ITest1 Test { get; } public Test33(ITest1 test) { this.Test = test; } } } ================================================ FILE: test/FactoryTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Resolution; using Stashbox.Tests.Utils; using System; using Xunit; namespace Stashbox.Tests; public class FactoryTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_DependencyResolve(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(() => new Test("test"))); container.Register(); var inst = container.Resolve(); Assert.IsType(inst.Test); Assert.Equal("test", inst.Test.Name); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_DependencyResolve_ServiceUpdated(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(() => new Test("test"))); container.Register(); container.ReMap(context => context.WithFactory(() => new Test("test1"))); var inst = container.Resolve(); Assert.IsType(inst.Test); Assert.Equal("test1", inst.Test.Name); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(() => new Test("test"))); container.Register(); var inst = container.Resolve(); Assert.IsType(inst.Test); Assert.Equal("test", inst.Test.Name); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_NotSame(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithInjectionParameter("name", "test")); container.Register(context => context.WithFactory(cont => { var test1 = cont.Resolve(); return new Test12(test1); })); var inst1 = container.Resolve(); var inst2 = container.Resolve(); Assert.NotSame(inst1.Test, inst2.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_ContainerFactory(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(context => context.WithFactory(c => c.Resolve())); var inst = container.Resolve(); Assert.IsType(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_ContainerFactory_Constructor(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.Register(typeof(ITest), context => context.WithFactory(() => new Test3())); var test1 = container.Resolve(); Assert.IsType(test1.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_ContainerFactory_Initializer(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(() => new Test4()).WithInitializer((t, r) => t.Init("Test"))); var test1 = container.Resolve(); Assert.Equal("Test", test1.Name); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Gets_The_Proper_Scope(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(resolver => new Test5(resolver))); using var scope = container.BeginScope(); var t = scope.Resolve(); Assert.Same(scope, t.DependencyResolver); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param1(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(context => context.WithFactory(t1 => { Assert.IsType(t1); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(context => context.WithFactory((t1, t2) => { Assert.IsType(t1); Assert.IsType(t2); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param3(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(context => context.WithFactory((t1, t2, t3) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param4(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(context => context.WithFactory((t1, t2, t3, t4) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param5(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(context => context.WithFactory((t1, t2, t3, t4, t5) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); Assert.IsType(t5); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_With_Param_With_Resolver(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithFactory(resolver => { return new Test5(resolver); })); using var scope = container.BeginScope(); var inst = scope.Resolve(); Assert.Same(scope, inst.DependencyResolver); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_Gets_The_Proper_Scope(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(typeof(Test5), context => context.WithFactory(resolver => new Test5(resolver))); using var scope = container.BeginScope(); var t = scope.Resolve(); Assert.Same(scope, t.DependencyResolver); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param1(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(typeof(Dummy), context => context.WithFactory(t1 => { Assert.IsType(t1); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param2(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(typeof(Dummy), context => context.WithFactory((t1, t2) => { Assert.IsType(t1); Assert.IsType(t2); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param3(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(typeof(Dummy), context => context.WithFactory((t1, t2, t3) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param4(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(typeof(Dummy), context => context.WithFactory((t1, t2, t3, t4) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param5(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.AsImplementedTypes()); container.Register(typeof(Dummy), context => context.WithFactory((t1, t2, t3, t4, t5) => { Assert.IsType(t1); Assert.IsType(t2); Assert.IsType(t3); Assert.IsType(t4); Assert.IsType(t5); return new Dummy(); })); container.Resolve(); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_NonGeneric_Resolve_With_Param_With_Resolver(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(typeof(Test5), context => context.WithFactory(resolver => { return new Test5(resolver); })); using var scope = container.BeginScope(); var inst = scope.Resolve(); Assert.Same(scope, inst.DependencyResolver); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Ensure_Exclude_Works(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType).WithDisposableTransientTracking()); var disposable = new Disposable(); Disposable second; bool shouldSkip = true; container.Register(context => context .WithFactory(requestContext => { if (shouldSkip) { shouldSkip = false; return requestContext.ExcludeFromTracking(disposable); } return new Disposable(); })); { using var scope = container.BeginScope(); var inst = scope.Resolve(); Assert.Same(disposable, inst); second = scope.Resolve(); Assert.NotSame(disposable, second); } Assert.True(second.IsDisposed); Assert.False(disposable.IsDisposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Ensure_Exclude_Works_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var disposable = new Disposable(); Disposable second; bool shouldSkip = true; container.Register(context => context .WithScopedLifetime() .WithFactory(requestContext => { if (shouldSkip) { shouldSkip = false; return requestContext.ExcludeFromTracking(disposable); } return new Disposable(); })); { using var scope1 = container.BeginScope(); var inst = scope1.Resolve(); Assert.Same(disposable, inst); using var scope2 = container.BeginScope(); second = scope2.Resolve(); Assert.NotSame(disposable, second); } Assert.True(second.IsDisposed); Assert.False(disposable.IsDisposed); } private delegate object Factory(Type serviceType); [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Factory_Type(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register(c => c.WithFactory(r => r.Resolve)); var factory = container.Resolve(); var inst = factory(typeof(Test3)); Assert.NotNull(inst); Assert.IsType(inst); } private delegate ITest TestFactory(string s); [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Factory_Delegate(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(); var factory = container.Resolve(); var inst = factory("test"); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } private delegate void TestAction(string s); [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Throws(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(); Assert.Throws(() => container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Multiple(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType).WithDisposableTransientTracking()) .Register(c => c.WithFactory(() => new TD()).AsServiceAlso().AsServiceAlso().AsServiceAlso()) .Register(); var inst = container.Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); Assert.IsType(inst.T3); Assert.IsType(inst.T4); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Multiple_All(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType).WithDisposableTransientTracking()) .Register(c => c.WithFactory(() => new TD()).AsImplementedTypes()) .Register(); var inst = container.Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); Assert.IsType(inst.T3); Assert.IsType(inst.T4); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(() => new TD())); Assert.NotNull(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_AsServiceAlso(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(() => new TD()).AsServiceAlso()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_DependencyResolver_AsServiceAlso(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(r => new TD()).AsServiceAlso()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_AsImplementedTypes(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(() => new TD()).AsImplementedTypes()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_DependencyResolver_AsImplementedTypes(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(r => new TD()).AsImplementedTypes()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_DependencyResolver_AsServiceAlso_Param(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register(c => c.WithFactory(_ => new TD()).AsServiceAlso()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_DependencyResolver_AsImplementedTypes_Param(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register(c => c.WithFactory(_ => new TD()).AsImplementedTypes()); Assert.IsType(container.Resolve()); Assert.Throws(() => container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_AsServiceAlso_Interface(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(() => (IT1)new TD()).AsServiceAlso()); Assert.IsType(container.Resolve()); Assert.IsType(container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_AsImplementedTypes_Interface(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(c => c.WithFactory(() => (IT1)new TD()).AsImplementedTypes()); Assert.IsType(container.Resolve()); Assert.Throws(() => container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_Unknown(CompilerType compilerType) { using var container1 = new StashboxContainer().Register(); using var container2 = new StashboxContainer(c => c.WithCompiler(compilerType) .WithUnknownTypeResolution(opts => { if (opts.ServiceType == typeof(IT1)) opts.WithFactory(() => container1.Resolve(opts.ServiceType)).AsServiceAlso(); })); Assert.IsType(container2.Resolve()); Assert.IsType(container2.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_Unknown_Open_Generic(CompilerType compilerType) { using var container1 = new StashboxContainer().Register(typeof(ITG<>), typeof(TG<>)); using var container2 = new StashboxContainer(c => c.WithCompiler(compilerType) .WithUnknownTypeResolution(opts => { opts.WithFactory(() => container1.Resolve(opts.ServiceType)); })); Assert.IsType>(container2.Resolve>()); Assert.IsType>(container2.Resolve>()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FactoryTests_Resolve_Action_Without_Implementation_Open_Generic(CompilerType compilerType) { using var container1 = new StashboxContainer().Register(typeof(ITG<>), typeof(TG<>)); using var container2 = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(typeof(ITG<>), c => c.WithFactory(t => container1.Resolve(t.Type))); Assert.IsType>(container2.Resolve>()); Assert.IsType>(container2.Resolve>()); } interface ITG; class TG : ITG; interface ITest { string Name { get; } } interface ITest1 { ITest Test { get; } } interface ITest2 { ITest Test { get; } } class Test3 : ITest { public string Name { get; } } class Test : ITest { public string Name { get; } public Test(string name) { this.Name = name; } } class Test2 : ITest2 { [Dependency] public ITest Test { get; set; } } class Test1 : ITest1 { public ITest Test { get; set; } [InjectionMethod] public void Init(ITest test) { this.Test = test; } } class Test12 : ITest1 { public ITest Test { get; private set; } public Test12(ITest test) { this.Test = test; } } interface ITest4 : ITest { void Init(string name); } class Test4 : ITest4 { public string Name { get; private set; } public void Init(string name) => Name = name; } class Test5 { public IDependencyResolver DependencyResolver { get; } public Test5(IDependencyResolver dependencyResolver) { DependencyResolver = dependencyResolver; } } interface IT1; interface IT2; interface IT3; interface IT4; interface IT5; class TComp : IT1, IT2, IT3, IT4, IT5; class Dummy; class Disposable : IDisposable { public bool IsDisposed { get; set; } public void Dispose() { if (this.IsDisposed) throw new ObjectDisposedException(nameof(Disposable)); this.IsDisposed = true; } } class TD : IT1, IT2, IT3, IT4, IT5 { [InjectionMethod] public void Init() {} } class TT { public IT1 T1 { get; } public IT2 T2 { get; } public IT3 T3 { get; } public IT4 T4 { get; } public TT(IT1 t1, IT2 t2, IT3 t3, IT4 t4) { T1 = t1; T2 = t2; T3 = t3; T4 = t4; } } } ================================================ FILE: test/FuncTests.cs ================================================ using Stashbox.Attributes; using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class FuncTests { [Fact] public void FuncTests_Resolve() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst()); } [Fact] public void FuncTests_Resolve_AssignableFrom() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsAssignableFrom>(inst); Assert.IsType(inst()); } [Fact] public void FuncTests_Resolve_Choose_Last() { var inst = new StashboxContainer() .Register() .Register() .Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst()); } [Fact] public void FuncTests_Resolve_Scoped() { var container = new StashboxContainer() .RegisterScoped(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var inst1 = factory(); var inst2 = factory(); Assert.Same(inst1, inst2); using var scope2 = scope.BeginScope(); var factory1 = scope2.Resolve>(); var inst3 = factory1(); var inst4 = factory1(); Assert.Same(inst3, inst4); Assert.NotSame(inst1, inst3); } [Fact] public void FuncTests_Resolve_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>(); Assert.Null(inst); } [Fact] public void FuncTests_Resolve_Lazy() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.IsType>>(inst); Assert.IsType(inst().Value); } [Fact] public void FuncTests_Resolve_Lazy_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>>(); Assert.Null(inst); } [Fact] public void FuncTests_Resolve_Enumerable() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.IsType>>(inst); Assert.IsType(inst().First()); } [Fact] public void FuncTests_Resolve_Enumerable_Null() { var container = new StashboxContainer(); var inst = container.Resolve>>(); Assert.Empty(inst()); } [Fact] public void FuncTests_Resolve_ConstructorDependency() { var container = new StashboxContainer(); container.Register(); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst.Test); Assert.IsType>(inst.Test); Assert.IsType(inst.Test()); } [Fact] public void FuncTests_Resolve_ConstructorDependency_Null() { var container = new StashboxContainer(); container.Register(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void FuncTests_Resolve_ParameterInjection() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); var t = new Test(); var r = inst(t); Assert.Same(t, r.Test); } [Fact] public void FuncTests_Resolve_ParameterInjection_2Params() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); var t = new Test(); var t1 = new FTest1(); var r = inst(t, t1); Assert.Same(t, r.Test); Assert.Same(t1, r.Test1); } [Fact] public void FuncTests_Resolve_ParameterInjection_3Params() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); var t = new Test(); var t1 = new FTest1(); var t2 = new FTest2(null); var r = inst(t, t1, t2); Assert.Same(t, r.Test); Assert.Same(t1, r.Test1); Assert.Same(t2, r.Test2); } [Fact] public void FuncTests_Resolve_ParameterInjection_SubDependency() { var container = new StashboxContainer(); container.Register(); container.Register(); var inst = container.Resolve>(); var t = new Test(); var r = inst(t); Assert.Same(t, r.Func2.Test); } [Fact] public void FuncTests_Resolve_ParameterInjection_Mixed() { var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(); var inst = container.Resolve(); var d1 = new Dep1(); var d3 = new Dep3(); var d = inst.Dep(d1, d3); var d12 = new Dep1(); Assert.NotSame(d1, d.Dep(d12).Dep); Assert.Same(d12, d.Dep(d12).Dep); } [Fact] public void FuncTests_Resolve_ParameterInjection_Mixed2() { var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); container.Register(); var inst = container.Resolve(); var d3 = new Dep3(); var d = inst.Dep(d3); var d32 = new Dep3(); Assert.NotNull(d.Dep(d32).Dep); Assert.NotSame(d3, d.Dep(d32).Dep); Assert.Same(d3, d.Dep1); } [Fact] public void FuncTests_Register_FuncDelegate() { var container = new StashboxContainer(); container.RegisterFunc((name, resolver) => { Assert.NotNull(name); Assert.NotNull(resolver); return new RegisteredFuncTest(name); }); var test = container.Resolve>()("test"); Assert.Equal("test", test.Name); } [Fact] public void FuncTests_Register_FuncDelegate_Lazy() { var container = new StashboxContainer(); container.RegisterFunc((name, resolver) => { Assert.NotNull(name); Assert.NotNull(resolver); return new RegisteredFuncTest(name); }); var test = container.Resolve>>().Value("test"); Assert.Equal("test", test.Name); } [Fact] public void FuncTests_Register_FuncDelegate_Resolver() { var container = new StashboxContainer(); var test1 = new Test(); container.RegisterInstance(test1); container.RegisterFunc((name, resolver) => new RegisteredFuncTest2(name, resolver.Resolve())); var test = container.Resolve>()("test"); Assert.Same(test1, test.Test1); Assert.Equal("test", test.Name); } [Fact] public void FuncTests_Register_FuncDelegate_TwoParams() { var container = new StashboxContainer(); var t1 = new Test(); var t2 = new Test(); container.RegisterFunc((test1, test2, resolver) => new RegisteredFuncTest3(test1, test2)); var test = container.Resolve>()(t1, t2); Assert.Same(t1, test.Test1); Assert.Same(t2, test.Test2); } [Fact] public void FuncTests_Register_FuncDelegate_ThreeParams() { var container = new StashboxContainer(); var t1 = new Test(); var t2 = new Test(); var t3 = new Test(); container.RegisterFunc((test1, test2, test3, resolver) => new RegisteredFuncTest4(test1, test2, test3)); var test = container.Resolve>()(t1, t2, t3); Assert.Same(t1, test.Test1); Assert.Same(t2, test.Test2); Assert.Same(t3, test.Test3); } [Fact] public void FuncTests_Register_FuncDelegate_FourParams() { var container = new StashboxContainer(); var t1 = new Test(); var t2 = new Test(); var t3 = new Test(); var t4 = new Test(); container.RegisterFunc((test1, test2, test3, test4, resolver) => new RegisteredFuncTest5(test1, test2, test3, test4)); var test = container.Resolve>()(t1, t2, t3, t4); Assert.Same(t1, test.Test1); Assert.Same(t2, test.Test2); Assert.Same(t3, test.Test3); Assert.Same(t4, test.Test4); } [Fact] public async Task FuncTests_Register_FuncDelegate_Async() { var container = new StashboxContainer(); var test = new Test(); container.RegisterInstance(test); container.RegisterFunc(async resolver => await Task.FromResult(resolver.Resolve())); var inst = await container.Resolve>>()(); var inst2 = await container.Resolve>>()(); Assert.Same(test, inst); Assert.Same(inst, inst2); } [Fact] public async Task FuncTests_Register_FuncDelegate_Async_Longrun() { var container = new StashboxContainer(); var test = new Test(); container.RegisterInstance(test); container.RegisterFunc(async resolver => { await Task.Delay(1000); return resolver.Resolve(); }); var inst = await container.Resolve>>()(); Assert.Same(test, inst); } [Fact] public void FuncTests_Register_Named() { var container = new StashboxContainer(); container.RegisterFunc(resolver => new Test(), "test"); var test = container.Resolve>("test")(); Assert.NotNull(test); } [Fact] public void FuncTests_Register_Multiple() { var container = new StashboxContainer(); container.RegisterFunc(resolver => new Test()); container.RegisterFunc(resolver => new Dep1()); Assert.IsAssignableFrom(container.Resolve>()()); Assert.IsAssignableFrom(container.Resolve>()()); } [Fact] public void FuncTests_Register_Multiple_ReMap() { var container = new StashboxContainer(); container.Register(); container.RegisterFunc(resolver => resolver.Resolve()); Assert.NotNull(container.Resolve>()()); container.ReMap(); Assert.NotNull(container.Resolve>()()); } [Fact] public void FuncTests_Register_Parallel() { var container = new StashboxContainer(); Parallel.For(0, 2000, i => { container.RegisterFunc(resolver => new Test(), i.ToString()); var test = container.Resolve>(i.ToString())(); Assert.NotNull(test); }); } [Fact] public void FuncTests_Register_Compiled_Lambda() { var inst = new StashboxContainer() .RegisterFunc(typeof(Test) .GetConstructor(Type.EmptyTypes) .MakeNew() .AsLambda>(typeof(IDependencyResolver).AsParameter()) .Compile()) .Resolve>()(); Assert.NotNull(inst); } [Fact] public void FuncTests_Register_Static_Factory() { var inst = new StashboxContainer() .RegisterFunc(Create) .Resolve>()(); Assert.NotNull(inst); } [Fact] public void FuncTests_Register_Does_Not_Resolve_Same_As_Param() { var preInst = new Test(); var inst = new StashboxContainer() .RegisterInstance(preInst) .Register() .Register() .Resolve(); Assert.NotSame(inst.Factory(new Test()).Test, preInst); Assert.Same(inst.Factory(preInst).Test, preInst); Assert.Same(inst.Test, preInst); } static ITest Create(IDependencyResolver resolver) => new Test(); class RegisteredFuncTest { public string Name { get; } public RegisteredFuncTest(string name) { this.Name = name; } } class RegisteredFuncTest2 { public string Name { get; } public ITest Test1 { get; } public RegisteredFuncTest2(string name, ITest test1) { this.Name = name; this.Test1 = test1; } } class RegisteredFuncTest3 { public ITest Test1 { get; } public ITest Test2 { get; } public RegisteredFuncTest3(ITest test1, ITest test2) { this.Test1 = test1; this.Test2 = test2; } } class RegisteredFuncTest4 { public ITest Test1 { get; } public ITest Test2 { get; } public ITest Test3 { get; } public RegisteredFuncTest4(ITest test1, ITest test2, ITest test3) { this.Test1 = test1; this.Test2 = test2; this.Test3 = test3; } } class RegisteredFuncTest5 { public ITest Test1 { get; } public ITest Test2 { get; } public ITest Test3 { get; } public ITest Test4 { get; } public RegisteredFuncTest5(ITest test1, ITest test2, ITest test3, ITest test4) { this.Test1 = test1; this.Test2 = test2; this.Test3 = test3; this.Test4 = test4; } } class SameAsParamFuncTest { public Func Factory { get; } public ITest Test { get; } public SameAsParamFuncTest(Func factory, ITest test) { this.Factory = factory; this.Test = test; } } class FuncTest { public FunctTest2 Func2 { get; set; } public FuncTest(FunctTest2 func2) { this.Func2 = func2; } } class FunctTest2 { public ITest Test { get; set; } public FunctTest2(ITest test) { this.Test = test; } } class FuncTest3 { public Func Dep { get; set; } public FuncTest3(Func dep) { Dep = dep; } } class FuncTest4 { public Func Dep { get; set; } public Func Dep1 { get; set; } public FuncTest4(Func dep, Func dep1) { Dep = dep; Dep1 = dep1; } } class FuncTest5 { public Func Dep { get; set; } public FuncTest5(Func dep) { Dep = dep; } } class FuncTest6 { public Dep3 Dep1 { get; set; } public Func Dep { get; set; } public FuncTest6(Dep3 dep1, Func dep) { Dep1 = dep1; Dep = dep; } } class Dep1; class Dep2 { public Dep1 Dep { get; set; } public Dep2(Dep1 dep) { Dep = dep; } } class Dep3; class Dep4 { public Dep3 Dep { get; set; } public Dep4(Dep3 dep) { Dep = dep; } } interface ITest; interface IFTest1 { ITest Test { get; set; } } interface IFTest2 { IFTest1 Test1 { get; set; } ITest Test { get; set; } } interface IFTest3 { IFTest2 Test2 { get; set; } IFTest1 Test1 { get; set; } ITest Test { get; set; } } class FTest1 : IFTest1 { public ITest Test { get; set; } [InjectionMethod] public void Init(ITest test) { this.Test = test; } } class FTest2 : IFTest2 { [Dependency] public ITest Test { get; set; } public IFTest1 Test1 { get; set; } public FTest2(IFTest1 test1) { this.Test1 = test1; } } class FTest3 : IFTest3 { [Dependency] public IFTest2 Test2 { get; set; } public IFTest1 Test1 { get; set; } public ITest Test { get; set; } [InjectionMethod] public void Init(ITest test) { this.Test = test; } public FTest3(IFTest1 test1) { this.Test1 = test1; } } class Test : ITest; class Test1 : ITest; class Test2 { public Func Test { get; private set; } public Test2(Func test) { this.Test = test; } } } ================================================ FILE: test/GenericTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class GenericTests { [Fact] public void GenericTests_Resolve() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_Singleton() { using var container = new StashboxContainer(); container.RegisterSingleton(typeof(ITest1<,>), typeof(Test1<,>)); var en = container.Resolve>>(); var inst = container.Resolve>(); Assert.Same(inst, en.ToArray()[0]); } [Fact] public void GenericTests_Resolve_Singleton_Many() { using var container = new StashboxContainer(config => config .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(typeof(ITest1<,>), typeof(Test1<,>)); container.RegisterSingleton(typeof(ITest1<,>), typeof(Test1<,>)); var en = container.Resolve>>(); var inst = container.Resolve>(); Assert.Same(inst, en.ToArray()[1]); } [Fact] public void GenericTests_Resolve_Singleton_Many_Mixed() { using var container = new StashboxContainer(config => config .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); var inst = new Test1(); container.RegisterSingleton(typeof(ITest1), typeof(Test1)); container.RegisterSingleton(typeof(ITest1<,>), typeof(Test1<,>)); container.Register(typeof(ITest1), typeof(Test1), config => config.WithInstance(inst)); var en = container.Resolve>>().ToArray(); Assert.Equal(3, en.Length); Assert.Same(inst, en[2]); } [Fact] public void GenericTests_Resolve_SameTime_DifferentParameter() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); var inst = container.Resolve>(); var inst2 = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.NotNull(inst2); Assert.IsType>(inst2); } [Fact] public void GenericTests_Resolve_SameTime_DifferentParameter_Singleton() { using var container = new StashboxContainer(); container.RegisterSingleton(typeof(ITest1<,>), typeof(Test1<,>)); var inst = container.Resolve>(); var inst2 = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.NotNull(inst2); Assert.IsType>(inst2); } [Fact] public void GenericTests_CanResolve() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); Assert.False(container.CanResolve(typeof(ITest1<,>))); Assert.True(container.CanResolve(typeof(ITest1))); } [Fact] public void GenericTests_Named() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>), config => config.WithName("G1")); container.Register(typeof(ITest1<,>), typeof(Test12<,>), config => config.WithName("G2")); Assert.IsType>(container.Resolve>("G1")); Assert.IsType>(container.Resolve>("G2")); } [Fact] public void GenericTests_DependencyResolve() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); container.Register(typeof(ITest2<,>), typeof(Test2<,>)); var inst = container.Resolve>(); Assert.NotNull(inst.Test); Assert.IsType>(inst.Test); } [Fact] public void GenericTests_Resolve_Fluent() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_ReMap() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); container.ReMap(typeof(ITest1<,>), typeof(Test12<,>)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_ReMap_Fluent() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); container.ReMap(typeof(ITest1<,>), typeof(Test12<,>)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_Parallel() { using var container = new StashboxContainer(); container.Register(typeof(ITest1<,>), typeof(Test1<,>)); Parallel.For(0, 50000, (i) => { var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); }); } [Fact] public void GenericTests_Resolve_Constraint_Array() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest<>)); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); var inst = container.ResolveAll>().ToArray(); Assert.Single(inst); } [Fact] public void GenericTests_Resolve_Constraint_Multiple() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest<>)); var inst = container.Resolve>(); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_Constraint() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); Assert.Throws(() => container.Resolve>()); } [Fact] public void GenericTests_Resolve_Constraint_Constructor() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest<>)); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); container.Register(); var inst = container.Resolve(); Assert.IsType>(inst.Test); } [Fact] public void GenericTests_Resolve_Constraint_Pick_RightImpl() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest3<>)); var inst = container.Resolve>(); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_Constraint_Pick_RightImpl_Decorator() { using var container = new StashboxContainer(); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>)); container.Register(typeof(IConstraintTest<>), typeof(ConstraintTest3<>)); container.RegisterDecorator(typeof(IConstraintTest<>), typeof(ConstraintDecorator<>)); container.RegisterDecorator(typeof(IConstraintTest<>), typeof(ConstraintDecorator2<>)); var inst = (ConstraintDecorator2)container.Resolve>(); Assert.IsType>(inst.ConstraintTest); } [Fact] public void GenericTests_Resolve_Prefer_Open_Generic_In_Named_Scope() { var container = new StashboxContainer(config => config .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)) .Register, Test1>() .Register(typeof(ITest1<,>), typeof(Test1<,>), config => config.InNamedScope("A")); container.BeginScope("A").Resolve>(); Assert.Equal(2, container.ContainerContext.RegistrationRepository.GetRegistrationMappings().Count()); } [Fact] public void GenericTests_Resolve_Prefer_Open_Generic_Enumerable_In_Named_Scope() { var container = new StashboxContainer(config => config .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)) .Register, Test1>(config => config.InNamedScope("A")) .Register(typeof(ITest1<,>), typeof(Test1<,>), config => config.InNamedScope("A")); var res = container.BeginScope("A").Resolve>>(); Assert.Equal(2, res.Count()); } [Fact] public void GenericTests_Resolve_Prefer_Valid_Constraint_In_Named_Scope() { var inst = new StashboxContainer() .Register(typeof(IConstraintTest<>), typeof(ConstraintTest3<>), config => config.InNamedScope("A")) .Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>), config => config.InNamedScope("A")) .BeginScope("A") .Resolve>(); Assert.IsType>(inst); } [Fact] public void GenericTests_Resolve_Prefer_Valid_Constraint_In_Named_Scope_Enumerable() { var inst = new StashboxContainer() .Register(typeof(IConstraintTest<>), typeof(ConstraintTest3<>), config => config.InNamedScope("A")) .Register(typeof(IConstraintTest<>), typeof(ConstraintTest2<>), config => config.InNamedScope("A")) .BeginScope("A") .ResolveAll>(); Assert.Single(inst); } [Fact] public void GenericTests_Nested_Generics() { var inst = new StashboxContainer() .Register(typeof(IGen1<>), typeof(Gen1<>)) .Register(typeof(IGen2<>), typeof(Gen2<>)) .Register() .Resolve>>(); Assert.NotNull(inst.Value); Assert.IsType>(inst.Value); Assert.NotNull(inst.Value.Value); Assert.IsType(inst.Value.Value); } [Fact] public void GenericTests_Nested_Generics_Decorator() { var inst = new StashboxContainer() .Register(typeof(IGen3<>), typeof(Gen3<>)) .RegisterDecorator(typeof(IGen3<>), typeof(Gen3Decorator<>)) .Register() .Resolve>(); Assert.NotNull(inst.Value); Assert.IsType(inst.Value); var decorator = (Gen3Decorator)inst; Assert.NotNull(decorator.Decorated); Assert.IsType>(decorator.Decorated); } [Fact] public void GenericTests_Nested_Within_Same_Request() { var inst = new StashboxContainer() .Register() .Register(typeof(IGen<>), typeof(Gen<>)) .Resolve(); Assert.NotNull(inst); } [Fact] public void GenericTests_Constraint_Contravariant() { using var container = new StashboxContainer() .Register, ConstraintTest4>(); var service = container.Resolve>(); Assert.IsType(service); } [Fact] public void GenericTests_Constraint_Contravariant_Returns_Original() { using var container = new StashboxContainer() .Register, ConstraintTest4>(); Assert.IsType(container.Resolve>()); } [Fact] public void GenericTests_Constraint_Contravariant_Collection() { using var container = new StashboxContainer() .Register, ConstraintTest4>() .Register, ConstraintTest5>(); var services = container.ResolveAll>().ToList(); Assert.Equal(2, services.Count); } [Fact] public void GenericTests_Constraint_Contravariant_Collection_Covariant() { using var container = new StashboxContainer() .Register, ConstraintTest4>() .Register, ConstraintTest5>(); var services = container.ResolveAll>().ToList(); Assert.Single(services); } [Fact] public void GenericTests_Constraint_Covariant() { using var container = new StashboxContainer() .Register, ConstraintTest7>(); var service = container.Resolve>(); Assert.IsType(service); } [Fact] public void GenericTests_Constraint_Covariant_Returns_Original() { using var container = new StashboxContainer() .Register, ConstraintTest7>(); Assert.IsType(container.Resolve>()); } [Fact] public void GenericTests_Constraint_Covariant_Collection() { using var container = new StashboxContainer() .Register, ConstraintTest6>() .Register, ConstraintTest7>(); var services = container.ResolveAll>().ToList(); Assert.Equal(2, services.Count); } [Fact] public void GenericTests_Constraint_Covariant_Collection_Contravariant() { using var container = new StashboxContainer() .Register, ConstraintTest6>() .Register, ConstraintTest7>(); var services = container.ResolveAll>().ToList(); Assert.Single(services); } [Fact] public void Ensure_Open_Generic_Options_Are_Not_Ignored() { using var container = new StashboxContainer() .Register(typeof(B<>), c => c.WithMetadata("A")) .Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.Equal("A", inst.Name); } class A { public object Name { get; set; } public A(Tuple, object> b) { this.Name = b.Item2; } } class B; interface IConstraint; interface IConstraint1; interface IConstraintTest; interface IContravariant; interface ICovariant; class ConstraintTest : IConstraintTest; class ConstraintTest2 : IConstraintTest where T : IConstraint; class ConstraintTest3 : IConstraintTest where T : IConstraint1; class ConstraintTest4 : IContravariant; class ConstraintTest5 : IContravariant; class ConstraintTest6 : ICovariant; class ConstraintTest7 : ICovariant; class ConstraintDecorator : IConstraintTest where T : IConstraint { [Dependency] public IConstraintTest ConstraintTest { get; set; } } class ConstraintDecorator2 : IConstraintTest where T : IConstraint1 { [Dependency] public IConstraintTest ConstraintTest { get; set; } } class ConstraintArgument; class ConstraintArgument1 : IConstraint1; class ConstraintTest3 { public IConstraintTest Test { get; set; } public ConstraintTest3(IConstraintTest test) { this.Test = test; } } interface ITest1 { I IProp { get; } K KProp { get; } } interface ITest2 { ITest1 Test { get; } } class Test1 : ITest1 { public I IProp { get; } public K KProp { get; } } class Test12 : ITest1 { public I IProp { get; } public K KProp { get; } } class Test2 : ITest2 { public ITest1 Test { get; private set; } public Test2(ITest1 test1, ITest1 test2) { Test = test1; } } interface IGen1 { T Value { get; } } interface IGen2 { T Value { get; } } interface IGen3 { T Value { get; } } class Gen1 : IGen1 { public Gen1(T value) { this.Value = value; } public T Value { get; } } class Gen2 : IGen2 { public Gen2(T value) { this.Value = value; } public T Value { get; } } class Gen3 : IGen3 { public Gen3(T value) { this.Value = value; } public T Value { get; } } class Gen3Decorator : IGen3 { public IGen3 Decorated { get; } public Gen3Decorator(IGen3 value) { this.Decorated = value; this.Value = value.Value; } public T Value { get; } } class Stub; class Stub1; interface IGen; class Gen : IGen; class Gen { public Gen(IGen stub, IGen stub1) { } } } ================================================ FILE: test/HierarchyTests.cs ================================================ using Xunit; namespace Stashbox.Tests; public class HierarchyTests { [Fact] public void NamedScope_Hierarchy_Respected() { var container = new StashboxContainer(c => c .WithAutoMemberInjection()) .Register(c => c.DefinesScope("root").WithName("root")) .Register(c => c.InNamedScope("root").DefinesScope("A")) .Register(c => c.InNamedScope("root").DefinesScope("B")) .Register(c => c.InNamedScope("A")) .Register(c => c.InNamedScope("A")) .Register(c => c.InNamedScope("B")) .Register(c => c.InNamedScope("B")); var r = container.Resolve("root"); Assert.IsType(((Test)r).Subs[0]); Assert.IsType(((Test)r).Subs[1]); Assert.IsType(((Test1)((Test)r).Subs[0]).Subs[0]); Assert.IsType(((Test1)((Test)r).Subs[0]).Subs[1]); Assert.IsType(((Test2)((Test)r).Subs[1]).Subs[0]); Assert.IsType(((Test2)((Test)r).Subs[1]).Subs[1]); } [Fact] public void NamedScope_Hierarchy_Respected_WithoutNames() { var container = new StashboxContainer(c => c .WithAutoMemberInjection()) .Register(c => c.DefinesScope().WithName("root")) .Register(c => c.InScopeDefinedBy().DefinesScope()) .Register(c => c.InScopeDefinedBy().DefinesScope()) .Register(c => c.InScopeDefinedBy(typeof(Test1))) .Register(c => c.InScopeDefinedBy(typeof(Test1))) .Register(c => c.InScopeDefinedBy()) .Register(c => c.InScopeDefinedBy()); var r = container.Resolve("root"); Assert.IsType(((Test)r).Subs[0]); Assert.IsType(((Test)r).Subs[1]); Assert.IsType(((Test1)((Test)r).Subs[0]).Subs[0]); Assert.IsType(((Test1)((Test)r).Subs[0]).Subs[1]); Assert.IsType(((Test2)((Test)r).Subs[1]).Subs[0]); Assert.IsType(((Test2)((Test)r).Subs[1]).Subs[1]); } [Fact] public void Conditional_Hierarchy_Respected() { var container = new StashboxContainer(c => c .WithAutoMemberInjection()) .Register(c => c.WithName("root")) .Register(c => c.WhenDependantIs()) .Register(c => c.WhenDependantIs()) .Register(c => c.WhenDependantIs(typeof(Test1))) .Register(c => c.WhenDependantIs(typeof(Test1))) .Register(c => c.WhenDependantIs()) .Register(c => c.WhenDependantIs()); var r = container.Resolve("root"); Assert.IsType(r.Subs[0]); Assert.IsType(r.Subs[1]); Assert.IsType(((Test1)r.Subs[0]).Subs[0]); Assert.IsType(((Test1)r.Subs[0]).Subs[1]); Assert.IsType(((Test2)r.Subs[1]).Subs[0]); Assert.IsType(((Test2)r.Subs[1]).Subs[1]); } interface ITest; class R { public ITest[] Subs { get; set; } } class Test : ITest { public ITest[] Subs { get; set; } } class Test1 : ITest { public ITest[] Subs { get; set; } } class Test2 : ITest { public ITest[] Subs { get; set; } } class Test3 : ITest; class Test4 : ITest; class Test5 : ITest; class Test6 : ITest; } ================================================ FILE: test/InitializerFinalizerTests.cs ================================================ using System; using System.Collections.Generic; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class InitializerFinalizerTests { [Fact] public void InitializerTests_Interface_Method() { ITest test; using (var container = new StashboxContainer()) { container.Register(context => context.WithInitializer((t, resolver) => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void InitializerTests_ImplOnly_Method() { ITest test; using (var container = new StashboxContainer()) { container.Register(); container.Register(context => context.WithInitializer((t, resolver) => t.ImplMethod(resolver.Resolve()))); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Register() { ITest test; using (var container = new StashboxContainer()) { container.Register(context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Register_ByInterface() { ITest test; using (var container = new StashboxContainer()) { container.Register(typeof(Test), context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Register_ByImplementation() { Test test; using (var container = new StashboxContainer()) { container.Register(context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_ReMap() { ITest test; using (var container = new StashboxContainer()) { container.Register(context => context.WithFinalizer(t => t.Method())); container.ReMap(context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_ReMap_ByInterface() { ITest test; using (var container = new StashboxContainer()) { container.Register(typeof(Test), context => context.WithFinalizer(t => t.Method())); container.ReMap(typeof(Test), context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_ReMap_ByImplementation() { Test test; using (var container = new StashboxContainer()) { container.Register(context => context.WithFinalizer(t => t.Method())); container.ReMap(context => context.WithFinalizer(t => t.Method())); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Instance_Interface() { var test = new Test(); using (var container = new StashboxContainer()) { container.RegisterInstance(test, finalizerDelegate: t => t.Method()); test = (Test)container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Instance_Implementation() { var test = new Test(); using (var container = new StashboxContainer()) { container.RegisterInstance(test, finalizerDelegate: t => t.Method()); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_WireUp_Interface() { var test = new Test(); using (var container = new StashboxContainer()) { container.WireUp(test, finalizerDelegate: t => t.Method()); test = (Test)container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_WireUp_Implementation() { var test = new Test(); using (var container = new StashboxContainer()) { container.WireUp(test, finalizerDelegate: t => t.Method()); test = container.Resolve(); } Assert.True(test.MethodCalled); } [Fact] public void FinalizerTests_Register_Multiple_Shouldnt_Throw() { using var container = new StashboxContainer(); container.Register(context => context.WithFinalizer(t => t.Method())); for (var i = 0; i < 10; i++) { var test = container.Resolve(); Assert.False(test.MethodCalled); } } [Fact] public void FinalizerTests_Register_Singleton_Multiple_Shouldnt_Throw() { using var container = new StashboxContainer(); container.Register(context => context.WithFinalizer(t => t.Method()).WithSingletonLifetime()); for (var i = 0; i < 10; i++) { var test = container.Resolve(); Assert.False(test.MethodCalled); } } [Fact] public void FinalizerTests_Register_Scoped_Multiple_Shouldnt_Throw() { using var container = new StashboxContainer(); container.Register(context => context.WithFinalizer(t => t.Method()).WithScopedLifetime()); for (var i = 0; i < 10; i++) { ITest test; using (var scope = container.BeginScope()) { test = scope.Resolve(); Assert.False(test.MethodCalled); } Assert.True(test.MethodCalled); } } [Fact] public void FinalizerTests_Instance_Implementation_Multiple_Shouldnt_Throw() { var test = new Test(); using (var container = new StashboxContainer()) { container.RegisterInstance(test, finalizerDelegate: t => t.Method()); for (var i = 0; i < 10; i++) { var test1 = container.Resolve(); Assert.False(test1.MethodCalled); Assert.Same(test, test1); } } Assert.True(test.MethodCalled); } [Fact] public async Task AsyncInitializer_Ensure_Order_Singleton() { using var container = new StashboxContainer() .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithSingletonLifetime()) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync())) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync())); var initializables = new List(); container.Resolve(dependencyOverrides: [initializables]); await container.InvokeAsyncInitializers(); Assert.Equal(3, initializables.Count); Assert.IsType(initializables[0]); Assert.IsType(initializables[1]); Assert.IsType(initializables[2]); } [Fact] public async Task AsyncInitializer_Ensure_Order() { using var container = new StashboxContainer() .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync())) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync())) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync())); var initializables = new List(); container.Resolve(dependencyOverrides: [initializables]); await container.InvokeAsyncInitializers(); Assert.Equal(4, initializables.Count); Assert.IsType(initializables[0]); Assert.IsType(initializables[1]); Assert.IsType(initializables[2]); Assert.IsType(initializables[3]); } [Fact] public async Task AsyncInitializer_Scoped_Multiple() { using var container = new StashboxContainer() .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithScopedLifetime()) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithScopedLifetime()) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithScopedLifetime()); var initializables = new List(); using var scope = container.BeginScope(); scope.Resolve(dependencyOverrides: [initializables]); await scope.InvokeAsyncInitializers(); Assert.Equal(3, initializables.Count); Assert.IsType(initializables[0]); Assert.IsType(initializables[1]); Assert.IsType(initializables[2]); scope.Resolve(dependencyOverrides: [initializables]); await scope.InvokeAsyncInitializers(); Assert.Equal(3, initializables.Count); } [Fact] public async Task AsyncInitializer_Scoped_Singleton_Multiple() { using var container = new StashboxContainer() .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithSingletonLifetime()) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithScopedLifetime()) .Register(c => c.WithAsyncInitializer((t, r, c) => t.InitAsync()).WithScopedLifetime()); var initializables = new List(); using var scope1 = container.BeginScope(); scope1.Resolve(dependencyOverrides: [initializables]); await scope1.InvokeAsyncInitializers(); Assert.Equal(3, initializables.Count); Assert.IsType(initializables[0]); Assert.IsType(initializables[1]); Assert.IsType(initializables[2]); using var scope2 = container.BeginScope(); scope2.Resolve(dependencyOverrides: [initializables]); await scope2.InvokeAsyncInitializers(); Assert.Equal(5, initializables.Count); Assert.IsType(initializables[0]); Assert.IsType(initializables[1]); Assert.IsType(initializables[2]); Assert.IsType(initializables[3]); Assert.IsType(initializables[4]); } [Fact] public void Finalizers_Ensure_Order_Singleton() { var finalizables = new List(); { using var container = new StashboxContainer() .Register(c => c.WithFinalizer(t => t.Fin()).WithSingletonLifetime()) .Register(c => c.WithFinalizer(t => t.Fin())) .Register(c => c.WithFinalizer(t => t.Fin())); container.Resolve(dependencyOverrides: [finalizables]); } Assert.Equal(3, finalizables.Count); Assert.IsType(finalizables[0]); Assert.IsType(finalizables[1]); Assert.IsType(finalizables[2]); } [Fact] public void Finalizers_Ensure_Order() { var finalizables = new List(); { using var container = new StashboxContainer() .Register(c => c.WithFinalizer(t => t.Fin())) .Register(c => c.WithFinalizer(t => t.Fin())) .Register(c => c.WithFinalizer(t => t.Fin())); container.Resolve(dependencyOverrides: [finalizables]); } Assert.Equal(4, finalizables.Count); Assert.IsType(finalizables[0]); Assert.IsType(finalizables[1]); Assert.IsType(finalizables[2]); Assert.IsType(finalizables[3]); } interface ITest { bool MethodCalled { get; } void Method(); } class Test1; class Test : ITest { public void Method() { if (this.MethodCalled) throw new Exception("Method called multiple times!"); this.MethodCalled = true; } public void ImplMethod(Test1 t) { if (t == null) throw new NullReferenceException(); if (this.MethodCalled) throw new Exception("Method called multiple times!"); this.MethodCalled = true; } public bool MethodCalled { get; private set; } } interface IT; class T1 : IT { private readonly List initializables; public T1(List initializables) { this.initializables = initializables; } public Task InitAsync() { this.initializables.Add(this); return Task.FromResult(false); } } class T2 : IT { private readonly List initializables; private readonly T1 t1; public T2(List initializables, T1 t1) { this.initializables = initializables; this.t1 = t1; } public Task InitAsync() { this.initializables.Add(this); return Task.FromResult(false); } } class T3 : IT { private readonly List initializables; private readonly T1 t1; private readonly T2 t2; public T3(List initializables, T1 t1, T2 t2) { this.initializables = initializables; this.t1 = t1; this.t2 = t2; } public Task InitAsync() { this.initializables.Add(this); return Task.FromResult(false); } } class F1 : IT { private readonly List finalizables; public F1(List finalizables) { this.finalizables = finalizables; } public void Fin() { this.finalizables.Add(this); } } class F2 : IT { private readonly List finalizables; private readonly F1 f1; public F2(List finalizables, F1 f1) { this.finalizables = finalizables; this.f1 = f1; } public void Fin() { this.finalizables.Add(this); } } class F3 : IT { private readonly List finalizables; private readonly F1 f1; private readonly F2 f2; public F3(List finalizables, F2 f2, F1 f1) { this.finalizables = finalizables; this.f1 = f1; this.f2 = f2; } public void Fin() { this.finalizables.Add(this); } } } ================================================ FILE: test/InjectionMemberTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Exceptions; using System; using System.Collections.Generic; using Xunit; namespace Stashbox.Tests; public class InjectionMemberTests { [Fact] public void InjectionMemberTests_Resolve() { using var container = new StashboxContainer(); container.Register(); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test); Assert.IsType(inst); Assert.IsType(inst.Test); Assert.NotNull(((Test1)inst).TestFieldProperty); Assert.IsType(((Test1)inst).TestFieldProperty); } [Fact] public void InjectionMemberTests_Resolve_WithoutRegistered() { using var container = new StashboxContainer(); var test1 = new Test1(); container.WireUp(test1); Assert.Throws(() => container.Resolve()); } [Fact] public void InjectionMemberTests_WireUp() { using var container = new StashboxContainer(); container.Register(); var test1 = new Test1(); container.WireUp(test1); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test); Assert.IsType(inst); Assert.IsType(inst.Test); Assert.NotNull(((Test1)inst).TestFieldProperty); Assert.IsType(((Test1)inst).TestFieldProperty); } [Fact] public void InjectionMemberTests_Resolve_InjectionParameter() { var container = new StashboxContainer(); container.Register(context => context.WithInjectionParameters(new KeyValuePair("Name", "test"))); var inst = container.Resolve(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void InjectionMemberTests_Resolve_InjectionParameter_WithNull() { var container = new StashboxContainer(c => c.WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Null(inst.Name); } [Fact] public void InjectionMemberTests_Inject_With_Config() { var container = new StashboxContainer(); container.Register(context => context.WithDependencyBinding("Test1", "test1").WithDependencyBinding("test2", "test2")) .Register(context => context.WithName("test1")) .Register(context => context.WithName("test2")); var inst = container.Resolve(); Assert.NotNull(inst.Test1); Assert.NotNull(inst.Test2); Assert.IsType(inst.Test1); Assert.IsType(inst.Test2); } [Fact] public void InjectionMemberTests_Inject_With_Invalid_Config() { var container = new StashboxContainer(); container.Register(context => context.WithDependencyBinding("Test3")); var inst = container.Resolve(); Assert.Null(inst.Test1); Assert.Null(inst.Test2); } [Fact] public void InjectionMemberTests_Inject_With_Config_Generic() { var container = new StashboxContainer(); container.Register(context => context.WithDependencyBinding(x => x.Test1, "test2")) .Register(context => context.WithName("test2")); var inst = container.Resolve(); Assert.NotNull(inst.Test1); Assert.Null(inst.Test2); Assert.IsType(inst.Test1); } [Fact] public void InjectionMemberTests_Inject_With_Config_Generic_Throws() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.WithDependencyBinding(x => 50))); } [Fact] public void InjectionMemberTests_Exclude_Globally() { var inst = new StashboxContainer(config => config.WithUnknownTypeResolution() .WithAutoMemberInjection(filter: info => info.Name != "Test4")) .Activate(); Assert.Null(inst.Test4); Assert.NotNull(inst.Test5); } [Fact] public void InjectionMemberTests_Exclude_PerReg() { var inst = new StashboxContainer(config => config.WithUnknownTypeResolution()) .Register(config => config.WithAutoMemberInjection(filter: info => info.Name != "Test4")) .Resolve(); Assert.Null(inst.Test4); Assert.NotNull(inst.Test5); } [Fact] public void InjectionMemberTests_Throws_Field() { using var container = new StashboxContainer() .Register(); Assert.Throws(() => container.Resolve()); } #if HAS_REQUIRED [Fact] public void InjectionMemberTests_AutoInject_Required() { using var container = new StashboxContainer() .Register() .Register() .Register(); var inst = container.Resolve(); Assert.NotNull(inst.Test4); Assert.NotNull(inst.Test5); } [Fact] public void InjectionMemberTests_AutoInject_Required_Disabled_Global() { using var container = new StashboxContainer(c => c.WithRequiredMemberInjection(false)) .Register() .Register() .Register(); var inst = container.Resolve(); Assert.Null(inst.Test4); Assert.Null(inst.Test5); } [Fact] public void InjectionMemberTests_AutoInject_Required_Disabled_Reg() { using var container = new StashboxContainer() .Register(c => c.WithRequiredMemberInjection(false)) .Register() .Register(); var inst = container.Resolve(); Assert.Null(inst.Test4); Assert.Null(inst.Test5); } [Fact] public void InjectionMemberTests_AutoInject_Required_Disabled_Global_Enabled_Reg() { using var container = new StashboxContainer(c => c.WithRequiredMemberInjection(false)) .Register(c => c.WithRequiredMemberInjection()) .Register() .Register(); var inst = container.Resolve(); Assert.NotNull(inst.Test4); Assert.NotNull(inst.Test5); } #endif interface ITest; interface ITest1 { ITest Test { get; } } class Test : ITest; class TestM1 : ITest; class TestM2 : ITest; class Test1 : ITest1 { [Dependency] private ITest testField = null; public ITest TestFieldProperty => this.testField; [Dependency] public ITest Test { get; set; } } interface ITest2 { string Name { get; set; } } class Test2 : ITest2 { [Dependency] public string Name { get; set; } } class Test3 { public ITest Test1 { get; set; } private ITest test2 = null; public ITest Test2 => this.test2; public void Test() { } } class Test4; class Test5; class Test6 { public Test4 Test4 { get; set; } public Test5 Test5 { get; set; } } class Test7 { [Dependency] #pragma warning disable 169 private Test4 test4; #pragma warning restore 169 } #if HAS_REQUIRED class Test8 { public required Test4 Test4 { get; init; } #pragma warning disable CS0649 // Field is never assigned to, and will always have its default value public required Test5 Test5; #pragma warning restore CS0649 // Field is never assigned to, and will always have its default value } #endif } ================================================ FILE: test/InstanceBuilderTests.cs ================================================ using Xunit; namespace Stashbox.Tests; public class InstanceBuilderTests { [Fact] public void InstanceBuilderTests_Resolve() { using var container = new StashboxContainer(); var dep = new Test(); container.RegisterInstance(dep); var inst = container.Resolve(); Assert.Same(inst, dep); } [Fact] public void InstanceBuilderTests_DependencyResolve() { using var container = new StashboxContainer(); var dep = new Test(); container.RegisterInstance(dep); container.Register(); var inst = container.Resolve(); Assert.Same(inst.Test, dep); } [Fact] public void InstanceBuilderTests_Resolve_Fluent() { using var container = new StashboxContainer(); var dep = new Test(); container.Register(context => context.WithInstance(dep)); var inst = container.Resolve(); Assert.Same(inst, dep); } [Fact] public void InstanceBuilderTests_Resolve_Fluent_ReMap() { using var container = new StashboxContainer(); var dep = new Test(); var dep1 = new Test(); container.Register(context => context.WithInstance(dep)); container.ReMap(context => context.WithInstance(dep1)); var inst = container.Resolve(); Assert.Same(inst, dep1); } [Fact] public void InstanceBuilderTests_Resolve_Fluent_ReMap_Self() { using var container = new StashboxContainer(); var dep = new Test(); var dep1 = new Test(); container.Register(dep.GetType(), context => context.WithInstance(dep)); container.ReMap(dep1.GetType(), context => context.WithInstance(dep1)); var inst = container.Resolve(); Assert.Same(inst, dep1); } [Fact] public void InstanceBuilderTests_DependencyResolve_Fluent() { using var container = new StashboxContainer(); var dep = new Test(); container.Register(context => context.WithInstance(dep)); container.Register(); var inst = container.Resolve(); Assert.Same(inst.Test, dep); } [Fact] public void InstanceBuilderTests_Multiple() { using var container = new StashboxContainer(); var dep = new Test(); var dep2 = new Test1(new Test()); container.RegisterInstance(dep); container.RegisterInstance(dep2); container.Resolve(); var inst = container.Resolve(); Assert.Same(inst, dep2); } interface ITest; interface ITest1 { ITest Test { get; } } class Test : ITest; class Test1 : ITest1 { public ITest Test { get; } public Test1(ITest test) { this.Test = test; } } } ================================================ FILE: test/IssueTests/102_Resolving_Func_use_wrong_constructor.cs ================================================ using Stashbox.Configuration; using System; using Xunit; namespace Stashbox.Tests.IssueTests; public class ResolvingFunUseWrongConstructor { [Fact] public void Ensure_Good_Constructor_Selected() { var container = new StashboxContainer(); container.RegisterAssemblyContaining(); var funcA = container.Resolve>(); var classA = container.Resolve(); var injectedA = funcA(classA); var funcB = container.Resolve>(); var classB = container.Resolve(); var injectedB = funcB(classB); Assert.Equal(classA, injectedA.ClassA); Assert.Equal(classB, injectedB.ClassB); } class ClassA; class ClassB; class InjectedClass { public ClassA ClassA; public ClassB ClassB; public InjectedClass(ClassA classA) { ClassA = classA; } public InjectedClass(ClassB classB) { ClassB = classB; } } [Fact] public void Ensure_Good_Constructor_Selected_Deeper_In_The_Tree() { var container = new StashboxContainer(); container.Register().Register().Register().Register(); var fa = container.Resolve>(); var a = container.Resolve(); var instA = fa(a); var fb = container.Resolve>(); var b = container.Resolve(); var instB = fb(b); Assert.Equal(a, instA.Subject1.A); Assert.Equal(b, instB.Subject1.B); } [Fact] public void Ensure_Constructor_Most_Params_Selector_Respects_Func_Param() { var container = new StashboxContainer(); container.Register().Register().Register(); var fb = container.Resolve>(); var b = container.Resolve(); var instB = fb(b); Assert.Equal(b, instB.B); Assert.NotNull(instB.A); } [Fact] public void Ensure_Constructor_Least_Params_Selector_Respects_Func_Param() { var container = new StashboxContainer(); container.Register().Register().Register(c => c.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters)); var fb = container.Resolve>(); var b = container.Resolve(); var instB = fb(b); Assert.Equal(b, instB.B); Assert.Null(instB.A); } class A; class B; class Subject1 { public Subject1(A a) { A = a; } public Subject1(B b) { B = b; } public A A { get; } public B B { get; } } class Subject2 { public Subject2(Subject1 subject1) { Subject1 = subject1; } public Subject1 Subject1 { get; } } class Subject3 { public Subject3(A a) { A = a; } public Subject3(B b) { B = b; } public Subject3(B b, A a) { B = b; A = a; } public B B { get; } public A A { get; } } } ================================================ FILE: test/IssueTests/103_Resolving_base_class_dependencies.cs ================================================ using Stashbox.Attributes; using Xunit; namespace Stashbox.Tests.IssueTests; public class A; public class B; public class BaseClass { [Dependency] public A A { get; set; } public bool DoneA { get; set; } [InjectionMethod] public void InjectA() { DoneA = true; } } public class MainClass : BaseClass { [Dependency] public B B { get; set; } public bool DoneB { get; set; } [InjectionMethod] public void InjectB() { DoneB = true; } } public class BaseClassMethod { [Fact] public void Test() { var container = new StashboxContainer(); container.Register().Register().Register(); var main = container.Resolve(); Assert.IsType(main.B); Assert.IsType(main.A); Assert.True(main.DoneA); Assert.True(main.DoneB); } } ================================================ FILE: test/IssueTests/105_Question_How_to_work_with_dependency_overrides_from_factory_method.cs ================================================ using Stashbox.Exceptions; using Stashbox.Resolution; using Xunit; namespace Stashbox.Tests.IssueTests; public class QuestionHowTWorkWithDependencyOverridesFromFactoryMethod { [Fact] public void Ensure_Context_Available_In_Factory() { var fakeOverride = new object(); var dep2Override = new Dep2(); using var container = new StashboxContainer() .Register(c => c.WithFactory(ctx => { var d = ctx.GetDependencyOverrideOrDefault(); Assert.Same(dep2Override, d); Assert.Equal([dep2Override, fakeOverride], ctx.GetOverrides()); return new Test(d); })) .Register(); var t = container.Resolve(dependencyOverrides: [dep2Override, fakeOverride]); Assert.Same(dep2Override, t.Dep); } [Fact] public void Returns_Null_When_No_Overrides_Passed() { using var container = new StashboxContainer() .Register(c => c.WithFactory(ctx => { var d = ctx.GetDependencyOverrideOrDefault(); Assert.Null(d); Assert.Empty(ctx.GetOverrides()); return new Dep1(); })); container.Resolve(); } [Fact] public void Ensure_Override_Doesnt_Trigger_Unknown() { var depOverride = new Dep2(); using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(ctx => { ctx.Skip(); })) .Register(); var t = container.Resolve(dependencyOverrides: [depOverride]); Assert.Same(depOverride, t.Dep); } [Fact] public void Ensure_Skipping_Doesnt_Let_Uknown_Type_Be_Registered() { var depOverride = new Dep2(); using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(ctx => { if (ctx.ServiceType == typeof(IDep)) ctx.Skip(); })) .Register() .Register(); Assert.Throws(() => container.Resolve()); Assert.NotNull(container.Resolve()); } class Test { public IDep Dep { get; set; } public Test(IDep dep) { this.Dep = dep; } } class Test1 { public Dep1 Dep { get; set; } public Test1(Dep1 dep) { this.Dep = dep; } } interface IDep; class Dep1 : IDep; class Dep2 : IDep; } ================================================ FILE: test/IssueTests/114_Unable_to_resolve_IHubContext.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class UnableToResolveIHubContext { [Fact] public void Ensure_Resolving_HubContext_Works() { using var container = new StashboxContainer() .Register(typeof(IHubContext<,>), typeof(HubContext<,>)) .Register(typeof(HubLifetimeManager<>)); Assert.NotNull(container.Resolve>()); } class Hub; class Hub : Hub where T : class; interface IHubContext where THub : Hub where T : class; class HubLifetimeManager where THub : Hub; class HubContext : IHubContext where THub : Hub where T : class { public HubContext(HubLifetimeManager lifetimeManager) { } } interface ITest; class TestHub : Hub; } ================================================ FILE: test/IssueTests/116_Different_types_registered_with_the_same_name.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class DifferentTypesRegisteredWithTheSameName { [Fact] public void Ensure_different_types_with_same_name_doesnt_throw_exception() { var container = new StashboxContainer(); container.Register(options => options.WithName(Name1).WithSingletonLifetime()) .Register(options => options.WithName(Name2).WithSingletonLifetime().WithFactory(resolver => resolver.Resolve(Name1).Bar)); Assert.NotNull(container.Resolve(Name1)); Assert.NotNull(container.Resolve(Name2)); } [Fact] public void Ensure_name_is_not_unique_between_types() { var container = new StashboxContainer(); container.Register(options => options.WithName(Name1).WithSingletonLifetime()) .Register(options => options.WithName(Name2).WithSingletonLifetime().WithFactory(resolver => resolver.Resolve(Name1).Bar)); Assert.NotNull(container.Resolve(Name2)); } const string Name1 = nameof(Name1); const string Name2 = nameof(Name1); sealed class Foo { public Bar Bar { get; } public Foo() { this.Bar = new(); } } sealed class Bar; } ================================================ FILE: test/IssueTests/118_Named_resolution_using_ResolveAll_returns_all_named_and_unnamed_instanсes.cs ================================================ using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class NamedResolutioUsingResolveAllReturnsAllNamedAndUnnameInstanсes { [Fact] public void Ensure_Named_ResolveAll_Works() { using var container = new StashboxContainer() .Register("test1") .Register("test2") .Register(); var inst = container.ResolveAll("test1").ToArray(); var nameLess = container.ResolveAll().ToArray(); Assert.Single(inst); Assert.Equal(3, nameLess.Length); } [Fact] public void Ensure_Named_ResolveAll_Works_Injection() { using var container = new StashboxContainer() .Register("test1") .Register("test2") .Register() .Register(c => c.WithDependencyBinding>("test1")) .Register("t2"); var inst = container.Resolve(); var nameLess = container.Resolve("t2"); Assert.Single(inst.Tests); Assert.Equal(3, nameLess.Tests.Count()); } [Fact] public void Ensure_Named_ResolveAll_Works_Injection_Convention() { using var container = new StashboxContainer(c => c.TreatParameterAndMemberNameAsDependencyName()) .Register("test1") .Register("test2") .Register() .Register(); var inst = container.Resolve(); Assert.Single(inst.Tests); } interface ITest; class Test1 : ITest; class Test2 { public Test2(IEnumerable test1) { this.Tests = test1; } public IEnumerable Tests { get; } } } ================================================ FILE: test/IssueTests/119_generic_resolution_issue.cs ================================================ using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class GenericResolutionIssue { [Fact] public void Ensure_AsServiceAlso_works() { using var container = new StashboxContainer(); container.Register(c => c.AsServiceAlso>().AsServiceAlso>().AsServiceAlso>().AsServiceAlso>()); var inst = container.Resolve(); Assert.NotNull(inst); var mappings = container.GetRegistrationMappings(); Assert.Equal(5, mappings.Count()); } class C; class AT : IA, IA, IB, IC; interface IA; interface IA; interface IB; interface IC; class B { public B(IA c) { } } } ================================================ FILE: test/IssueTests/129_Sharing_singleton_instances_between_Resolve_and_ResolveAll_and_subtypes.cs ================================================ using TestAssembly; using Xunit; namespace Stashbox.Tests.IssueTests; public class Issue129 { [Fact] public void ResolveAllResolvesExistingInstance() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining( type => typeof(ITA_T1).IsAssignableFrom(type), configurator: opt => opt.WithSingletonLifetime() ); var all = container.ResolveAll(); var instance = container.Resolve(); Assert.Contains(all, it => it is TA_T1); Assert.Contains(all, it => it == instance); } [Fact] public void ResolveAllResolvesExistingInstance_Ensure_AsImplementedTypes_Doesnt_Replace() { using var container = new StashboxContainer(); container.RegisterAssemblyContaining( type => typeof(ITA_T1).IsAssignableFrom(type), configurator: opt => opt.WithSingletonLifetime().AsImplementedTypes() ); var all = container.ResolveAll(); var instance = container.Resolve(); Assert.Contains(all, it => it is TA_T1); Assert.Contains(all, it => it == instance); } } ================================================ FILE: test/IssueTests/132_OpenGenericResolveIssue.cs ================================================ using System; using Microsoft.Win32; using Xunit; namespace Stashbox.Tests.IssueTests; public class OpenGenericResolveIssue { [Fact] public void Ensure_Generic_Class_Constraint_Works_With_Interface() { using var container = new StashboxContainer(); container.Register(typeof(IA<,>), typeof(A<,>)); Assert.NotNull(container.Resolve>()); Assert.NotNull(container.Resolve>()); Assert.NotNull(container.Resolve>()); } [Fact] public void Ensure_Generic_Struct_Constraint_Works() { using var container = new StashboxContainer(); container.Register(typeof(IS<>), typeof(S<>)); Assert.NotNull(container.Resolve>()); } interface IA; class B; abstract class C; interface IA where TV : class; class A : IA where TV : class; interface IS where T : struct; class S : IS where T:struct; struct D; } ================================================ FILE: test/IssueTests/141_Decorator_and_ResolveAll.cs ================================================ using System.Collections.Generic; using System.Linq; using Stashbox.Configuration; using Xunit; namespace Stashbox.Tests.IssueTests; public class DecoratorAndResolveAll { [Fact] public void Ensure_DecoratorAndResolveAll_Works() { var container = new StashboxContainer(c => { c.WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications); }); container.Register(c => c.WithSingletonLifetime().AsImplementedTypes()); container.Register(c => c.WithSingletonLifetime().AsImplementedTypes()); container.Register(c => c.WithSingletonLifetime().AsImplementedTypes()); container.Register(c => c.WithSingletonLifetime().AsImplementedTypes()); container.RegisterDecorator(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.AsImplementedTypes()); container.RegisterDecorator(c => c.AsImplementedTypes()); container.Register(); var services = container.Resolve().Services.ToArray(); Assert.Equal(4, services.Length); Assert.IsType(services[0]); Assert.IsType(((CachedAnimalRepository)services[0]).Service); Assert.IsType(services[1]); Assert.IsType(((CachedCarRepository)services[1]).Service); Assert.IsType(services[2]); Assert.IsType(((CachedPhoneRepository)services[2]).Service); Assert.IsType(services[3]); } interface IDataRepository { void CommonMethod(); } interface IAnimalRepository : IDataRepository; interface ICarRepository : IDataRepository; interface IPhoneRepository : IDataRepository; interface IHouseRepository : IDataRepository; sealed class AnimalRepository : IAnimalRepository { public void CommonMethod() {} } sealed class CarRepository : ICarRepository { public void CommonMethod() {} } sealed class PhoneRepository : IPhoneRepository { public void CommonMethod() {} } sealed class HouseRepository : IHouseRepository { public void CommonMethod() {} } sealed class CachedAnimalRepository : IAnimalRepository { public IAnimalRepository Service { get; } public CachedAnimalRepository(IAnimalRepository service) { Service = service; } public void CommonMethod() {} } sealed class CachedCarRepository : ICarRepository { public ICarRepository Service { get; } public CachedCarRepository(ICarRepository service) { Service = service; } public void CommonMethod() {} } sealed class CachedPhoneRepository : IPhoneRepository { public IPhoneRepository Service { get; } public CachedPhoneRepository(IPhoneRepository service) { Service = service; } public void CommonMethod() {} } sealed class CollectionOfServices { public IEnumerable Services { get; } public CollectionOfServices(IEnumerable services) { Services = services; } } } ================================================ FILE: test/IssueTests/144_Generic_decorators_broken.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class GenericDecoratorsBroken { [Fact] public void Ensure_GenericDecorators_Working() { using var container = new StashboxContainer(); container.RegisterSingleton(typeof(ICommand), typeof(Command)); container.RegisterDecorator(typeof(ICommand), typeof(Decorator)); var decorator = container.Resolve>(); Assert.IsType>(decorator); Assert.IsType>(((Decorator)decorator).Dep); } [Fact] public void Ensure_GenericDecorators_Working_Open() { using var container = new StashboxContainer(); container.RegisterSingleton(typeof(ICommand), typeof(Command)); container.RegisterDecorator(typeof(ICommand<>), typeof(Decorator<>)); var decorator = container.Resolve>(); Assert.IsType>(decorator); Assert.IsType>(((Decorator)decorator).Dep); } interface ICommand; class Command : ICommand; class Decorator : ICommand { public ICommand Dep { get; } public Decorator(ICommand dep) { Dep = dep; } } class Context; } ================================================ FILE: test/IssueTests/163_Last_write_win_problem_when_hash_collision_happens.cs ================================================ using Stashbox.Tests.Utils; using Xunit; namespace Stashbox.Tests.IssueTests; public class LastWriteWinProblemWhenHashCollisionHappens { [Fact] public void Ensure_Collision_Doesnt_Happen() { var (type1, type2) = TypeGen.GetCollidingTypes(); var services = new StashboxContainer(); services.Register(type1); services.Register(type2); Assert.NotNull(services.Resolve(type2)); Assert.NotNull(services.Resolve(type1)); } } ================================================ FILE: test/IssueTests/16_Extensions_Identity_OptionsMonitor.cs ================================================ using System.Collections.Generic; using Xunit; namespace Stashbox.Tests.IssueTests; public class ExtensionsIdentityOptionsMonitor { [Fact] public void ExtensionsIdentityOptionsMonitor_WithoutVariance() { using var container = new StashboxContainer(c => c.WithVariantGenericTypes(false)); container.Register, Op>(); container.Register, Op>(); Assert.Single(container.Resolve>>()); } private interface IOp; private class Op : IOp; private class A; private class B : A; } ================================================ FILE: test/IssueTests/213_Bug_Resolving_Lazy_Func.cs ================================================ using System; using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests.IssueTests; public class BugResolvingLazyFunc { [Fact] public void ResolvingLazyWrapper_ShouldNotInstantiateService_Singleton() { using var container = new StashboxContainer(); container.RegisterSingleton(); var lazy = container.Resolve>(); Assert.Throws(() => lazy.Value); } [Fact] public void ResolvingFuncWrapper_ShouldNotInstantiateService_Singleton() { using var container = new StashboxContainer(); container.RegisterSingleton(); var func = container.Resolve>(); Assert.Throws(() => func()); } [Fact] public void ResolvingLazyWrapper_ShouldNotInstantiateService_Scoped() { using var container = new StashboxContainer(); container.RegisterScoped(); var lazy = container.Resolve>(); Assert.Throws(() => lazy.Value); var scope = container.BeginScope(); lazy = scope.Resolve>(); Assert.Throws(() => lazy.Value); } [Fact] public void ResolvingFuncWrapper_ShouldNotInstantiateService_Scoped() { using var container = new StashboxContainer(); container.RegisterScoped(); var func = container.Resolve>(); Assert.Throws(() => func()); var scope = container.BeginScope(); func = scope.Resolve>(); Assert.Throws(() => func()); } private class Service { public Service() { throw new InvalidOperationException("Don't instantiate me yet!"); } } } ================================================ FILE: test/IssueTests/228_Stashbox_does_not_handle_Optional_correctly.cs ================================================ #nullable enable using System.Runtime.InteropServices; using Xunit; namespace Stashbox.Tests.IssueTests; public class StashboxDoesNotHandleOptionalCorrectly { [Fact] public void Optional_Works() { using var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); Assert.NotNull(container.Resolve()); Assert.NotNull(container.Resolve()); Assert.NotNull(container.Resolve()); } [Fact] public void Optional_Works_Unknown() { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution()); container.Register(); Assert.NotNull(container.Resolve()); } private class A { } private class B([Optional] A? a) { } private class C([Optional] int? a) { } private class D([Optional] string? a) { } private class E([Optional] A a) { } } ================================================ FILE: test/IssueTests/33_ScopedLifetime_thread_safety.cs ================================================ using System.Threading; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests.IssueTests; public class ScopedLifetimeThreadSafeTests { [Fact] public void ScopedLifetime_thread_safety() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); for (var i = 0; i < 1000; i++) { using var scope = container.BeginScope(); Parallel.For(0, 50, _ => { var inst = scope.Resolve(); Assert.Same(inst, scope.Resolve()); }); } } [Fact] public void ScopedLifetime_thread_safety_count() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); for (var i = 0; i < 1000; i++) { using var scope = container.BeginScope(); Parallel.For(0, 50, _ => { scope.Resolve(); }); } Assert.Equal(1000, TestC.Counter); } [Fact] public void ScopedLifetime_thread_safety_generic() { using IStashboxContainer container = new StashboxContainer(); container.Register(typeof(TestG<>), c => c.WithScopedLifetime()); for (var i = 0; i < 1000; i++) { using var scope = container.BeginScope(); Parallel.For(0, 50, _ => { var inst = scope.Resolve>(); Assert.Same(inst, scope.Resolve>()); }); } } class Test; class TestG; class TestC { public static int Counter = 0; public TestC() { Interlocked.Increment(ref Counter); } } } ================================================ FILE: test/IssueTests/34_Resolution_from_parent_container.cs ================================================ using System; using System.Collections.Generic; using Xunit; namespace Stashbox.Tests.IssueTests; public class ResolutionFromParentContainerTests { [Fact] public void ContainerTests_ChildContainer_Resolve_Dependency_From_Child() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer(); child.Register(); child.Register(); var test3 = child.Resolve(); Assert.NotNull(test3); Assert.IsType(test3); Assert.Same(container.ContainerContext, child.ContainerContext.ParentContext); } [Fact] public void ContainerTests_ChildContainer_Resolve_Dependency_Across_Childs() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer().CreateChildContainer(); child.Register(); child.Register(); var test3 = child.Resolve(); Assert.NotNull(test3); } [Fact] public void ContainerTests_ChildContainer_Resolve_Dependency_Across_Childs_2() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer(); child.Register(); var child2 = child.CreateChildContainer(); child2.Register(); var test3 = child2.Resolve(); Assert.NotNull(test3); } [Fact] public void ContainerTests_ChildContainer_Resolve_Dependency_Across_Childs_3() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer(); child.Register(); var child2 = child.CreateChildContainer(); child2.Register(); var test3 = child2.Resolve(); Assert.NotNull(test3); } [Fact] public void ContainerTests_ChildContainer_Resolve_Dependency_Across_Childs_Wrapper() { var container = new StashboxContainer(); container.Register(); var child = container.CreateChildContainer(); child.Register(c => c.WithMetadata(new object())); var child2 = child.CreateChildContainer(); child2.Register(); var child3 = child2.CreateChildContainer(); child3.Register(); var test3 = child3.Resolve(); Assert.NotNull(test3); Assert.NotNull(test3.Func()); Assert.NotNull(test3.Lazy.Value); Assert.NotNull(test3.Enumerable); Assert.Single(test3.Enumerable); Assert.NotNull(test3.Tuple.Item1); } interface ITest1; interface ITest2; interface ITest3; interface ITest5 { Func Func { get; } Lazy Lazy { get; } IEnumerable Enumerable { get; } Tuple Tuple { get; } } class Test1 : ITest1; class Test2 : ITest2 { public Test2(ITest1 test1) { } } class Test3 : ITest3 { public Test3(ITest1 test1, ITest2 test2) { } } class Test5 : ITest5 { public Test5(Func func, Lazy lazy, IEnumerable enumerable, Tuple tuple) { Func = func; Lazy = lazy; Enumerable = enumerable; Tuple = tuple; } public Func Func { get; } public Lazy Lazy { get; } public IEnumerable Enumerable { get; } public Tuple Tuple { get; } } } ================================================ FILE: test/IssueTests/35_Mixture_of_named_and_non_named_registrations_result_in_the_wrong_type_resolved.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class MixtureOfNamedAndNonNamedRegistrationTests { [Fact] public void Mixture_of_named_and_non_named_registrations_result_in_the_wrong_type_resolved() { var sb = new StashboxContainer(); sb.RegisterSingleton(typeof(ITest), typeof(Test)); sb.RegisterSingleton(typeof(ITest), typeof(Test1), "Test2"); sb.RegisterSingleton(typeof(ITest), typeof(Test2), "Test3"); var test = sb.Resolve(); Assert.NotNull(test); Assert.IsType(test); } interface ITest; class Test : ITest; class Test1 : ITest; class Test2 : ITest; } ================================================ FILE: test/IssueTests/37_Resolver_factory_invoke_doesnt_pass_different_parameters_given_when_theyre_the_same_type.cs ================================================ using System; using Xunit; namespace Stashbox.Tests.IssueTests; public class ResolverFactoryIssue { [Fact] public void Resolver_factory_invoke_doesnt_pass_different_parameters_given_when_theyre_the_same_type() { var factory = new StashboxContainer() .Register() .ResolveFactory(typeof(IFoo), parameterTypes: [typeof(string), typeof(string)]); Assert.Equal("foobar", ((IFoo)factory.DynamicInvoke("foo", "bar")).Result); } [Fact] public void Resolver_factory_invoke_doesnt_pass_different_parameters_given_when_theyre_the_same_type_func() { var factory = new StashboxContainer() .Register() .Resolve>(); Assert.Equal("foobar", factory("foo", "bar").Result); } interface IFoo { string Result { get; set; } } class Foobar : IFoo { public Foobar(string x, string y) { this.Result = x + y; } public string Result { get; set; } } } ================================================ FILE: test/IssueTests/38_Injecting_container_itself.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class InjectingContainerItself { [Fact] public void Injecting_container_itself() { var container = new StashboxContainer(); var factory = container.RegisterInstance(container) .RegisterSingleton() .Resolve(); Assert.NotNull(factory); } [Fact] public void Injecting_container_itself2() { var factory = new StashboxContainer() .RegisterSingleton() .Resolve(); Assert.NotNull(factory); } } interface ICoreFactory { IEngine GetEngine(); ILogManager GetLogManager(); } interface IEngine; interface ILogManager; class CoreFactory : ICoreFactory { private readonly StashboxContainer _container; public CoreFactory(StashboxContainer container) { _container = container; } public IEngine GetEngine() { return _container.Resolve(); } public ILogManager GetLogManager() { return _container.Resolve(); } } class CoreFactory2 : ICoreFactory { private readonly IDependencyResolver _container; public CoreFactory2(IDependencyResolver container) { _container = container; } public IEngine GetEngine() { return _container.Resolve(); } public ILogManager GetLogManager() { return _container.Resolve(); } } ================================================ FILE: test/IssueTests/42_Circular_dependency_tracking_doesnt_work_with_factory_resolution.cs ================================================ using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests.IssueTests; public class CircularDependencyTrackingDoesntWorkWithFactoryResolution { [Fact] public void Circular_dependency_tracking_doesnt_work_with_factory_resolution() { var container = new StashboxContainer(); container.Register(registrator => registrator.WithFactory(f => new Foo(f))); Assert.Throws(container.Resolve); } interface IFoo; class Foo : IFoo { public Foo(IFoo foo) { } } } ================================================ FILE: test/IssueTests/43_Issue_with_Member_Injection_with_Attribute_but_private_setter.cs ================================================ using Stashbox.Configuration; using Xunit; namespace Stashbox.Tests.IssueTests; public class IssueWithMemberInjectionwithAttributeButPrivateSetter { [Fact] public void Issue_with_Member_Injection_with_Attribute_but_private_setter_private_property() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var test = container.Register(config => config.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)) .Resolve(); Assert.True(test.Test1Prop2 != null, "test.Test1Prop2 != null"); Assert.True(test.Test1Prop != null, "test.Test1Prop != null"); } [Fact] public void Issue_with_Member_Injection_with_Attribute_but_private_setter_private_field() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var test = container.Register(config => config.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields)) .Resolve(); Assert.True(test.Test1Prop2 != null, "test.Test1Prop2 != null"); Assert.True(test.Test1Prop != null, "test.Test1Prop != null"); } [Fact] public void Issue_with_Member_Injection_with_Attribute_but_private_setter_public_property() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var test = container.Register(config => config.WithAutoMemberInjection()) .Resolve(); Assert.True(test.Test1Prop2 != null, "test.Test1Prop2 != null"); Assert.True(test.Test1Prop != null, "test.Test1Prop != null"); } class Test; class Test1 { public Test Test1Prop { get; private set; } } class Test2 : Test1 { public Test Test1Prop2 { get; private set; } } class Test3 { private Test test1 = null; public Test Test1Prop => this.test1; } class Test4 : Test3 { private Test test1 = null; public Test Test1Prop2 => this.test1; } class Test5 { public Test Test1Prop { get; set; } } class Test6 : Test5 { public Test Test1Prop2 { get; set; } } } ================================================ FILE: test/IssueTests/44_Lifetime_Issues.cs ================================================ using Stashbox.Registration.Fluent; using System; using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class LifetimeIssues { class DoResolveAttribute : Attribute; interface ITier1; class Tier1 : ITier1 {[DoResolve] public ITier2 Inner { get; set; }[DoResolve] public TierBase OtherInner { get; set; } } interface ITier2; class Tier2 : ITier2 { public Tier2(string name) { Name = name; } public string Name { get; set; } } abstract class TierBase { public int Id { get; protected set; } } class Tier3 : TierBase { public Tier3(int id) { Id = id; }[DoResolve] public ITier2 Inner { get; set; } } public abstract class PrivateArgs { public object[] ArgList { get; protected set; } public abstract Type Target { get; } } public class PrivateArgs : PrivateArgs { static readonly Type _Target = typeof(T); public PrivateArgs(params object[] args) : base() { ArgList = args; } public static PrivateArgs Get(params object[] args) { var res = new PrivateArgs { ArgList = args }; return res; } public override Type Target { get; } = _Target; } [Fact] public void ContextEstablishedInChildContainersCanBeAccessedWhenUsingAParentScopeConstruction() { StashboxContainer sb1 = CreateContainer(c => c.WithSingletonLifetime()); StashboxContainer sb2 = CreateContainer(c => c.WithSingletonLifetime()); // This works sb1.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get("Bob")); sb1.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get(5)); ITier1 renderer = (ITier1)sb1.Resolve(typeof(ITier1)); Assert.NotNull(renderer); using var scope = sb2.BeginScope(); scope.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get("Bob")); scope.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get(5)); ITier1 renderer2 = (ITier1)scope.Resolve(typeof(ITier1)); Assert.NotNull(renderer2); } [Fact] public void ContextEstablishedInChildContainersCanBeAccessedWhenUsingAParentScopeConstructionWithChildContainer() { StashboxContainer sb1 = CreateContainer(c => c.WithScopedLifetime()); StashboxContainer sb2 = CreateContainer(c => c.WithScopedLifetime()); // This works using var scope1 = sb1.BeginScope(); scope1.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get("Bob")); scope1.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get(5)); ITier1 renderer = (ITier1)scope1.Resolve(typeof(ITier1)); Assert.NotNull(renderer); // This fails using var sbc = sb2.CreateChildContainer(); using var scope2 = sbc.BeginScope(); scope2.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get("Bob")); scope2.PutInstanceInScope(typeof(PrivateArgs), PrivateArgs.Get(5)); ITier1 renderer2 = (ITier1)scope2.Resolve(typeof(ITier1)); Assert.NotNull(renderer2); } private static StashboxContainer CreateContainer(Action scopeConfig = null) { var sb = new StashboxContainer( config => config .WithUnknownTypeResolution(ctx => ctx.WhenHas()) .WithAutoMemberInjection( Stashbox.Configuration.Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, ti => ti.CustomAttributes.Any(a => a.GetType() == typeof(DoResolveAttribute)) ) ); var allTypes = new[] { new Tuple(typeof(ITier1), typeof(Tier1)), new Tuple(typeof(ITier2), typeof(Tier2)), new Tuple(typeof(TierBase), typeof(Tier3)), }.ToList(); foreach (var type in allTypes) { var tbuilt = type.Item2; var tinterface = type.Item1; var targs = typeof(PrivateArgs<>).MakeGenericType(tinterface); sb.Register(tinterface, tbuilt, c => c .WithFactory(d => { PrivateArgs argContainer = d.Resolve(targs) as PrivateArgs; object[] args = argContainer?.ArgList; object res = null; try { res = Activator.CreateInstance(tbuilt, args ?? new object[0]); } catch { } return res; }) .WithAutoMemberInjection( Stashbox.Configuration.Rules.AutoMemberInjectionRules.PropertiesWithPublicSetter, ti => ti.CustomAttributes.Any(a => a.GetType() == typeof(DoResolveAttribute))) // Simple extension method allowing conditional configuration inline .ApplyIf(scopeConfig != null, scopeConfig) ); } return sb; } } public static class RegExt { public static void ApplyIf(this RegistrationConfigurator configurator, bool b, Action scopeConfig) { if (b) scopeConfig.Invoke(configurator); } } ================================================ FILE: test/IssueTests/46_AspNetCore_Failing_spec_tests_forconstrained_generics.cs ================================================ using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class AspNetCoreFailingSpecTestsForConstrainedGenerics { [Fact] public void PublicNoArgCtorConstrainedOpenGenericServicesCanBeResolved() { var container = new StashboxContainer(); container.Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>)) .Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithNewConstraint<>)); var allServices = container.ResolveAll>().ToList(); var constrainedServices = container.ResolveAll>().ToList(); Assert.Equal(2, allServices.Count); Assert.Single(constrainedServices); } [Fact] public void SelfReferencingConstrainedOpenGenericServicesCanBeResolved() { var container = new StashboxContainer(); var poco = new PocoClass(); var comparable = new ClassImplementingIComparable(); container.Register(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)) .Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithSelfReferencingConstraint<>)) .RegisterInstance(poco) .RegisterInstance(comparable); var allServices = container.ResolveAll>().ToList(); var constrainedServices = container.ResolveAll>().ToList(); Assert.Equal(2, allServices.Count); Assert.Same(comparable, allServices[0].Value); Assert.Same(comparable, allServices[1].Value); Assert.Single(constrainedServices); Assert.Same(poco, constrainedServices[0].Value); } [Fact] public void ClassConstrainedOpenGenericServicesCanBeResolved() { var container = new StashboxContainer(); container.Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>)) .Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithClassConstraint<>)); var allServices = container.ResolveAll>().ToList(); var constrainedServices = container.ResolveAll>().ToList(); Assert.Equal(2, allServices.Count); Assert.Single(constrainedServices); } [Fact] public void StructConstrainedOpenGenericServicesCanBeResolved() { var container = new StashboxContainer(); container.Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithNoConstraints<>)) .Register(typeof(IFakeOpenGenericService<>), typeof(ClassWithStructConstraint<>)); var allServices = container.ResolveAll>().ToList(); var constrainedServices = container.ResolveAll>().ToList(); Assert.Equal(2, allServices.Count); Assert.Single(constrainedServices); } [Fact] public void ClosedServicesPreferredOverOpenGenericServices() { // Arrange var container = new StashboxContainer() .Register(typeof(IFakeOpenGenericService), typeof(FakeService)) .Register(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)) .RegisterSingleton(); // Act var service = container.Resolve>(); // Assert Assert.IsType(service); } [Fact] public void ResolvesMixedOpenClosedGenericsAsEnumerable() { // Arrange var container = new StashboxContainer(); var instance = new FakeOpenGenericService(null); container.Register(); container.RegisterSingleton(typeof(IFakeOpenGenericService), typeof(FakeService)); container.RegisterSingleton(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>)); container.RegisterInstance>(instance); var enumerable = container.Resolve>>().ToArray(); // Assert Assert.Equal(3, enumerable.Length); Assert.NotNull(enumerable[0]); Assert.NotNull(enumerable[1]); Assert.NotNull(enumerable[2]); Assert.Equal(instance, enumerable[2]); Assert.IsType(enumerable[0]); } } interface IFakeService; interface IFakeSingletonService : IFakeService; interface IFakeEveryService : IFakeService, IFakeSingletonService, IFakeOpenGenericService; interface IFakeOpenGenericService { T Value { get; } } class ClassWithNoConstraints : IFakeOpenGenericService { public T Value { get; } = default; } class ClassWithNewConstraint : IFakeOpenGenericService where T : new() { public T Value { get; } = new T(); } class ClassWithSelfReferencingConstraint : IFakeOpenGenericService where T : IComparable { public ClassWithSelfReferencingConstraint(T value) => Value = value; public T Value { get; } } class FakeOpenGenericService : IFakeOpenGenericService { public FakeOpenGenericService(TVal value) { Value = value; } public TVal Value { get; } } class ClassWithClassConstraint : IFakeOpenGenericService where T : class { public T Value { get; } = default; } class ClassWithStructConstraint : IFakeOpenGenericService where T : struct { public T Value { get; } = default; } class PocoClass; class ClassWithPrivateCtor { private ClassWithPrivateCtor() { } } class ClassImplementingIComparable : IComparable { public int CompareTo(ClassImplementingIComparable other) => 0; } class FakeService : IFakeEveryService, IDisposable { public PocoClass Value { get; set; } public bool Disposed { get; private set; } public void Dispose() { if (Disposed) { throw new ObjectDisposedException(nameof(FakeService)); } Disposed = true; } } ================================================ FILE: test/IssueTests/48_Chained_named_scopes_are_not_working_properly.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class ChainedNamedScopesAreNotWorkingProperly { [Fact] public void Chained_named_scopes_are_not_working_properly() { var container = new StashboxContainer() .Register(config => config.DefinesScope("B").InNamedScope("A")) .Register(config => config.InNamedScope("B")) .Register(config => config.DefinesScope("A")); Assert.NotNull(container.Resolve()); } class NamedScopeTest1; class NamedScopeTest2 { public NamedScopeTest2(NamedScopeTest1 t) { } } class NamedScopeTest3 { public NamedScopeTest3(NamedScopeTest2 t) { } } } ================================================ FILE: test/IssueTests/49_Unable_to_use_nullable_types_with_injection_parameters.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class UnableToUseNullableTypesWithInjectionParameters { [Fact] public void Unable_to_use_nullable_types_with_injection_parameters() { var container = new StashboxContainer(config => config .WithDefaultValueInjection()); var someInt = 123; var inst = container.Register( config => config.WithInjectionParameter("exampleValue", someInt)).Resolve(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal(someInt, inst.ExampleProperty.Value); } [Fact] public void Unable_to_use_nullable_types_with_injection_parameters_member() { var container = new StashboxContainer(config => config .WithDefaultValueInjection() .WithAutoMemberInjection()); var someInt = 123; var inst = container.Register( config => config.WithInjectionParameter("ExampleProperty", someInt)).Resolve(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal(someInt, inst.ExampleProperty.Value); } interface IExample { int? ExampleProperty { get; } } class ExampleClass : IExample { public ExampleClass(int? exampleValue = null) { ExampleProperty = exampleValue; } public int? ExampleProperty { get; private set; } } class ExampleClass2 : IExample { public int? ExampleProperty { get; set; } } } ================================================ FILE: test/IssueTests/50_Generate_one_instance_for_multiple_interfaces.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class GenerateOneInstanceForMultipleInterfaces { [Fact] public void Generate_one_instance_for_multiple_interfaces() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes()); var inst1 = container.Resolve(); var inst2 = container.Resolve(); var inst3 = container.Resolve(); var inst4 = container.Resolve(); var inst5 = container.Resolve(); Assert.NotSame(inst1, inst2); Assert.NotSame(inst2, inst3); Assert.NotSame(inst3, inst4); Assert.NotSame(inst4, inst5); } [Fact] public void Generate_one_instance_for_multiple_interfaces_singleton() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes().WithSingletonLifetime()); var inst1 = container.Resolve(); var inst2 = container.Resolve(); var inst3 = container.Resolve(); var inst4 = container.Resolve(); var inst5 = container.Resolve(); Assert.Same(inst1, inst2); Assert.Same(inst2, inst3); Assert.Same(inst3, inst4); Assert.Same(inst4, inst5); } [Fact] public void Generate_one_instance_for_multiple_interfaces_scoped() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes().WithScopedLifetime()); var scope = container.BeginScope(); var inst1 = scope.Resolve(); var inst2 = scope.Resolve(); var inst3 = scope.Resolve(); var inst4 = scope.Resolve(); var inst5 = scope.Resolve(); Assert.Same(inst1, inst2); Assert.Same(inst2, inst3); Assert.Same(inst3, inst4); Assert.Same(inst4, inst5); } [Fact] public void Generate_one_instance_for_multiple_interfaces_named_scope() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes().InNamedScope("A")); var scope = container.BeginScope("A"); var inst1 = scope.Resolve(); var inst2 = scope.Resolve(); var inst3 = scope.Resolve(); var inst4 = scope.Resolve(); var inst5 = scope.Resolve(); Assert.Same(inst1, inst2); Assert.Same(inst2, inst3); Assert.Same(inst3, inst4); Assert.Same(inst4, inst5); } interface ITest; interface ITest1; interface ITest2; class Test; class Test2 : Test, ITest, ITest1, ITest2; } ================================================ FILE: test/IssueTests/51_WithUnknownTypeResolution_breaks_constructor_selection_rules.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class WithUnknownTypeResolutionBreaksConstructorSelectionRules { [Fact] public void WithUnknownTypeResolution_breaks_constructor_selection_rules() { var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var inst = container .Register() .Register() .Resolve(); Assert.NotNull(inst.Dep1); Assert.Null(inst.Dep); } [Fact] public void WithUnknownTypeResolution_breaks_constructor_selection_rules_ensure_ok_without_unknown_type_resolver() { var container = new StashboxContainer(); var inst = container .Register() .Register() .Resolve(); Assert.NotNull(inst.Dep1); Assert.Null(inst.Dep); } class Dep; class Dep1; class Test { public Dep Dep { get; } public Dep1 Dep1 { get; } public Test(Dep dep) { Dep = dep; } public Test(Dep1 dep) { Dep1 = dep; } } } ================================================ FILE: test/IssueTests/52_Verify_child_container_working.cs ================================================ using System; using System.Threading; using Xunit; namespace Stashbox.Tests.IssueTests; public class VerifyChildContainerWorking { [Fact] public void Verify_child_container_working() { var container = new StashboxContainer() .RegisterSingleton() .RegisterScoped(); Assert.Equal(1, container.Resolve().Id); Assert.Equal(1, container.BeginScope().Resolve().Id); var child = container.CreateChildContainer(); Assert.Equal(1, child.Resolve().Id); Assert.Equal(2, child.BeginScope().Resolve().Id); var child2 = container.CreateChildContainer().RegisterSingleton(); Assert.Equal(2, child2.Resolve().Id); Assert.Equal(3, child2.BeginScope().Resolve().Id); } [Fact] public void Verify_child_container_working_dispose() { Singleton s = null; { using var container = new StashboxContainer() .RegisterSingleton(); { { using var child = container.CreateChildContainer(); s = child.Resolve(); } Assert.False(s.Disposed); } } Assert.True(s.Disposed); } private class Singleton : IDisposable { private static int seed; public int Id = Interlocked.Increment(ref seed); public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(Singleton)); this.Disposed = true; } } private class Scoped { private static int seed; public int Id = Interlocked.Increment(ref seed); } } ================================================ FILE: test/IssueTests/53_ComposeBy_with_instance_or_injection.cs ================================================ using Moq; using Xunit; namespace Stashbox.Tests.IssueTests; public class ComposeByWithInstanceOrInjection { [Fact] public void ComposeByWithInstance() { var mock = new Mock(); var container = new StashboxContainer().ComposeBy(mock.Object); mock.Verify(m => m.Compose(container), Times.Once); } [Fact] public void ComposeByWithInjection() { new StashboxContainer() .Register() .ComposeBy(); } [Fact] public void ComposeByWithMemberInjection() { new StashboxContainer(c => c.WithAutoMemberInjection()) .Register() .ComposeBy(); } [Fact] public void ComposeByWithInjectionWithDependencyOverride() { new StashboxContainer() .ComposeBy(5); } class TestDep; class TestRoot : ICompositionRoot { public TestDep Test { get; set; } public void Compose(IStashboxContainer container) { if (this.Test == null) Assert.True(false, "Dependency not resolved"); } } class TestRoot1 : ICompositionRoot { public TestDep Test { get; set; } public TestRoot1(TestDep test) { this.Test = test; } public void Compose(IStashboxContainer container) { if (this.Test == null) Assert.True(false, "Dependency not resolved"); } } class TestRoot2 : ICompositionRoot { public int Test { get; set; } public TestRoot2(int test) { this.Test = test; } public void Compose(IStashboxContainer container) { if (this.Test != 5) Assert.True(false, "Dependency not resolved"); } } } ================================================ FILE: test/IssueTests/54_Make_InjectionParameter_configuration_more_fluent.cs ================================================ using System.Collections.Generic; using Xunit; namespace Stashbox.Tests.IssueTests; public class MakeInjectionParameterConfigurationMoreFluent { [Fact] public void MakeInjectionParameterConfigurationMoreFluent_Mixed() { var instance = new StashboxContainer() .Register(c => c.WithAutoMemberInjection() .WithInjectionParameter(nameof(Test1.TestString), "sample") .WithInjectionParameters(new KeyValuePair(nameof(Test1.TestInt), 5))) .Resolve(); Assert.Equal("sample", instance.TestString); Assert.Equal(5, instance.TestInt); } class Test1 { public string TestString { get; set; } public int TestInt { get; set; } } } ================================================ FILE: test/IssueTests/55_Conditions_on_member_name_are_not_easy_to_recognize.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class ConditionsOnMemberNameAreNotEasyToRecognize { [Fact] public void ConditionsOnMemberNameAreNotEasyToRecognize_UseParameterOrMemberName() { var container = new StashboxContainer(); container.Register(context => context.When(t => t.ParameterOrMemberName == "Test1")); container.Register(context => context.When(t => t.ParameterOrMemberName == "Test11")); container.Register(c => c.WithAutoMemberInjection()); var test5 = container.Resolve(); Assert.IsType(test5.Test1); Assert.IsType(test5.Test11); } interface ITest1; class Test1 : ITest1; class Test11 : ITest1; class Test2 { public ITest1 Test1 { get; set; } public ITest1 Test11 { get; set; } } } ================================================ FILE: test/IssueTests/58_InjectionParameter_NullReference.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class InjectionParameterNullReference { [Fact] public void InjectionParameter_NullReference() { var inst = new StashboxContainer() .Register(c => c.WithInjectionParameter("arg", null)) .Resolve(); Assert.NotNull(inst); } [Fact] public void InjectionParameter_NullReference_Object() { var inst = new StashboxContainer() .Register(c => c.WithInjectionParameter("arg", null)) .Resolve(); Assert.NotNull(inst); } class Test { public Test(Test2 arg) { } } class Test2 { public Test2(object arg) { } } } ================================================ FILE: test/IssueTests/59_Static_factory_fails.cs ================================================ using System; using System.Linq.Expressions; using System.Reflection; using Xunit; namespace Stashbox.Tests.IssueTests; public class StaticFactoryFails { [Fact] public void Ensure_Static_Factory_Registration_Works() { var inst = new StashboxContainer().Register(c => c.WithFactory(Factory)).Resolve(); Assert.NotNull(inst); } [Fact] public void Ensure_Static_Factory_Registration_With_Resolver_Works() { var inst = new StashboxContainer().Register(c => c.WithFactory(ResolverFactory)).Resolve(); Assert.NotNull(inst); } [Fact] public void Ensure_Static_Factory_Registration_WithProperty_Works() { var prop = typeof(St) .GetProperty("Tp"); var inst = new StashboxContainer().Register(c => c .WithFactory(prop .Access(null) .AsLambda>() .Compile())) .Resolve(); Assert.NotNull(inst); } [Fact] public void Ensure_Static_Factory_Registration_CompiledLambda_Works() { var param = typeof(IDependencyResolver).AsParameter(); var inst = new StashboxContainer().Register(c => c .WithFactory(this .GetType() .GetMethod("ResolverFactory", BindingFlags.Static | BindingFlags.NonPublic) .CallStaticMethod(param) .AsLambda>(param) .Compile())) .Resolve(); Assert.NotNull(inst); } private static T Factory() => new(); private static T ResolverFactory(IDependencyResolver resolver) => new(); private class T; private static class St { public static T Tp => new(); } } ================================================ FILE: test/IssueTests/63_named_unnamed_resolution_not_working_as_expected.cs ================================================ using Stashbox.Attributes; using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests.IssueTests; public class NamedUnnamedResolutionNotWorkingAsExpected { [Fact] public void Ensures_Named_Dependency_Selected_When_Convention_Enabled() { var inst = new StashboxContainer(c => c.TreatParameterAndMemberNameAsDependencyName()) .Register(c => c.WithName("t1")) .Register(c => c.WithName("t2")) .Register() .Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); } [Fact] public void Ensures_Named_Dependency_Selected_When_Convention_Enabled_InjectionMembers() { var inst = new StashboxContainer(c => c.TreatParameterAndMemberNameAsDependencyName()) .Register(c => c.WithName("T1")) .Register(c => c.WithName("T2")) .Register(c => c.WithAutoMemberInjection()) .Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); } [Fact] public void Ensures_Named_Dependency_Selected_When_Convention_Enabled_InjectionMethod() { var inst = new StashboxContainer(c => c.TreatParameterAndMemberNameAsDependencyName()) .Register(c => c.WithName("t1")) .Register(c => c.WithName("t2")) .Register() .Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); } [Fact] public void Ensures_UnNamed_Dependency_Selected_When_Named_Not_Available() { var container = new StashboxContainer(c => c.WithNamedDependencyResolutionForUnNamedRequests()) .Register(c => c.WithName("t1").WithSingletonLifetime()); var inst = container.Resolve("t1"); var inst2 = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst2); Assert.Equal(inst, inst2); } [Fact] public void Ensures_UnNamed_Dependency_Selected_When_Convention_Enabled_But_Named_Preferred() { var container = new StashboxContainer(c => c .TreatParameterAndMemberNameAsDependencyName() .WithNamedDependencyResolutionForUnNamedRequests()) .Register() .Register("t1") .Register(); var inst = container.Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); } [Fact] public void Ensures_Dont_Resolve_Named_When_Not_Enabled() { var container = new StashboxContainer(c => c .TreatParameterAndMemberNameAsDependencyName()) .Register() .Register("t1"); Assert.Throws(() => container.Resolve()); } [Fact] public void Ensures_UnNamed_Dependency_Selected_When_Named_Not_Available_With_Treating_Param_Names_As_Dependency_Names() { var container = new StashboxContainer(c => c .TreatParameterAndMemberNameAsDependencyName()) .Register() .Register("t1") .Register(); var inst = container.Resolve(); Assert.IsType(inst.T1); Assert.IsType(inst.T2); } interface ITest; class Test1 : ITest; class Test2 : ITest; class Test3 { public ITest T1 { get; } public ITest T2 { get; } public Test3(ITest t1, ITest t2) { T1 = t1; T2 = t2; } } class Test4 { public ITest T1 { get; set; } public ITest T2 { get; set; } } class Test5 { public ITest T1 { get; set; } public ITest T2 { get; set; } [InjectionMethod] public void Init(ITest t1, ITest t2) { T1 = t1; T2 = t2; } } } ================================================ FILE: test/IssueTests/64_WithFactory_MemberInjection_Not_Working_With_ImplementationType.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class WithFactoryMemberInjectionNotWorkingWithImplementationType { [Fact] public void Ensure_MemberInjection_Works_WithFactory() { var inst = new StashboxContainer(c => c.WithUnknownTypeResolution().WithAutoMemberInjection()) .Register(ctx => ctx.WithFactory(r => new Test())) .Resolve(); Assert.NotNull(((Test)inst).Dummy); } class Dummy; interface ITest; class Test : ITest { public Dummy Dummy { get; set; } } } ================================================ FILE: test/IssueTests/66_Named_PutInstanceInScope.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class NamedPutInstanceInScope { [Fact] public void Ensure_Named_Scoped_Instance_Working() { using var container = new StashboxContainer() .Register(); var a1 = new A(); var a2 = new A(); var a3 = new A(); { using var scope = container.BeginScope(); scope.PutInstanceInScope(a1); scope.PutInstanceInScope(a2, name: "a"); scope.PutInstanceInScope(a3); Assert.Same(a2, scope.Resolve("a")); } { using var scope = container.BeginScope(); scope.PutInstanceInScope(a1, name: "a1"); scope.PutInstanceInScope(a2, name: "a2"); scope.PutInstanceInScope(a3, name: "a3"); Assert.Same(a2, scope.Resolve("a2")); } } class A; } ================================================ FILE: test/IssueTests/67_Dictionaries_get_resolved_to_arrays_of_key_type_by_default.cs ================================================ using Stashbox.Configuration; using System.Collections.Generic; using Xunit; namespace Stashbox.Tests.IssueTests; public class DictionariesGetResolvedToArraysOfKeyTypeByDefault { [Fact] public void Ensure_Dictionary_Resolves() { var container = new StashboxContainer(c => c.WithUnknownTypeResolution(c2 => c2.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters))); Assert.NotNull(container.Resolve>()); } } ================================================ FILE: test/IssueTests/68_Programmatic_multiple_instances_registration.cs ================================================ using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class ProgrammaticMultipleInstancesRegistration { [Fact] public void Ensure_Multiple_Instance_Registration_Working() { var regs = new StashboxContainer() .RegisterInstances(new Test(), new Test1()).GetRegistrationMappings(); Assert.Equal(2, regs.Count()); } [Fact] public void Ensure_Multiple_Instance_Registration_Working_Enumerable() { var regs = new StashboxContainer() .RegisterInstances(new ITest[] { new Test(), new Test1() }).GetRegistrationMappings(); Assert.Equal(2, regs.Count()); } interface ITest; class Test : ITest; class Test1 : ITest; } ================================================ FILE: test/IssueTests/70_UnkownType_overrides_instance_in_scope.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class UnkownTypOverridesInstanceInScope { [Fact] public void Ensure_UnknownType_Doesnt_Overrides_Instance_In_Scope() { var container = new StashboxContainer(c => c.WithUnknownTypeResolution()); var inst = container.Resolve(); using var scope = container.BeginScope(); var @new = new object(); scope.PutInstanceInScope(@new); var scoped = scope.Resolve(); Assert.NotSame(inst, scoped); Assert.Same(@new, scoped); } } ================================================ FILE: test/IssueTests/71_FastExpressionCompiler_Issue.cs ================================================ using FastExpressionCompiler; using System; using System.Linq.Expressions; using System.Reflection; using Xunit; namespace Stashbox.Tests.IssueTests; public class FastExpressionCompilerIssue { static Test T { get; } = new Test(); [Fact] public void Ensure_FastExpressionCompiler_Works() { var prop = typeof(FastExpressionCompilerIssue).GetProperty("T", BindingFlags.NonPublic | BindingFlags.Static); var memberLambda = Expression.MakeMemberAccess(null, prop).AsLambda>().CompileFast(); Assert.NotNull(new StashboxContainer() .Register(c => c.WithFactory(memberLambda, true)) .Resolve()); } class Test; } ================================================ FILE: test/IssueTests/72_Default_lifetime_set.cs ================================================ using Stashbox.Lifetime; using System.Linq; using Xunit; namespace Stashbox.Tests.IssueTests; public class DefaultLifetimeSet { [Fact] public void Ensure_Default_Lifetime_Used_When_Custom_Not_Set() { var mappings = new StashboxContainer(c => c.WithDefaultLifetime(Lifetimes.Scoped)) .Register().GetRegistrationMappings(); var reg = mappings.First(); Assert.Equal(typeof(Test), reg.Key); Assert.Same(Lifetimes.Scoped, reg.Value.Lifetime); } [Fact] public void Ensure_Custom_Lifetime_Used_When_Both_Set() { var mappings = new StashboxContainer(c => c.WithDefaultLifetime(Lifetimes.Scoped)) .Register(c => c.WithSingletonLifetime()).GetRegistrationMappings(); var reg = mappings.First(); Assert.Equal(typeof(Test), reg.Key); Assert.Same(Lifetimes.Singleton, reg.Value.Lifetime); } [Fact] public void Ensure_Transient_Lifetime_Used_By_Default() { var mappings = new StashboxContainer() .Register().GetRegistrationMappings(); var reg = mappings.First(); Assert.Equal(typeof(Test), reg.Key); Assert.Same(Lifetimes.Transient, reg.Value.Lifetime); } class Test; } ================================================ FILE: test/IssueTests/76_Exception_when_building_expressions.cs ================================================ using Stashbox.Configuration; using Stashbox.Tests.Utils; using System; using System.Threading; using Xunit; namespace Stashbox.Tests.IssueTests; public class ExceptionWhenBuildingExpressions { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_scoped(CompilerType compilerType) { A.Counter = 0; B.Counter = 0; C.Counter = 0; D.Counter = 0; E.Counter = 0; F.Counter = 0; A inst = null; { using var container = new StashboxContainer(c => c.WithDisposableTransientTracking() .WithCompiler(compilerType)) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithFactory(r => new C()).WithScopedLifetime().WithoutDisposalTracking()); using var scope = container.BeginScope(); inst = scope.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.B); Assert.NotNull(inst.C); Assert.NotNull(inst.B.D); Assert.NotNull(inst.B.C); Assert.NotNull(inst.B.D.C); Assert.NotNull(inst.B.D.E); Assert.NotNull(inst.B.D.F); Assert.NotNull(inst.B.D.E.C); Assert.NotNull(inst.B.D.F.C); Assert.Same(inst.C, inst.B.C); Assert.Same(inst.B.C, inst.B.D.C); Assert.Same(inst.B.D.C, inst.B.D.E.C); Assert.Same(inst.B.D.E.C, inst.B.D.F.C); } Assert.True(inst.Disposed); Assert.True(inst.B.Disposed); Assert.False(inst.C.Disposed); Assert.True(inst.B.D.Disposed); Assert.False(inst.B.C.Disposed); Assert.False(inst.B.D.C.Disposed); Assert.True(inst.B.D.E.Disposed); Assert.True(inst.B.D.F.Disposed); Assert.False(inst.B.D.E.C.Disposed); Assert.False(inst.B.D.F.C.Disposed); Assert.Equal(1, A.Counter); Assert.Equal(1, B.Counter); Assert.Equal(1, C.Counter); Assert.Equal(1, D.Counter); Assert.Equal(1, E.Counter); Assert.Equal(1, F.Counter); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_scoped_one_singleton(CompilerType compilerType) { A.Counter = 0; B.Counter = 0; C.Counter = 0; D.Counter = 0; E.Counter = 0; F.Counter = 0; A inst = null; { using var container = new StashboxContainer(c => { c.WithDisposableTransientTracking().WithCompiler(compilerType); }) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithSingletonLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithFactory(r => new C()).WithScopedLifetime().WithoutDisposalTracking()); using var scope = container.BeginScope(); inst = scope.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.B); Assert.NotNull(inst.C); Assert.NotNull(inst.B.D); Assert.NotNull(inst.B.C); Assert.NotNull(inst.B.D.C); Assert.NotNull(inst.B.D.E); Assert.NotNull(inst.B.D.F); Assert.NotNull(inst.B.D.E.C); Assert.NotNull(inst.B.D.F.C); Assert.NotSame(inst.C, inst.B.C); Assert.Same(inst.B.C, inst.B.D.C); Assert.Same(inst.B.D.C, inst.B.D.E.C); Assert.Same(inst.B.D.E.C, inst.B.D.F.C); } Assert.True(inst.Disposed); Assert.True(inst.B.Disposed); Assert.False(inst.C.Disposed); Assert.True(inst.B.D.Disposed); Assert.False(inst.B.C.Disposed); Assert.False(inst.B.D.C.Disposed); Assert.True(inst.B.D.E.Disposed); Assert.True(inst.B.D.F.Disposed); Assert.False(inst.B.D.E.C.Disposed); Assert.False(inst.B.D.F.C.Disposed); Assert.Equal(1, A.Counter); Assert.Equal(1, B.Counter); Assert.Equal(2, C.Counter); Assert.Equal(1, D.Counter); Assert.Equal(1, E.Counter); Assert.Equal(1, F.Counter); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_singleton(CompilerType compilerType) { A.Counter = 0; B.Counter = 0; C.Counter = 0; D.Counter = 0; E.Counter = 0; F.Counter = 0; A inst = null; { using var container = new StashboxContainer(c => { c.WithDisposableTransientTracking() .WithCompiler(compilerType); }) .Register(c => c.DefinesScope("A")) .Register(c => c.WithSingletonLifetime().DefinesScope("B")) .Register(c => c.DefinesScope("D").WithScopedLifetime()) .Register(c => c.DefinesScope("E")) .Register(c => c.DefinesScope("F")) .Register(c => c.WithFactory(r => new C()).WithoutDisposalTracking().WithScopedLifetime()); { using var scope = container.BeginScope(); inst = scope.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.B); Assert.NotNull(inst.C); Assert.NotNull(inst.B.D); Assert.NotNull(inst.B.C); Assert.NotNull(inst.B.D.C); Assert.NotNull(inst.B.D.E); Assert.NotNull(inst.B.D.F); Assert.NotNull(inst.B.D.E.C); Assert.NotNull(inst.B.D.F.C); Assert.NotSame(inst.C, inst.B.C); Assert.NotSame(inst.B.C, inst.B.D.C); Assert.NotSame(inst.B.D.C, inst.B.D.E.C); Assert.NotSame(inst.B.D.E.C, inst.B.D.F.C); } Assert.True(inst.Disposed); Assert.False(inst.B.Disposed); Assert.False(inst.C.Disposed); Assert.False(inst.B.D.Disposed); Assert.False(inst.B.C.Disposed); Assert.False(inst.B.D.C.Disposed); Assert.False(inst.B.D.E.Disposed); Assert.False(inst.B.D.F.Disposed); Assert.False(inst.B.D.E.C.Disposed); Assert.False(inst.B.D.F.C.Disposed); } Assert.True(inst.Disposed); Assert.True(inst.B.Disposed); Assert.False(inst.C.Disposed); Assert.True(inst.B.D.Disposed); Assert.False(inst.B.C.Disposed); Assert.False(inst.B.D.C.Disposed); Assert.True(inst.B.D.E.Disposed); Assert.True(inst.B.D.F.Disposed); Assert.False(inst.B.D.E.C.Disposed); Assert.False(inst.B.D.F.C.Disposed); Assert.Equal(1, A.Counter); Assert.Equal(1, B.Counter); Assert.Equal(5, C.Counter); Assert.Equal(1, D.Counter); Assert.Equal(1, E.Counter); Assert.Equal(1, F.Counter); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_mixed(CompilerType compilerType) { A.Counter = 0; B.Counter = 0; C.Counter = 0; D.Counter = 0; E.Counter = 0; F.Counter = 0; A inst = null; { using var container = new StashboxContainer(c => { c.WithDisposableTransientTracking().WithCompiler(compilerType); }) .Register() .Register() .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithSingletonLifetime()); { for (int i = 0; i < 5; i++) { { using var scope = container.BeginScope(); for (int j = 0; j < 5; j++) { inst = scope.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.B); Assert.NotNull(inst.C); Assert.NotNull(inst.B.D); Assert.NotNull(inst.B.C); Assert.NotNull(inst.B.D.C); Assert.NotNull(inst.B.D.E); Assert.NotNull(inst.B.D.F); Assert.NotNull(inst.B.D.E.C); Assert.NotNull(inst.B.D.F.C); Assert.Same(inst.C, inst.B.C); Assert.Same(inst.B.C, inst.B.D.C); Assert.Same(inst.B.D.C, inst.B.D.E.C); Assert.Same(inst.B.D.E.C, inst.B.D.F.C); } } Assert.True(inst.Disposed); Assert.True(inst.B.Disposed); Assert.False(inst.C.Disposed); Assert.True(inst.B.D.Disposed); Assert.False(inst.B.C.Disposed); Assert.False(inst.B.D.C.Disposed); Assert.True(inst.B.D.E.Disposed); Assert.True(inst.B.D.F.Disposed); Assert.False(inst.B.D.E.C.Disposed); Assert.False(inst.B.D.F.C.Disposed); } } } Assert.True(inst.Disposed); Assert.True(inst.B.Disposed); Assert.True(inst.C.Disposed); Assert.True(inst.B.D.Disposed); Assert.True(inst.B.C.Disposed); Assert.True(inst.B.D.C.Disposed); Assert.True(inst.B.D.E.Disposed); Assert.True(inst.B.D.F.Disposed); Assert.True(inst.B.D.E.C.Disposed); Assert.True(inst.B.D.F.C.Disposed); Assert.Equal(25, A.Counter); Assert.Equal(25, B.Counter); Assert.Equal(1, C.Counter); Assert.Equal(5, D.Counter); Assert.Equal(5, E.Counter); Assert.Equal(5, F.Counter); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_singleton_dispose(CompilerType compilerType) { C.Counter = 0; D inst = null; { using var container = new StashboxContainer(c => { c.WithDisposableTransientTracking().WithCompiler(compilerType); }) .Register(c => c.WithScopedLifetime().DefinesScope()) .Register(c => c.WithSingletonLifetime().DefinesScope()) .Register(c => c.WithScopedLifetime().DefinesScope()) .Register(c => c.WithScopedLifetime()); { using var scope = container.BeginScope(); inst = scope.Resolve(); } Assert.False(inst.E.Disposed); } Assert.True(inst.E.Disposed); Assert.Equal(3, C.Counter); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void Ensure_expression_built_correctly_singleton_dispose_simple(CompilerType compilerType) { C.Counter = 0; F inst = null; { using var container = new StashboxContainer(c => { c.WithDisposableTransientTracking().WithCompiler(compilerType); }) .Register(c => c.WithScopedLifetime()) .Register(c => c.WithSingletonLifetime()); { using var scope = container.BeginScope(); inst = scope.Resolve(); } Assert.True(inst.Disposed); Assert.False(inst.C.Disposed); } Assert.True(inst.C.Disposed); Assert.Equal(1, C.Counter); } class A : IDisposable { public static int Counter = 0; public A(B b, C c) { Interlocked.Increment(ref Counter); B = b; C = c; } public B B { get; } public C C { get; } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(A)); this.Disposed = true; } } class B : IDisposable { public static int Counter = 0; public B(C c, D d) { Interlocked.Increment(ref Counter); C = c; D = d; } public C C { get; } public D D { get; } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(B)); this.Disposed = true; } } class C : IDisposable { public static int Counter = 0; public C() { Interlocked.Increment(ref Counter); } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(C)); this.Disposed = true; } } class D : IDisposable { public static int Counter = 0; public D(C c, E e, F f) { Interlocked.Increment(ref Counter); C = c; E = e; F = f; } public C C { get; } public E E { get; } public F F { get; } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(D)); this.Disposed = true; } } class E : IDisposable { public static int Counter = 0; public E(C c) { Interlocked.Increment(ref Counter); C = c; } public C C { get; } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(E)); this.Disposed = true; } } class F : IDisposable { public static int Counter = 0; public F(C c) { Interlocked.Increment(ref Counter); C = c; } public C C { get; } public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(F)); this.Disposed = true; } } } ================================================ FILE: test/IssueTests/77_UnknownType_Resolution_Does_Not_Work.cs ================================================ using Stashbox.Exceptions; using System; using Xunit; namespace Stashbox.Tests.IssueTests; public class UnknownTypeResolutionDoesNotWork { [Fact] public void Ensure_Unknown_Type_Resolution_Works_With_Interface() { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { if (config.ServiceType == typeof(ITest)) config.SetImplementationType(typeof(Test)); })); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test); } [Fact] public void Unknown_Type_Resolution_With_Interface_Bad_Implementation() { Assert.Throws(() => { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { if (config.ServiceType == typeof(ITest)) config.SetImplementationType(typeof(object)); })); container.Resolve(); }); } [Fact] public void Ensures_Registration_Validation_Works() { using var container = new StashboxContainer(); Assert.Throws(() => container.Register()); Assert.Throws(() => container.Register(typeof(Test1))); } [Fact] public void Ensures_Unknown_Registration_Does_Not_Activate_When_Unresolvable_And_Null_Enabled() { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { })); Assert.Null(container.ResolveOrDefault()); } [Fact] public void Ensures_Unknown_Registration_Activate_When_Resolvable_And_Null_Enabled() { using var container = new StashboxContainer(c => c.WithUnknownTypeResolution(config => { if (config.ServiceType == typeof(ITest)) config.SetImplementationType(typeof(Test)); })); Assert.NotNull(container.ResolveOrDefault()); } interface ITest; class Test : ITest; class Test1 { public Test1(ITest test) { Test = test; } public ITest Test { get; } } } ================================================ FILE: test/IssueTests/80_Expected_override_behaviour_not_working_with_scopes.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class ExpectedOverrideBehaviourNotWorkingWithScopes { [Fact] public void RegisteredInstancesCanBeOverridenViaAFactory() { var container = new StashboxContainer(); var toInclude = new A { Id = 20 }; container.RegisterInstance(toInclude); var outer = container.Resolve(); A inner1 = null; A inner2 = null; using (var scope = container.BeginScope()) { inner1 = scope.Resolve(); var toOverride = new A { Id = 30 }; scope.PutInstanceInScope(toOverride); inner2 = scope.Resolve(); } Assert.Equal(toInclude.Id, outer.Id); Assert.Equal(toInclude.Id, inner1.Id); Assert.Equal(30, inner2.Id); } class A { public int Id { get; set; } } } ================================================ FILE: test/IssueTests/84_DefinesScope_does_not_work_correctly.cs ================================================ using Stashbox.Lifetime; using Xunit; namespace Stashbox.Tests.IssueTests; public class DefinesScopeDoesNotWorkCorrectly { [Fact] public void DefinesScope_does_not_work_correctly() { using var container = new StashboxContainer() .Register(c => c .DefinesScope() .WithLifetime(Lifetimes.Singleton) .WithoutDisposalTracking()) .Register() .RegisterScoped(); var inst = container.Resolve(); Assert.NotSame(inst.Repo, inst.Cache.Repo); } } class ProductCache { public ProductRepository Repo { get; } public ProductCache(ProductRepository repo) { Repo = repo; } } class ProductService { public ProductRepository Repo { get; } public ProductCache Cache { get; } public ProductService(ProductRepository repo, ProductCache cache) { Repo = repo; Cache = cache; } } class ProductRepository; ================================================ FILE: test/IssueTests/88_IdentityServer_not_compatible.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class IdentityServerNotCompatible { [Fact] public void Ensure_SubGraph_Cache_Not_Messes_Up_The_Graph() { using var container = new StashboxContainer() .Register() .Register() .RegisterScoped() .RegisterScoped(); Assert.NotNull(container.Resolve()); } [Fact] public void Ensure_SubGraph_Cache_Not_Messes_Up_The_Graph_2() { using var container = new StashboxContainer() .Register() .Register() .RegisterScoped() .RegisterScoped(); Assert.NotNull(container.Resolve()); } class TransientRoot2 { public TransientRoot2(TransientProxy d, ScopedProxy c) { } } class TransientRoot { public TransientRoot(ScopedProxy c, TransientProxy d) { } } class TransientProxy { public TransientProxy(Scoped d) { } } class Scoped; class ScopedProxy { public ScopedProxy(TransientProxy b) { } } } ================================================ FILE: test/IssueTests/89_Call_interception.cs ================================================ using Castle.DynamicProxy; using System; using Xunit; namespace Stashbox.Tests.IssueTests; public class CallInterception { [Fact] public void Ensure_Expression_Override_Does_Not_Mess_Up_Cache() { var proxyBuilder = new DefaultProxyBuilder(); using var container = new StashboxContainer().Register() .RegisterDecorator(proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(typeof(ILevel2Service), new Type[0], ProxyGenerationOptions.Default)) .RegisterDecorator(proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(typeof(ILevel2bService), new Type[0], ProxyGenerationOptions.Default)) .RegisterDecorator(proxyBuilder.CreateInterfaceProxyTypeWithTargetInterface(typeof(ILevel3Service), new Type[0], ProxyGenerationOptions.Default)) .RegisterScoped() .RegisterScoped() .RegisterScoped() .RegisterScoped() .RegisterScoped(); Assert.NotNull(container.Resolve()); } public interface ILevel1Service; public interface ILevel2bService; public interface ILevel2Service; public interface ILevel3Service; public interface ILevel4Service; class Level1Service : ILevel1Service { private readonly ILevel2Service level2Service; public Level1Service(ILevel2Service level2Service) { this.level2Service = level2Service; } } class Level2bService : ILevel2bService { public Level2bService(ILevel3Service level3Service) { } } class Level2Service : ILevel2Service { public Level2Service(ILevel2bService level2BService, ILevel3Service level3Service) { } } class Level3Service : ILevel3Service { public Level3Service(ILevel4Service level4Service) { } } class Level4Service : ILevel4Service { public Level4Service() { } } class NoInterceptor : IInterceptor { public void Intercept(IInvocation invocation) { } } } ================================================ FILE: test/IssueTests/91_Resolving_with_custom_parameter_values.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class ResolvingWithCustomParameterValues { [Fact] public void Ensure_Dependency_Override_Works() { var name = "PAS.LEV"; using var container = new StashboxContainer() .Register(name); var subProduct = new Subproduct(); var variant = container.Resolve(name, [subProduct]); Assert.NotNull(variant); Assert.Same(subProduct, variant.Subproduct); } interface IVariantSubproduct { ISubproduct Subproduct { get; } } interface ISubproduct; class Subproduct : ISubproduct; class VariantSubproduct : IVariantSubproduct { public ISubproduct Subproduct { get; } protected VariantSubproduct(ISubproduct subproduct) { Subproduct = subproduct; } } class StainlessSteelPlate : VariantSubproduct { public StainlessSteelPlate(ISubproduct subproduct) : base(subproduct) { } } } ================================================ FILE: test/IssueTests/97_Does_Scope_AttachToParent_only_affect_Dispose_behaviour.cs ================================================ using System; using Xunit; namespace Stashbox.Tests.IssueTests; public class DoesScopeAttachToParentOnlyAffectDisposeBehaviour { [Fact] public void Test_ScopeGraph_Named_Job_Scopes() { var container = new StashboxContainer() .Register(c => c.InNamedScope("A")) .Register(c => c.InNamedScope("A")) .Register(c => c.InNamedScope("B")) .Register(c => c.InNamedScope("B")); Job1 j1 = null; Job2 j2 = null; { using var a = container.BeginScope("A"); { using var b = a.BeginScope("B"); j1 = b.Resolve(); j2 = b.Resolve(); } Assert.Same(j1, j2.Job); Assert.True(j1.Disposed); Assert.True(j2.Disposed); Assert.Same(j1.Cached, j2.Cached1); Assert.False(j1.Cached.Disposed); Assert.False(j2.Cached1.Disposed); Assert.False(j2.Cached2.Disposed); Assert.False(j2.Job.Cached.Disposed); } Assert.True(j1.Cached.Disposed); Assert.True(j2.Cached1.Disposed); Assert.True(j2.Cached2.Disposed); } [Fact] public void Test_ScopeGraph_Nameless_Job_Scopes() { var container = new StashboxContainer() .Register(c => c.InNamedScope("A")) .Register(c => c.InNamedScope("A")) .RegisterScoped() .RegisterScoped(); Job1 j1 = null; Job2 j2 = null; { using var a = container.BeginScope("A"); { using var b = a.BeginScope(); j1 = b.Resolve(); j2 = b.Resolve(); } Assert.Same(j1, j2.Job); Assert.True(j1.Disposed); Assert.True(j2.Disposed); Assert.Same(j1.Cached, j2.Cached1); Assert.False(j1.Cached.Disposed); Assert.False(j2.Cached1.Disposed); Assert.False(j2.Cached2.Disposed); Assert.False(j2.Job.Cached.Disposed); } Assert.True(j1.Cached.Disposed); Assert.True(j2.Cached1.Disposed); Assert.True(j2.Cached2.Disposed); } } class Cached1 : Disposable; class Cached2 : Disposable; class Job1 : Disposable { public Job1(Cached1 cached) { Cached = cached; } public Cached1 Cached { get; } } class Job2 : Disposable { public Job2(Job1 job, Cached2 cached2, Cached1 cached1) { Job = job; Cached2 = cached2; Cached1 = cached1; } public Job1 Job { get; } public Cached2 Cached2 { get; } public Cached1 Cached1 { get; } } abstract class Disposable : IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) { throw new ObjectDisposedException(nameof(Disposable)); } this.Disposed = true; } } ================================================ FILE: test/IssueTests/98_Replace_doesnt_working_singleton.cs ================================================ using Xunit; namespace Stashbox.Tests.IssueTests; public class ReplaceDoesntWorkingSingleton { [Fact] public void Ensure_Replace_Works_With_Singleton() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test").WithSingletonLifetime()); var test1 = container.Resolve("test"); Assert.IsType(test1); container.Register(context => context.WithName("test").ReplaceExisting().WithSingletonLifetime()); var test2 = container.Resolve("test"); Assert.IsType(test2); } interface ITest; class Test1 : ITest; class Test2 : ITest; } ================================================ FILE: test/KeyValueTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Tests.Utils; using System; using System.Collections.Generic; using Xunit; namespace Stashbox.Tests; public class KeyValueTests { [Fact] public void KeyValueTests_NotFound() { var container = new StashboxContainer(); container.Register("A"); Assert.Throws(() => container.Resolve>("A")); } [Fact] public void KeyValueTests_NotFound_Null() { var container = new StashboxContainer(); container.Register("A"); Assert.Equal(default, container.ResolveOrDefault>("A")); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void KeyValueTests_Resolve(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>("A"); Assert.IsType(a.Value); Assert.Equal("A", a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void KeyValueTests_Resolve_Wrapped(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>>("A"); Assert.IsType(a.Value.Value); Assert.Equal("A", a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void KeyValueTests_Resolve_Wrapped_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>(); Assert.IsType(a.Value[0]); Assert.IsType(a.Value[1]); Assert.Null(a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void KeyValueTests_Resolve_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var values = container.Resolve[]>(); Assert.Equal(2, values.Length); Assert.IsType(values[0].Value); Assert.Equal("A", values[0].Key); Assert.IsType(values[1].Value); Assert.Equal("B", values[1].Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void KeyValueTests_Resolve_Enumerable_Includes_Non_Named(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register(); var values = container.Resolve[]>(); Assert.Equal(2, values.Length); Assert.IsType(values[0].Value); Assert.Equal("A", values[0].Key); Assert.IsType(values[1].Value); Assert.Null(values[1].Key); } [Fact] public void ReadOnlyKeyValueTests_NotFound() { var container = new StashboxContainer(); container.Register("A"); Assert.Throws(() => container.Resolve>("A")); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ReadOnlyKeyValueTests_Resolve(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>("A"); Assert.IsType(a.Value); Assert.Equal("A", a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ReadOnlyKeyValueTests_Resolve_Wrapped(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>>("A"); Assert.IsType(a.Value.Value); Assert.Equal("A", a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ReadOnlyKeyValueTests_Resolve_Wrapped_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var a = container.Resolve>(); Assert.IsType(a.Value[0]); Assert.IsType(a.Value[1]); Assert.Null(a.Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ReadOnlyKeyValueTests_Resolve_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register("B"); var values = container.Resolve[]>(); Assert.Equal(2, values.Length); Assert.IsType(values[0].Value); Assert.Equal("A", values[0].Key); Assert.IsType(values[1].Value); Assert.Equal("B", values[1].Key); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ReadOnlyKeyValueTests_Resolve_Enumerable_Includes_Non_Named(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register("A"); container.Register(); var values = container.Resolve[]>(); Assert.Equal(2, values.Length); Assert.IsType(values[0].Value); Assert.Equal("A", values[0].Key); Assert.IsType(values[1].Value); Assert.Null(values[1].Key); } interface IT; class A : IT; class B : IT; } ================================================ FILE: test/KeyedTests.cs ================================================ using System; using System.Linq; using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Resolution; using Stashbox.Tests.IssueTests; using Xunit; namespace Stashbox.Tests; public class KeyedTests { private static object UniversalName = new(); [Fact] public void ResolveKeyedService() { var service1 = new Service(); var service2 = new Service(); using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithInstance(service1).WithName("service1").WithSingletonLifetime()); container.Register(c => c.WithInstance(service2).WithName("service2").WithSingletonLifetime()); Assert.Null(container.ResolveOrDefault()); Assert.Same(service1, container.ResolveOrDefault("service1")); Assert.Same(service2, container.ResolveOrDefault("service2")); } [Fact] public void ResolveKeyedOpenGenericService() { using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(typeof(IFakeOpenGenericService<>), typeof(FakeOpenGenericService<>), c => c.WithName("my-service")); container.RegisterSingleton(); // Act var genericService = container.ResolveOrDefault>("my-service"); var singletonService = container.ResolveOrDefault(); // Assert Assert.Same(singletonService, genericService.Value); } [Fact] public void ResolveKeyedServices() { var service1 = new Service(); var service2 = new Service(); var service3 = new Service(); var service4 = new Service(); using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithName("first-service").WithInstance(service1).WithSingletonLifetime()); container.Register(c => c.WithName("service").WithInstance(service2).WithSingletonLifetime()); container.Register(c => c.WithName("service").WithInstance(service3).WithSingletonLifetime()); container.Register(c => c.WithName("service").WithInstance(service4).WithSingletonLifetime()); var firstSvc = container.ResolveAll("first-service").ToList(); Assert.Single(firstSvc); Assert.Same(service1, firstSvc[0]); var services = container.ResolveAll("service").ToList(); Assert.Equal(new[] { service2, service3, service4 }, services); } [Fact] public void ResolveKeyedGenericServices() { var service1 = new FakeService(); var service2 = new FakeService(); var service3 = new FakeService(); var service4 = new FakeService(); using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register>(c => c.WithName("first-service") .WithInstance(service1).WithSingletonLifetime()); container.Register>(c => c.WithName("service") .WithInstance(service2).WithSingletonLifetime()); container.Register>(c => c.WithName("service") .WithInstance(service3).WithSingletonLifetime()); container.Register>(c => c.WithName("service") .WithInstance(service4).WithSingletonLifetime()); var firstSvc = container.ResolveAll>("first-service").ToList(); Assert.Single(firstSvc); Assert.Same(service1, firstSvc[0]); var services = container.ResolveAll>("service").ToList(); Assert.Equal(new[] { service2, service3, service4 }, services); } [Fact] public void ResolveKeyedServiceSingletonInstance() { var service = new Service(); using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithName("service1").WithInstance(service).WithSingletonLifetime()); Assert.Null(container.ResolveOrDefault()); Assert.Same(service, container.ResolveOrDefault("service1")); } [Fact] public void ResolveKeyedServiceSingletonInstanceWithKeyInjection() { var serviceKey = "this-is-my-service"; using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(serviceKey); Assert.Null(container.ResolveOrDefault()); var svc = container.ResolveOrDefault(serviceKey); Assert.NotNull(svc); Assert.Equal(serviceKey, svc.ToString()); } [Fact] public void ResolveKeyedServiceSingletonInstanceWithAnyKey() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(UniversalName); Assert.Null(container.ResolveOrDefault()); var serviceKey1 = "some-key"; var svc1 = container.ResolveOrDefault(serviceKey1); Assert.NotNull(svc1); Assert.Equal(serviceKey1, svc1.ToString()); var serviceKey2 = "some-other-key"; var svc2 = container.ResolveOrDefault(serviceKey2); Assert.NotNull(svc2); Assert.Equal(serviceKey2, svc2.ToString()); } [Fact] public void ResolveKeyedServicesSingletonInstanceWithAnyKey() { var service1 = new FakeService(); var service2 = new FakeService(); using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register>(c => c.WithName(UniversalName) .WithInstance(service1).WithSingletonLifetime()); container.Register>(c => c.WithName("some-key") .WithInstance(service2).WithSingletonLifetime()); var services = container.ResolveAll>("some-key").ToList(); Assert.Equal(new[] { service2 }, services); } [Fact] public void ResolveKeyedServiceSingletonInstanceWithKeyedParameter() { using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton("service1"); container.RegisterSingleton("service2"); container.RegisterSingleton(); Assert.Null(container.ResolveOrDefault()); var svc = container.ResolveOrDefault(); Assert.NotNull(svc); Assert.Equal("service1", svc.Service1.ToString()); Assert.Equal("service2", svc.Service2.ToString()); } [Fact] public void ResolveKeyedServiceSingletonInstanceWithKeyedParameterWithAdditionalAttribute() { using var container = new StashboxContainer(config => config .WithAdditionalDependencyAttribute() .WithAdditionalDependencyNameAttribute() .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton("service1"); container.RegisterSingleton("service2"); container.RegisterSingleton(); Assert.Null(container.ResolveOrDefault()); var svc = container.ResolveOrDefault(); Assert.NotNull(svc); Assert.Equal("service1", svc.Service1.ToString()); Assert.Equal("service2", svc.Service2.ToString()); } [Fact] public void ResolveKeyedServiceSingletonFactory() { var service = new Service(); using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithName("service1") .WithFactory(() => service).WithSingletonLifetime()); Assert.Null(container.ResolveOrDefault()); Assert.Same(service, container.ResolveOrDefault("service1")); } [Fact] public void ResolveKeyedServiceSingletonFactoryWithAnyKey() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithName(UniversalName) .WithFactory(t => new Service((string)t.DependencyName)).WithSingletonLifetime()); Assert.Null(container.ResolveOrDefault()); for (int i = 0; i < 3; i++) { var key = "service" + i; var s1 = container.ResolveOrDefault(key); var s2 = container.ResolveOrDefault(key); Assert.Same(s1, s2); Assert.Equal(key, s1.ToString()); } } [Fact] public void ResolveKeyedServiceSingletonType() { using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton("service1"); Assert.Null(container.ResolveOrDefault()); Assert.Equal(typeof(Service), container.ResolveOrDefault("service1")!.GetType()); } [Fact] public void ResolveKeyedServiceTransientFactory() { using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithName("service1") .WithFactory(t => new Service((string)t.DependencyName))); Assert.Null(container.ResolveOrDefault()); var first = container.ResolveOrDefault("service1"); var second = container.ResolveOrDefault("service1"); Assert.NotSame(first, second); Assert.Equal("service1", first.ToString()); Assert.Equal("service1", second.ToString()); } [Fact] public void ResolveKeyedServiceTransientType() { using var container = new StashboxContainer(config => config .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register("service1"); Assert.Null(container.ResolveOrDefault()); var first = container.ResolveOrDefault("service1"); var second = container.ResolveOrDefault("service1"); Assert.NotSame(first, second); } [Fact] public void ResolveKeyedServiceTransientTypeWithAnyKey() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(UniversalName); Assert.Null(container.ResolveOrDefault()); var first = container.ResolveOrDefault("service1"); var second = container.ResolveOrDefault("service1"); Assert.NotSame(first, second); } [Fact] public void ResolveKeyedServicesAnyKey() { var service1 = new Service(); var service2 = new Service(); var service3 = new Service(); var service4 = new Service(); var service5 = new Service(); var service6 = new Service(); using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithInstance(service1).WithName("first-service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service2).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service3).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service4).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service5).WithName(null).WithSingletonLifetime()); container.Register(c => c.WithInstance(service6).WithSingletonLifetime()); // Return all services registered with a non null key var allServices = container.ResolveAll(UniversalName).ToList(); Assert.Equal(4, allServices.Count); Assert.Equal(new[] { service1, service2, service3, service4 }, allServices); // Check again (caching) var allServices2 = container.ResolveAll(UniversalName).ToList(); Assert.Equal(allServices, allServices2); } [Fact] public void ResolveKeyedServicesAnyKeyWithAnyKeyRegistration() { var service1 = new Service(); var service2 = new Service(); var service3 = new Service(); var service4 = new Service(); var service5 = new Service(); var service6 = new Service(); using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithFactory(() => new Service()).WithName(UniversalName)); container.Register(c => c.WithInstance(service1).WithName("first-service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service2).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service3).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service4).WithName("service").WithSingletonLifetime()); container.Register(c => c.WithInstance(service5).WithName(null).WithSingletonLifetime()); container.Register(c => c.WithInstance(service6).WithSingletonLifetime()); _ = container.Resolve("something-else"); _ = container.Resolve("something-else-again"); // Return all services registered with a non null key, but not the one "created" with KeyedService.AnyKey var allServices = container.ResolveAll(UniversalName).ToList(); Assert.Equal(5, allServices.Count); Assert.Equal([service1, service2, service3, service4], allServices.Skip(1)); } [Fact] public void CombinationalRegistration() { Service service1 = new(); Service service2 = new(); Service keyedService1 = new(); Service keyedService2 = new(); Service anykeyService1 = new(); Service anykeyService2 = new(); Service nullkeyService1 = new(); Service nullkeyService2 = new(); using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(c => c.WithInstance(service1).WithSingletonLifetime()); container.Register(c => c.WithInstance(service2).WithSingletonLifetime()); container.Register(c => c.WithInstance(nullkeyService1).WithName(null).WithSingletonLifetime()); container.Register(c => c.WithInstance(nullkeyService2).WithName(null).WithSingletonLifetime()); container.Register(c => c.WithInstance(anykeyService1).WithName(UniversalName).WithSingletonLifetime()); container.Register(c => c.WithInstance(anykeyService2).WithName(UniversalName).WithSingletonLifetime()); container.Register(c => c.WithInstance(keyedService1).WithName("keyedService").WithSingletonLifetime()); container.Register(c => c.WithInstance(keyedService2).WithName("keyedService").WithSingletonLifetime()); Assert.Equal( [service1, service2, nullkeyService1, nullkeyService2], container.ResolveAll()); Assert.Equal(nullkeyService2, container.Resolve()); Assert.Equal( [service1, service2, nullkeyService1, nullkeyService2], container.ResolveAll()); Assert.Equal(nullkeyService2, container.Resolve()); Assert.Equal( [keyedService1, keyedService2], container.ResolveAll(UniversalName)); Assert.Throws(() => container.Resolve(UniversalName)); Assert.Equal( [keyedService1, keyedService2], container.ResolveAll("keyedService")); Assert.Equal(keyedService2, container.Resolve("keyedService")); } [Fact] public void ResolveKeyedServiceWithKeyedParameter_MissingRegistration_FirstParameter() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(); Assert.Null(container.ResolveOrDefault()); Assert.Throws(container.ResolveOrDefault); } [Fact] public void ResolveKeyedServiceWithKeyedParameter_MissingRegistration_SecondParameter() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(); container.RegisterSingleton("service1"); Assert.Null(container.ResolveOrDefault()); Assert.Throws(container.ResolveOrDefault); } [Fact] public void ResolveKeyedServiceWithKeyedParameter_MissingRegistrationButWithUnkeyedService() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.RegisterSingleton(); container.RegisterSingleton(); Assert.NotNull(container.ResolveOrDefault()); Assert.Throws(container.ResolveOrDefault); } [Fact] public void ResolveKeyedServiceSingletonFactoryWithAnyKeyIgnoreWrongType() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); container.Register(UniversalName); Assert.Null(container.ResolveOrDefault()); Assert.NotNull(container.ResolveOrDefault(87)); Assert.ThrowsAny(() => container.ResolveOrDefault(new object())); } [Fact] public void EnsureNamedResolveOrDefaultDoesntThrow() { using var container = new StashboxContainer(config => config .WithUniversalName(UniversalName) .WithDisposableTransientTracking() .WithNamedDependencyResolutionForUnNamedRequests(false, false) .OverrideResolutionFailedExceptionWith() .WithIgnoreServicesWithUniversalNameForUniversalNamedRequests() .WithForceThrowWhenNamedDependencyIsNotResolvable() .WithRegistrationBehavior(Rules.RegistrationBehavior.PreserveDuplications)); Assert.Null(container.ResolveOrDefault("missing")); } private interface IService; private class Service([DependencyName] string id) : IService { public Service() : this(Guid.NewGuid().ToString()) { } public override string ToString() => id; } private class Service2([AdditionalName] string id) : IService { public Service2() : this(Guid.NewGuid().ToString()) { } public override string ToString() => id; } private class OtherService( [Dependency("service1")] IService service1, [Dependency("service2")] IService service2) { public IService Service1 { get; } = service1; public IService Service2 { get; } = service2; } private class OtherService2( [AdditionalDependency("service1")] IService service1, [AdditionalDependency("service2")] IService service2) { public IService Service1 { get; } = service1; public IService Service2 { get; } = service2; } private class ServiceWithIntKey([DependencyName] int id) : IService { private readonly int id = id; } private class AdditionalNameAttribute : Attribute; private class AdditionalDependencyAttribute : Attribute { public AdditionalDependencyAttribute(string name) { } } } ================================================ FILE: test/LazyTests.cs ================================================ using Stashbox.Exceptions; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class LazyTests { [Fact] public void LazyTests_Resolve() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.False(inst.IsValueCreated); Assert.IsType>(inst); Assert.IsType(inst.Value); } [Fact] public void LazyTests_Resolve_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>(); Assert.Null(inst); } [Fact] public void LazyTests_Resolve_Func() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.False(inst.IsValueCreated); Assert.IsType>>(inst); Assert.IsType(inst.Value()); } [Fact] public void LazyTests_Resolve_Func_Param() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>>>(); Assert.NotNull(inst); Assert.False(inst.IsValueCreated); Assert.IsType>>>(inst); Assert.IsType(inst.Value(5).Value); Assert.Equal(5, inst.Value(5).Value.T); } [Fact] public void LazyTests_Resolve_Func_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>>(); Assert.Null(inst); } [Fact] public void LazyTests_Resolve_Enumerable() { var container = new StashboxContainer(); container.Register(); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.False(inst.IsValueCreated); Assert.IsType>>(inst); Assert.IsType(inst.Value.First()); } [Fact] public void LazyTests_Resolve_Enumerable_Null() { var container = new StashboxContainer(); var inst = container.Resolve>>(); Assert.Empty(inst.Value); } [Fact] public void LazyTests_Resolve_ConstructorDependency() { var container = new StashboxContainer(); container.Register(); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst.Test); Assert.False(inst.Test.IsValueCreated); Assert.IsType>(inst.Test); Assert.IsType(inst.Test.Value); } [Fact] public void LazyTests_Resolve_ConstructorDependency_Null() { var container = new StashboxContainer(); container.Register(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void LazyTests_Resolve_Circular_Evaluate() { var container = new StashboxContainer(); container.Register(); container.Register(); Assert.Throws(container.Resolve); } [Fact] public void LazyTests_Resolve_Circular_Evaluate_Singleton() { var container = new StashboxContainer(); container.RegisterSingleton(); container.RegisterSingleton(); Assert.Throws(container.Resolve); } interface ITest; class Test : ITest; interface ITest2 { int T { get; } } class Test3 : ITest2 { public Test3(int t) { this.T = t; } public int T { get; } } class Test2 { public Lazy Test { get; } public Test2(Lazy test) { this.Test = test; } } class Circular1 { public Lazy Dep { get; set; } public Circular1(Lazy dep) { Dep = dep; } } class Circular2 { public Lazy Dep { get; set; } public Circular2(Lazy dep) { Dep = dep; } } class Circular3 { public IEnumerable> Dep { get; set; } public Circular3(IEnumerable> dep) { Dep = dep; } } class Circular4 { public IEnumerable> Dep { get; set; } public Circular4(IEnumerable> dep) { Dep = dep; } } } ================================================ FILE: test/LifetimeTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Lifetime; using Stashbox.Tests.Utils; using Stashbox.Utils; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class LifetimeTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Resolve(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(); container.Register(); container.Register(); var test1 = container.Resolve(); test1.Name = "test1"; var test2 = container.Resolve(); var test3 = container.Resolve(); Assert.NotNull(test1); Assert.NotNull(test2); Assert.NotNull(test3); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Resolve_Parallel(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(typeof(ITest1), typeof(Test1)); container.Register(); container.Register(); Parallel.For(0, 50000, (i) => { var test1 = container.Resolve(); test1.Name = "test1"; var test2 = container.Resolve(); var test3 = container.Resolve(); Assert.NotNull(test1); Assert.NotNull(test2); Assert.NotNull(test3); }); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Resolve_Parallel_Lazy(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(context => context.WithSingletonLifetime()); container.Register(); container.Register(); Parallel.For(0, 50000, (i) => { var test1 = container.Resolve>(); test1.Value.Name = "test1"; var test2 = container.Resolve>(); var test3 = container.Resolve>(); Assert.NotNull(test1.Value); Assert.NotNull(test2.Value); Assert.NotNull(test3.Value); }); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Scoped_WithNull(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterScoped(); Assert.Null(container.BeginScope().ResolveOrDefault()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_DefinesScope_Generic(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.DefinesScope()); container.RegisterScoped(); using var scope = container.BeginScope(); var inst1 = scope.Resolve(); var inst2 = scope.Resolve(); Assert.NotSame(inst1.Test5, inst2.Test5); Assert.Same(inst2.Test5, inst2.Test6.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_DefinesScope(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(typeof(Test7), c => c.DefinesScope()); container.RegisterScoped(); using var scope = container.BeginScope(); var inst1 = scope.Resolve(); var inst2 = scope.Resolve(); Assert.NotSame(inst1.Test5, inst2.Test5); Assert.Same(inst2.Test5, inst2.Test6.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Per_Request(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()); container.Register(); container.Register(); var inst = container.Resolve(); var t5 = container.Resolve(); Assert.NotNull(inst.Test5); Assert.NotNull(inst.Test6.Test5); Assert.Same(inst.Test5, inst.Test6.Test5); Assert.NotSame(t5, inst.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Per_Request_WithScope(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()); container.Register(c => c.WithScopedLifetime()); container.Register(); using var scope = container.BeginScope(); var inst = scope.Resolve(); var t5 = scope.Resolve(); Assert.NotNull(inst.Test5); Assert.NotNull(inst.Test6.Test5); Assert.Same(inst.Test5, inst.Test6.Test5); Assert.NotSame(t5, inst.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Per_Request_Enumerable(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()); container.Register(); container.Register(); var inst = container.ResolveAll().First(); var t5 = container.ResolveAll().First(); Assert.NotNull(inst.Test5); Assert.NotNull(inst.Test6.Test5); Assert.Same(inst.Test5, inst.Test6.Test5); Assert.NotSame(t5, inst.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Per_Request_Wrapper_Enumerable(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()); container.Register(); container.Register(); var inst = container.Resolve(); var t5 = container.Resolve(); Assert.NotNull(inst.Test5); Assert.NotNull(inst.Test6.Test5); Assert.Same(inst.Test5.First(), inst.Test6.Test5); Assert.NotSame(t5, inst.Test5.First()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Per_Request_Wrapper_Lazy(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()); container.Register(); container.Register(); var inst = container.Resolve(); var t5 = container.Resolve(); Assert.NotNull(inst.Test5); Assert.NotNull(inst.Test6.Test5); Assert.Same(inst.Test5.Value, inst.Test6.Test5); Assert.NotSame(t5, inst.Test5.Value); } [Theory] [InlineData(true)] [InlineData(false)] public void LifetimeTests_Shorter_Lifetime_Not_Resolvable_From_Longer_Direct(bool enabledValidation) { using IStashboxContainer container = new StashboxContainer(c => { if (enabledValidation) c.WithLifetimeValidation(); }); container.RegisterSingleton(); container.RegisterScoped(); using var scope = container.BeginScope(); if (enabledValidation) { var exception = Assert.Throws(() => scope.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("The life-span of", exception.Message); } else { Assert.NotNull(scope.Resolve()); } } [Fact] public void LifetimeTests_Named_Scope_Lifetime_Not_Resolvable_From_Longer_Direct() { using IStashboxContainer container = new StashboxContainer(c => c.WithLifetimeValidation()); container.RegisterSingleton(); container.Register(c => c.WithLifetime(Lifetimes.NamedScope("A"))); using var scope = container.BeginScope("A"); var exception = Assert.Throws(() => scope.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("The life-span of", exception.Message); } [Fact] public void LifetimeTests_Named_Scope_Lifetime_Not_Resolvable_From_Singleton() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton(); container.Register(c => c.WithLifetime(Lifetimes.NamedScope("A"))); using var scope = container.BeginScope("A"); var exception = Assert.Throws(() => scope.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("The scope 'A' was not found to resolve", exception.Message); } [Theory] [InlineData(true)] [InlineData(false)] public void LifetimeTests_Shorter_Lifetime_Not_Resolvable_From_Longer_InDirect(bool enabledValidation) { using IStashboxContainer container = new StashboxContainer(c => { if (enabledValidation) c.WithLifetimeValidation(); }); container.RegisterSingleton(); container.Register(); container.RegisterScoped(); using var scope = container.BeginScope(); if (enabledValidation) { var exception = Assert.Throws(() => scope.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("The life-span of", exception.Message); } else { Assert.NotNull(scope.Resolve()); } } [Fact] public void LifetimeTests_Named_Scope_Lifetime_Not_Resolvable_From_Longer_InDirect() { using IStashboxContainer container = new StashboxContainer(c => c.WithLifetimeValidation()); container.RegisterSingleton(); container.Register(); container.Register(c => c.InNamedScope("A")); using var scope = container.BeginScope("A"); var exception = Assert.Throws(() => scope.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("The life-span of", exception.Message); } [Theory] [InlineData(true)] [InlineData(false)] public void LifetimeTests_Scoped_Is_Not_Resolvable_From_Root_Direct(bool enabledValidation) { using IStashboxContainer container = new StashboxContainer(c => { if (enabledValidation) c.WithLifetimeValidation(); }); container.RegisterScoped(); if (enabledValidation) { var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("from the root scope", exception.Message); } else { Assert.NotNull(container.Resolve()); } } [Fact] public void LifetimeTests_Named_Scoped_Is_Not_Resolvable_From_Root_Direct() { using IStashboxContainer container = new StashboxContainer(); container.Register(c => c.InNamedScope("A")); Assert.Throws(() => container.Resolve()); } [Theory] [InlineData(true)] [InlineData(false)] public void LifetimeTests_Scoped_Is_Not_Resolvable_From_Root_InDirect(bool enabledValidation) { using IStashboxContainer container = new StashboxContainer(c => { if (enabledValidation) c.WithLifetimeValidation(); }); container.Register(); container.RegisterScoped(); if (enabledValidation) { var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("from the root scope", exception.Message); } else { Assert.NotNull(container.Resolve()); } } [Theory] [InlineData(true)] [InlineData(false)] public void LifetimeTests_Scoped_Is_Not_Resolvable_From_Root_AfterResolved(bool enabledValidation) { using IStashboxContainer container = new StashboxContainer(c => { if (enabledValidation) c.WithLifetimeValidation(); }); container.Register(); container.RegisterScoped(); if (enabledValidation) { using var scope = container.BeginScope(); scope.Resolve(); var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test5), exception.Type); Assert.Contains("from the root scope", exception.Message); } else { using var scope = container.BeginScope(); scope.Resolve(); Assert.NotNull(container.Resolve()); } } [Fact] public void LifetimeTests_Named_Scoped_Is_Not_Resolvable_From_Root_AfterResolved() { using IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(c => c.InNamedScope("A")); using var scope = container.BeginScope("A"); scope.Resolve(); var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test6), exception.Type); Assert.Contains("found with unresolvable parameter", exception.Message); } [Fact] public void LifetimeTests_PerRequest_With_Singleton() { using IStashboxContainer container = new StashboxContainer(); container.Register(c => c.WithSingletonLifetime()); container.Register(c => c.WithPerRequestLifetime()); container.Register(); var inst = container.Resolve(); Assert.Same(inst.Test5, inst.Test6.Test5); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_AutoLifetime(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.WithSingletonLifetime()); container.Register(c => c.WithAutoLifetime(Lifetimes.Singleton)); Assert.Same(container.Resolve(), container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_AutoLifetime_Scoped(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.WithSingletonLifetime()); container.Register(c => c.WithAutoLifetime(Lifetimes.Scoped)); Assert.NotSame(container.BeginScope().Resolve(), container.BeginScope().Resolve()); var scope = container.BeginScope(); Assert.Same(scope.Resolve(), scope.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_AutoLifetime_Transient(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(c => c.WithSingletonLifetime()); container.Register(c => c.WithAutoLifetime(Lifetimes.Transient)); Assert.NotSame(container.Resolve(), container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_AutoLifetime_Remains_Transient(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); container.Register(c => c.WithAutoLifetime(Lifetimes.Singleton)); Assert.NotSame(container.Resolve(), container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_AutoLifetime_Without_Dependency(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithAutoLifetime(Lifetimes.Singleton)); Assert.NotSame(container.Resolve(), container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Singleton_Recursive(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(); var ex = Assert.Throws(container.Resolve); Assert.Contains("Circular dependency was detected while resolving", ex.Message); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void LifetimeTests_Singleton_Subsequent_Calls_Get_The_Same_Exception(CompilerType compilerType) { using IStashboxContainer container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.RegisterSingleton(); var ex = Assert.Throws(container.Resolve); Assert.Contains("Service is not registered properly or unresolvable type requested.", ex.Message); ex = Assert.Throws(container.Resolve); Assert.Contains("Service is not registered properly or unresolvable type requested.", ex.Message); } interface ITest1 { string Name { get; set; } } interface ITest2 { string Name { get; set; } } interface ITest3 { string Name { get; set; } } class Test1 : ITest1 { public string Name { get; set; } } class Test2 : ITest2 { public ITest1 Test1 { get; } public string Name { get; set; } public Test2(ITest1 test1) { Test1 = test1; Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureNotNullOrEmpty(test1.Name, nameof(test1.Name)); Shield.EnsureTypeOf(test1); } } class Test3 : ITest3 { public ITest1 Test1 { get; } public ITest2 Test2 { get; } public string Name { get; set; } public Test3(ITest1 test1, ITest2 test2) { Test1 = test1; Test2 = test2; Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureNotNull(test2, nameof(test2)); Shield.EnsureNotNullOrEmpty(test1.Name, nameof(test1.Name)); Shield.EnsureTypeOf(test1); Shield.EnsureTypeOf(test2); } } class Test4 { public static bool IsConstructed; public Test4() { if (IsConstructed) throw new InvalidOperationException(); IsConstructed = true; } } class Test5; class Test6 { public Test5 Test5 { get; } public Test6(Test5 test5) { Test5 = test5; } } class Test7 { public Test5 Test5 { get; } public Test6 Test6 { get; } public Test7(Test5 test5, Test6 test6) { Test5 = test5; Test6 = test6; } } class Test8 { public Test6 Test6 { get; } public Test8(Test6 test6) { Test6 = test6; } } class Test9 { public IEnumerable Test5 { get; } public Test6 Test6 { get; } public Test9(IEnumerable test5, Test6 test6) { Test5 = test5; Test6 = test6; } } class Test10 { public Lazy Test5 { get; } public Test6 Test6 { get; } public Test10(Lazy test5, Test6 test6) { Test5 = test5; Test6 = test6; } } class Test11 { public Test11(IDependencyResolver resolver) { resolver.Resolve(); } } class Test12 { public Test12(IDependencyResolver resolver) { resolver.Resolve(); } } } ================================================ FILE: test/MetadataTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Tests.Utils; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class MetadataTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Fact] public void TupleTests_Resolve_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>(); Assert.Null(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Lazy(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1.Value); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Func(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1()); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.IsAssignableFrom>>(inst); Assert.IsType(inst.First().Item1); Assert.Same(inst.First().Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Enumerable_ShouldNull(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1.First()); Assert.Null(inst.Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Constructor(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.IsType>(inst.Test); Assert.IsType(inst.Test.Item1); Assert.Same(inst.Test.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); ITest1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta_Implementation_Type(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); Test1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta_Chooses_Best_Match(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var inst1 = container.Resolve>(); var inst2 = container.Resolve>(); Assert.NotNull(inst1); Assert.NotNull(inst2); Assert.IsType(inst1.Item1); Assert.Equal("A", inst1.Item2); Assert.IsType(inst2.Item1); Assert.Equal(1, inst2.Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta_Enumerable_Filter(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var filtered1 = container.Resolve>>(); var filtered2 = container.Resolve>>(); Assert.Single(filtered1); Assert.IsType(filtered1.First().Item1); Assert.Equal("A", filtered1.First().Item2); Assert.Single(filtered2); Assert.IsType(filtered2.First().Item1); Assert.Equal(1, filtered2.First().Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta_Enumerable_Choose_Only_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Single(filtered); Assert.IsType(filtered.First().Item1); Assert.Equal("A", filtered.First().Item2); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void TupleTests_Resolve_Custom_Meta_Enumerable_Empty_When_Nothing_Registered_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Empty(filtered); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotEqual(default(ValueTuple), inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Fact] public void ValueTupleTests_Resolve_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>(); Assert.Equal(default(ValueTuple), inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Lazy(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotEqual(default, inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1.Value); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Func(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotEqual(default, inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1()); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.IsAssignableFrom>>(inst); Assert.IsType(inst.First().Item1); Assert.Same(inst.First().Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Enumerable_ShouldNull(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotEqual(default, inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Item1.First()); Assert.Null(inst.Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Constructor(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.IsType>(inst.Test); Assert.IsType(inst.Test.Item1); Assert.Same(inst.Test.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); ITest1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotEqual(default, inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta_Implementation_Type(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); Test1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotEqual(default, inst); Assert.IsType>(inst); Assert.IsType(inst.Item1); Assert.Same(inst.Item2, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta_Chooses_Best_Match(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var inst1 = container.Resolve>(); var inst2 = container.Resolve>(); Assert.NotEqual(default, inst1); Assert.NotEqual(default, inst2); Assert.IsType(inst1.Item1); Assert.Equal("A", inst1.Item2); Assert.IsType(inst2.Item1); Assert.Equal(1, inst2.Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta_Enumerable_Filter(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var filtered1 = container.Resolve>>(); var filtered2 = container.Resolve>>(); Assert.Single(filtered1); Assert.IsType(filtered1.First().Item1); Assert.Equal("A", filtered1.First().Item2); Assert.Single(filtered2); Assert.IsType(filtered2.First().Item1); Assert.Equal(1, filtered2.First().Item2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta_Enumerable_Choose_Only_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Single(filtered); Assert.IsType(filtered.First().Item1); Assert.Equal("A", filtered.First().Item2); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ValueTupleTests_Resolve_Custom_Meta_Enumerable_Empty_When_Nothing_Registered_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Empty(filtered); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Service); Assert.Same(inst.Data, meta); } [Fact] public void MetadataTests_Resolve_Null() { var container = new StashboxContainer(); var inst = container.ResolveOrDefault>(); Assert.Null(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Lazy(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Service.Value); Assert.Same(inst.Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Func(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Service()); Assert.Same(inst.Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Enumerable(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>>(); Assert.NotNull(inst); Assert.IsAssignableFrom>>(inst); Assert.IsType(inst.First().Service); Assert.Same(inst.First().Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Enumerable_ShouldNull(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve, object>>(); Assert.NotNull(inst); Assert.IsType, object>>(inst); Assert.IsType(inst.Service.First()); Assert.Null(inst.Data); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Constructor(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); var meta = new object(); container.Register(c => c.WithMetadata(meta)); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.IsType>(inst.Test); Assert.IsType(inst.Test.Service); Assert.Same(inst.Test.Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); ITest1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Service); Assert.Same(inst.Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta_ImplementationType(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); Test1 meta = new Test1(); container.Register(c => c.WithMetadata(meta)); var inst = container.Resolve>(); Assert.NotNull(inst); Assert.IsType>(inst); Assert.IsType(inst.Service); Assert.Same(inst.Data, meta); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta_Chooses_Best_Match(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var inst1 = container.Resolve>(); var inst2 = container.Resolve>(); Assert.NotNull(inst1); Assert.NotNull(inst2); Assert.IsType(inst1.Service); Assert.Equal("A", inst1.Data); Assert.IsType(inst2.Service); Assert.Equal(1, inst2.Data); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta_Enumerable_Filter(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(c => c.WithMetadata(1)); var filtered1 = container.Resolve>>(); var filtered2 = container.Resolve>>(); Assert.Single(filtered1); Assert.IsType(filtered1.First().Service); Assert.Equal("A", filtered1.First().Data); Assert.Single(filtered2); Assert.IsType(filtered2.First().Service); Assert.Equal(1, filtered2.First().Data); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta_Enumerable_Choose_Only_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithMetadata("A")); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Single(filtered); Assert.IsType(filtered.First().Service); Assert.Equal("A", filtered.First().Data); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void MetadataTests_Resolve_Custom_Meta_Enumerable_Empty_When_Nothing_Registered_With_Meta(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); container.Register(); var filtered = container.Resolve>>(); var unfiltered = container.Resolve>().ToArray(); Assert.Empty(filtered); Assert.Equal(2, unfiltered.Length); Assert.IsType(unfiltered[0]); Assert.IsType(unfiltered[1]); } interface ITest; interface ITest1; class Test : ITest; class A : ITest; class B : ITest; class Test1 : ITest1; class Test2 { public Tuple Test { get; } public Test2(Tuple test) { this.Test = test; } } class Test3 { public Metadata Test { get; } public Test3(Metadata test) { this.Test = test; } } class Test4 { public ValueTuple Test { get; } public Test4(ValueTuple test) { this.Test = test; } } } ================================================ FILE: test/MultitenantTests.cs ================================================ using Stashbox.Multitenant; using System; using System.Reflection; using System.Threading.Tasks; using Moq; using Stashbox.Configuration; using Stashbox.Registration.Fluent; using Stashbox.Resolution; using Xunit; namespace Stashbox.Tests; #pragma warning disable 0618 public class MultitenantTests { [Fact] public void MultitenantTests_Same_Root() { var container = new StashboxContainer(); ITenantDistributor md = new TenantDistributor(container); Assert.Same(container, md.GetType().GetField("rootContainer", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(md)); } [Fact] public void MultitenantTests_Null_Container() { ITenantDistributor md = new TenantDistributor(null); Assert.NotNull(md.GetType().GetField("rootContainer", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(md)); } [Fact] public void MultitenantTests_Get_NonExisting_Null() { var container = new StashboxContainer(); ITenantDistributor md = new TenantDistributor(container); Assert.Null(md.GetTenant("A")); } [Fact] public void MultitenantTests_Configure() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("A", c => c.Register()); Assert.IsType(container.Resolve()); var tenant = md.GetTenant("A"); Assert.NotNull(tenant); Assert.IsType(md.GetTenant("A").Resolve()); } [Fact] public void MultitenantTests_Configure_Dep() { var container = new StashboxContainer(); container.Register(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("A", c => c.Register()); var tenant = md.GetTenant("A"); Assert.IsType(container.Resolve().Ia); Assert.IsType(md.GetTenant("A").Resolve().Ia); } [Fact] public void MultitenantTests_Configure_Validate_Root_Throws() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("A", c => c.Register()); var exception = Assert.Throws(() => md.Validate()); Assert.Single(exception.InnerExceptions); } [Fact] public void MultitenantTests_Configure_Validate_Root_And_Tenants_Throws() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("A", c => c.Register()); var exception = Assert.Throws(() => md.Validate()); Assert.Equal(2, exception.InnerExceptions.Count); } [Fact] public void MultitenantTests_Configure_Validate_Valid() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("A", c => c.Register()); md.Validate(); } [Fact] public void MultitenantTests_Dispose() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("C", c => { }); var tenant = md.GetTenant("C"); var inst = (C)tenant.Resolve(); md.Dispose(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public void MultitenantTests_Dispose_Tenant() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("C", c => c.Register()); var tenant = md.GetTenant("C"); var inst = (C)tenant.Resolve(); md.Dispose(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public void MultitenantTests_Dispose_Multiple() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.Dispose(); md.Dispose(); } [Fact] public void MultitenantTests_Ensure_Container_Method_Calls_Delegate_To_Root() { var container = new Mock(); ITenantDistributor d = new TenantDistributor(container.Object); d.RegisterResolver(new Mock().Object); d.CreateChildContainer(); d.IsRegistered(); d.IsRegistered(typeof(IA)); d.Configure(c => { }); d.Validate(); d.GetRegistrationMappings(); d.GetRegistrationDiagnostics(); d.Register(c => { }); d.Register(); d.Register("a"); d.Register(typeof(C)); d.Register(typeof(IA), typeof(C)); d.Register(c => { }); d.Register("a"); d.Register(typeof(C)); d.RegisterSingleton(); d.RegisterSingleton(typeof(IA), typeof(C)); d.RegisterSingleton("a"); d.RegisterScoped(); d.RegisterScoped(typeof(IA), typeof(C)); d.RegisterScoped("a"); d.RegisterInstance(new C()); d.RegisterInstance((object)new C(), typeof(IA)); d.WireUp(new C()); d.WireUp((object)new C(), typeof(IA)); d.GetService(typeof(IA)); d.Resolve(typeof(IA)); d.Resolve(typeof(IA), ResolutionBehavior.Default); d.Resolve(typeof(IA), ["a"]); d.Resolve(typeof(IA), "a", ["a"]); d.Resolve(typeof(IA), "a"); d.ResolveOrDefault(typeof(IA)); d.ResolveOrDefault(typeof(IA), ResolutionBehavior.Default); d.ResolveOrDefault(typeof(IA), ["a"]); d.ResolveOrDefault(typeof(IA), "a", ["a"]); d.ResolveOrDefault(typeof(IA), "a"); d.ResolveAll(); d.ResolveAll("a"); d.ResolveAll(["a"]); d.ResolveAll("a", ["a"]); d.ResolveAll(typeof(IA)); d.ResolveAll(typeof(IA),"a"); d.ResolveAll(typeof(IA), ["a"]); d.ResolveAll(typeof(IA),"a", ["a"]); d.ResolveFactory(typeof(IA)); d.ResolveFactoryOrDefault(typeof(IA)); d.BeginScope(); d.PutInstanceInScope(typeof(IA), new C()); d.BuildUp(new C()); d.Activate(typeof(C)); d.Activate(typeof(C), ResolutionBehavior.Default); d.InvokeAsyncInitializers(); d.CanResolve(); d.CanResolve(typeof(IA)); d.GetDelegateCacheEntries(); d.ReMap(); d.ReMap(typeof(C)); d.ReMap(typeof(IA), typeof(C)); d.ReMap(); d.ReMapDecorator(); d.ReMapDecorator(typeof(C)); d.ReMapDecorator(typeof(IA), typeof(C)); d.RegisterTypesAs(typeof(IA), new []{typeof(C)}); d.RegisterTypes(new []{typeof(C)}); d.ComposeBy(new Mock().Object.GetType()); d.ComposeBy(new Mock().Object); d.RegisterDecorator(); d.RegisterDecorator(typeof(IA), typeof(C)); d.RegisterDecorator(); d.RegisterDecorator(typeof(C)); d.RegisterDecorator(typeof(C)); d.RegisterFunc((r) => new C()); d.RegisterFunc((i, resolver) => new C()); d.RegisterFunc((i, i1, arg3) => new C()); d.RegisterFunc((i, i1, arg3, arg4) => new C()); d.RegisterFunc((i, i1, arg3, arg4, arg5) => new C()); container.Verify(c => c.RegisterResolver(It.IsAny()), Times.Once); container.Verify(c => c.CreateChildContainer(null, true), Times.Once); container.Verify(c => c.IsRegistered(null), Times.Once); container.Verify(c => c.IsRegistered(typeof(IA), null), Times.Once); container.Verify(c => c.Configure(It.IsAny>()), Times.Once); container.Verify(c => c.Validate(), Times.Once); container.Verify(c => c.GetRegistrationMappings(), Times.Once); container.Verify(c => c.GetRegistrationDiagnostics(), Times.Once); container.Verify(c => c.Register(It.IsAny>()), Times.Once); container.Verify(c => c.Register("a"), Times.Once); container.Verify(c => c.Register(typeof(C), null), Times.Once); container.Verify(c => c.Register(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.Register(It.IsAny>>()), Times.Once); container.Verify(c => c.Register("a"), Times.Once); container.Verify(c => c.Register(typeof(C), null), Times.Once); container.Verify(c => c.RegisterSingleton(null), Times.Once); container.Verify(c => c.RegisterSingleton(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.RegisterSingleton("a"), Times.Once); container.Verify(c => c.RegisterScoped(null), Times.Once); container.Verify(c => c.RegisterScoped(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.RegisterScoped("a"), Times.Once); container.Verify(c => c.RegisterInstance(It.IsAny(), null, false, null), Times.Once); container.Verify(c => c.RegisterInstance(It.IsAny(), It.IsAny(), null, false), Times.Once); container.Verify(c => c.WireUp(It.IsAny(), null, false, null), Times.Once); container.Verify(c => c.WireUp(It.IsAny(), It.IsAny(), null, false), Times.Once); container.Verify(c => c.GetService(typeof(IA)), Times.Once); container.Verify(c => c.Resolve(typeof(IA)), Times.Once); container.Verify(c => c.Resolve(typeof(IA), "a", new object[] { "a" }, ResolutionBehavior.Default), Times.Once); container.Verify(c => c.ResolveOrDefault(typeof(IA)), Times.Once); container.Verify(c => c.ResolveOrDefault(typeof(IA), "a", new object[] { "a" }, ResolutionBehavior.Default), Times.Once); container.Verify(c => c.ResolveFactory(typeof(IA), null, ResolutionBehavior.Default, It.IsAny()), Times.Once); container.Verify(c => c.ResolveFactoryOrDefault(typeof(IA), null, ResolutionBehavior.Default, It.IsAny()), Times.Once); container.Verify(c => c.BeginScope(null, false), Times.Once); container.Verify(c => c.PutInstanceInScope(typeof(IA), It.IsAny(), false, null), Times.Once); container.Verify(c => c.BuildUp(It.IsAny(), ResolutionBehavior.Default), Times.Once); container.Verify(c => c.Activate(typeof(C), ResolutionBehavior.Default), Times.Exactly(2)); container.Verify(c => c.InvokeAsyncInitializers(default), Times.Once); container.Verify(c => c.CanResolve(typeof(IA), null, ResolutionBehavior.Default), Times.Exactly(2)); container.Verify(c => c.GetDelegateCacheEntries(), Times.Once); container.Verify(c => c.ReMap(null), Times.Once); container.Verify(c => c.ReMap(typeof(C), null), Times.Once); container.Verify(c => c.ReMap(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.ReMap(null), Times.Once); container.Verify(c => c.ReMapDecorator(null), Times.Once); container.Verify(c => c.ReMapDecorator(typeof(C), null), Times.Once); container.Verify(c => c.ReMapDecorator(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.RegisterTypesAs(typeof(IA), It.IsAny(), null, null), Times.Once); container.Verify(c => c.RegisterTypes(It.IsAny(), null, null, true, null), Times.Once); container.Verify(c => c.ComposeBy(It.IsAny(), It.IsAny()), Times.Once); container.Verify(c => c.ComposeBy(It.IsAny(), It.IsAny()), Times.Once); container.Verify(c => c.RegisterDecorator(null), Times.Once); container.Verify(c => c.RegisterDecorator(typeof(IA), typeof(C), null), Times.Once); container.Verify(c => c.RegisterDecorator(null), Times.Once); container.Verify(c => c.RegisterDecorator(typeof(C), null), Times.Once); container.Verify(c => c.RegisterDecorator(typeof(C), null), Times.Once); container.Verify(c => c.RegisterFunc(It.IsAny>(), null), Times.Once); container.Verify(c => c.RegisterFunc(It.IsAny>(), null), Times.Once); container.Verify(c => c.RegisterFunc(It.IsAny>(), null), Times.Once); container.Verify(c => c.RegisterFunc(It.IsAny>(), null), Times.Once); container.Verify(c => c.RegisterFunc(It.IsAny>(), null), Times.Once); } #if HAS_ASYNC_DISPOSABLE [Fact] public async Task MultitenantTests_Dispose_Async() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); container.Register(); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("C", c => { }); var tenant = md.GetTenant("C"); var inst = (C)tenant.Resolve(); await md.DisposeAsync(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public async Task MultitenantTests_Dispose_Async_Tenant() { var container = new StashboxContainer(c => c.WithDisposableTransientTracking()); ITenantDistributor md = new TenantDistributor(container); md.ConfigureTenant("C", c => c.Register()); var tenant = md.GetTenant("C"); var inst = (C)tenant.Resolve(); await md.DisposeAsync(); Assert.True(inst.Disposed); Assert.Throws(() => container.Resolve()); Assert.Throws(() => tenant.Resolve()); } [Fact] public async Task MultitenantTests_Dispose_Async_Multiple() { var container = new StashboxContainer(); container.Register(); ITenantDistributor md = new TenantDistributor(container); await md.DisposeAsync(); await md.DisposeAsync(); } #endif interface IA; class A : IA; class B : IA; class C : IA, IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(nameof(C)); this.Disposed = true; } } class D { public D(IA ia) { Ia = ia; } public IA Ia { get; } } } ================================================ FILE: test/NamedResolveTests.cs ================================================ using Stashbox.Exceptions; using System.Linq; using System.Reflection; using System.Text; using Xunit; namespace Stashbox.Tests; public class NamedResolveTests { [Fact] public void NamedResolveTests_Resolve() { using var container = new StashboxContainer() .Register("A") .Register("B"); Assert.IsType(container.Resolve("A")); Assert.IsType(container.Resolve("B")); } [Fact] public void NamedResolveTests_Resolve_ImplType() { using var container = new StashboxContainer() .Register("A") .Register("B"); Assert.IsType(container.Resolve("A")); Assert.IsType(container.Resolve("B")); } [Fact] public void NamedResolveTests_Resolve_Throw_On_Same_Name() { Assert.Throws( () => new StashboxContainer(c => c.WithRegistrationBehavior(Configuration.Rules.RegistrationBehavior.ThrowException)) .Register("A") .Register("A")); } [Fact] public void NamedResolveTests_Resolve_Throw_On_Same_Name_Equality_By_Value() { var key = new StringBuilder("A"); Assert.Throws( () => new StashboxContainer(c => c.WithRegistrationBehavior(Configuration.Rules.RegistrationBehavior.ThrowException)) .Register("A") .Register(key.ToString())); } [Fact] public void NamedResolveTests_Ensure_Delegate_Cache_Works_OnRoot() { using var container = new StashboxContainer() .Register("A"); container.Resolve("A"); container.Resolve("A"); var cache = container.ContainerContext.RootScope.GetDelegateCacheEntries(); Assert.Single(cache); } [Fact] public void NamedResolveTests_Ensure_Delegate_Cache_Works_By_Value_OnRoot() { var key = new StringBuilder("A"); using var container = new StashboxContainer() .Register("A"); container.Resolve("A"); container.Resolve(key.ToString()); var cache = container.ContainerContext.RootScope.GetDelegateCacheEntries(); Assert.Single(cache); } [Fact] public void NamedResolveTests_Ensure_Delegate_Cache_Shared_Between_UnNamed_Scopes() { using var container = new StashboxContainer(); var scope1 = container.BeginScope(); var scope2 = container.BeginScope(); var scope3 = scope2.BeginScope(); var scopeType = scope1.GetType(); var delegateCache1 = scopeType.GetField("delegateCache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(scope1); var delegateCache2 = scopeType.GetField("delegateCache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(scope2); var delegateCache3 = scopeType.GetField("delegateCache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(scope3); Assert.Same(delegateCache1, delegateCache2); Assert.Same(delegateCache2, delegateCache3); } [Fact] public void NamedResolveTests_Ensure_Delegate_Cache_Not_Shared_Between_Root_And_UnNamed_Scopes() { using var container = new StashboxContainer(); var scope = container.BeginScope(); var scopeType = scope.GetType(); var delegateCache1 = scopeType.GetField("delegateCache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(scope); var delegateCache2 = scopeType.GetField("delegateCache", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(container.ContainerContext.RootScope); Assert.NotSame(delegateCache1, delegateCache2); } [Fact] public void NamedResolveTests_Named_Scope_Cache_Works() { using var container = new StashboxContainer() .Register() .Register(); var scope1 = container.BeginScope("A"); var scope2 = container.BeginScope("A"); scope1.Resolve(); scope2.Resolve(); var rootCache = container.ContainerContext.RootScope.GetDelegateCacheEntries(); Assert.Empty(rootCache); var scope1Cache = scope1.GetDelegateCacheEntries(); Assert.Equal(2, scope1Cache.Count()); var scope2Cache = scope2.GetDelegateCacheEntries(); Assert.Equal(2, scope2Cache.Count()); } [Fact] public void NamedResolveTests_Named_Scope_Cache_Works_Equality_By_Value() { using var container = new StashboxContainer() .Register() .Register(); var scope1 = container.BeginScope("A"); var scope2 = container.BeginScope("A"); scope1.Resolve(); scope2.Resolve(); var rootCache = container.ContainerContext.RootScope.GetDelegateCacheEntries(); Assert.Empty(rootCache); var scope1Cache = scope1.GetDelegateCacheEntries(); Assert.Equal(2, scope1Cache.Count()); var scope2Cache = scope2.GetDelegateCacheEntries(); Assert.Equal(2, scope2Cache.Count()); } interface IA; class A : IA; class B : IA; class C : IA; class D : IA; } ================================================ FILE: test/NamedScopeTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Lifetime; using Stashbox.Tests.Utils; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class NamedScopeTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Prefer_Named(CompilerType compilerType) { var name = "A".ToLower(); var inst = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register() .Register(config => config.InNamedScope(name)) .Register() .BeginScope(name) .Resolve(); Assert.IsType(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dependency_Resolve_Prefer_Named(CompilerType compilerType) { var inst = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register() .Register() .Register(config => config.InNamedScope("A")) .Register() .BeginScope("A") .Resolve(); Assert.IsType(inst.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Wrapper_Prefer_Named(CompilerType compilerType) { using var scope = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register(c => c.WithMetadata(new object())) .Register(config => config.InNamedScope("A").WithMetadata(new object())) .Register(c => c.WithMetadata(new object())) .BeginScope("A"); var func = scope.Resolve>(); var lazy = scope.Resolve>(); var tuple = scope.Resolve>(); var enumerable = scope.Resolve>(); var all = scope.ResolveAll(); Assert.IsType(func()); Assert.IsType(lazy.Value); Assert.IsType(tuple.Item1); Assert.IsType(enumerable.Last()); Assert.IsType(all.First()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dependency_Resolve_Wrapper_Prefer_Named(CompilerType compilerType) { var inst = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register() .Register(c => c.WithMetadata(new object())) .Register(config => config.InNamedScope("A").WithMetadata(new object())) .Register(c => c.WithMetadata(new object())) .BeginScope("A") .Resolve(); Assert.IsType(inst.Func()); Assert.IsType(inst.Lazy.Value); Assert.IsType(inst.Enumerable.Last()); Assert.IsType(inst.Tuple.Item1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Prefer_Named_Last(CompilerType compilerType) { var inst = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")) .Register() .Register(config => config.InNamedScope("A")) .BeginScope("A") .Resolve(); Assert.IsType(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Gets_Same_Within_Scope(CompilerType compilerType) { using var scope = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register() .Register(config => config.InNamedScope("A")) .BeginScope("A"); var a = scope.Resolve(); var b = scope.Resolve(); Assert.Same(a, b); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Gets_Named_Within_Scope(CompilerType compilerType) { using var scope = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")) .Register(config => config.InNamedScope("A").WithName("T")) .Register(config => config.InNamedScope("A")) .BeginScope("A"); var a = scope.Resolve("T"); var b = scope.Resolve("T"); var c = scope.Resolve(); Assert.Same(a, b); Assert.NotSame(a, c); Assert.IsType(a); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Gets_Named_When_Scoped_Doesnt_Exist(CompilerType compilerType) { using var scope = new StashboxContainer(config => config.WithCompiler(compilerType)) .Register() .Register(config => config.WithName("T")) .Register() .BeginScope("A"); var a = scope.Resolve("T"); var b = scope.Resolve("T"); var c = scope.Resolve(); Assert.IsType(c); Assert.IsType(b); Assert.IsType(a); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dependency_Resolve_Wrapper_Gets_Same_Within_Scope(CompilerType compilerType) { var inst = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register(config => config.InNamedScope("A").WithMetadata(new object())) .Register(c => c.WithMetadata(new object())) .BeginScope("A") .Resolve(); Assert.Same(inst.Func(), inst.Lazy.Value); Assert.Same(inst.Lazy.Value, inst.Enumerable.Last()); Assert.Same(inst.Enumerable.Last(), inst.Tuple.Item1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Simple_Resolve_Get_Last_If_Scoped_Doesnt_Exist(CompilerType compilerType) { var inst = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register() .BeginScope("A") .Resolve(); Assert.IsType(inst); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dependency_Get_Last_If_Scoped_Doesnt_Exist(CompilerType compilerType) { var inst = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register() .Register(c => c.WithMetadata(new object())) .Register(c => c.WithMetadata(new object())) .BeginScope("A") .Resolve(); Assert.IsType(inst.Func()); Assert.IsType(inst.Lazy.Value); Assert.IsType(inst.Enumerable.Last()); Assert.IsType(inst.Tuple.Item1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Defines_Scope_Prefer_Named(CompilerType compilerType) { var inst = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.DefinesScope("A")) .Register(c => c.WithMetadata(new object())) .Register(config => config.InNamedScope("A").WithMetadata(new object())) .Register(c => c.WithMetadata(new object())) .Resolve(); Assert.IsType(inst.Func()); Assert.IsType(inst.Lazy.Value); Assert.IsType(inst.Enumerable.Last()); Assert.IsType(inst.Tuple.Item1); Assert.Same(inst.Func(), inst.Lazy.Value); Assert.Same(inst.Lazy.Value, inst.Enumerable.Last()); Assert.Same(inst.Enumerable.Last(), inst.Tuple.Item1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Preserve_Instance_Through_Nested_Scopes(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); using var s1 = container.BeginScope("A"); var i1 = s1.Resolve(); using var s2 = s1.BeginScope("C"); var i2 = s2.Resolve(); Assert.Same(i2, i1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dispose_Instance_Through_Nested_Scopes(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); ITest i1; using (var s1 = container.BeginScope("A")) { i1 = s1.Resolve(); using (var s2 = s1.BeginScope()) { var i2 = s2.Resolve(); Assert.Same(i2, i1); } Assert.False(((Test12)i1).Disposed); } Assert.True(((Test12)i1).Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Dispose_Instance_Defines_Named_Scope(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")) .Register(config => config.DefinesScope("A")); Test2 i1; using (var s1 = container.BeginScope()) { i1 = s1.Resolve(); } Assert.True(((Test12)i1.Test).Disposed); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Lifetime_Check(CompilerType compilerType) { var inst = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")) .ContainerContext.RegistrationRepository.GetRegistrationMappings().First(reg => reg.Key == typeof(ITest)); Assert.IsType(inst.Value.Lifetime); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Throws_ResolutionFailedException_Without_Scope(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); Assert.Throws(() => container.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Throws_ResolutionFailedException_Wrong_Scope(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); using var scope = container.BeginScope("B"); Assert.Throws(() => scope.Resolve()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Throws_ResolutionFailedException_Wrong_Scope_Null(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); using var scope = container.BeginScope("B"); Assert.Null(scope.ResolveOrDefault()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_WithNull(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")); Assert.Null(container.BeginScope("A").ResolveOrDefault()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_ChildContainer_Chain(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.DefinesScope("B").InNamedScope("A")); var child = container.CreateChildContainer() .Register(config => config.InNamedScope("B")) .Register(config => config.DefinesScope("A")); var inst = child.Resolve(); Assert.NotNull(inst.Test); Assert.NotNull(inst.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_ChildContainer(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.DefinesScope("A")) .Register(config => config.InNamedScope("A")); var child = container.CreateChildContainer() .Register(config => config.InNamedScope("A")); var inst = child.Resolve(); Assert.IsType(inst.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Chain(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.DefinesScope("B").InNamedScope("A")) .Register(config => config.InNamedScope("B")) .Register(config => config.DefinesScope("A")); var inst = container.Resolve(); Assert.NotNull(inst.Test); Assert.NotNull(inst.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_ChildContainer_Chain_Reverse(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(typeof(Test4), config => config.DefinesScope("A")) .Register(config => config.InNamedScope("B")); var child = container.CreateChildContainer() .Register(config => config.DefinesScope("B").InNamedScope("A")); var inst = child.Resolve(); Assert.NotNull(inst.Test); Assert.NotNull(inst.Test.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void NamedScope_Cache(CompilerType compilerType) { var container = new StashboxContainer(c => c.WithCompiler(compilerType)) .Register(config => config.InNamedScope("A")) .Register(); using var scope = container.BeginScope(); var inst = scope.Resolve(); Assert.IsType(inst); using var scope1 = container.BeginScope("A"); inst = scope1.Resolve(); Assert.IsType(inst); } interface ITest; class Test : ITest; class Test1 : ITest; class Test11 : ITest; class Test12 : ITest, IDisposable { public bool Disposed { get; private set; } public void Dispose() { if (this.Disposed) throw new ObjectDisposedException(""); this.Disposed = true; } } class Test2 { public Test2(ITest test) { Test = test; } public ITest Test { get; } } class Test4 { public Test4(Test2 test) { Test = test; } public Test2 Test { get; } } class Test3 { public Test3(Func func, Lazy lazy, IEnumerable enumerable, Tuple tuple) { Func = func; Lazy = lazy; Enumerable = enumerable; Tuple = tuple; } public Func Func { get; } public Lazy Lazy { get; } public IEnumerable Enumerable { get; } public Tuple Tuple { get; } } } ================================================ FILE: test/OverrideTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Utils; using System; using System.Linq; using System.Threading.Tasks; using Stashbox.Exceptions; using Xunit; namespace Stashbox.Tests; public class OverrideTests { [Fact] public void OverrideTests_Resolve() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(context => context.WithInjectionParameter("test1", new Test1 { Name = "fakeName" })); var inst2 = container.Resolve(); Assert.IsType(inst2); Assert.Equal("fakeName", inst2.Name); container.Register(); var inst1 = container.Resolve(); inst1.Name = "test1"; container.Resolve(); var factory = container.Resolve>(); var inst3 = factory(inst1, inst2); Assert.NotNull(inst3); Assert.IsType(inst3); Assert.Equal("test1fakeNametest1", inst3.Name); } [Fact] public void OverrideTests_Resolve_Parallel() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(context => context.WithInjectionParameter("test1", new Test1 { Name = "fakeName" })); var inst2 = container.Resolve(); Assert.IsType(inst2); Assert.Equal("fakeName", inst2.Name); container.Register(); Parallel.For(0, 50000, (i) => { var inst1 = container.Resolve(); inst1.Name = "test1"; var factory = container.Resolve>(); var inst3 = factory(inst1, inst2); Assert.NotNull(inst3); Assert.IsType(inst3); Assert.Equal("test1fakeNametest1", inst3.Name); }); } [Fact] public void OverrideTests_Resolve_Factory_NonGeneric() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); var inst1 = container.Resolve(); inst1.Name = "test1"; container.Resolve(); var factory = container.ResolveFactory(typeof(ITest2), parameterTypes: typeof(ITest1)); var inst2 = ((Func)factory)(inst1); Assert.NotNull(inst2); Assert.IsType(inst2); Assert.Equal("test1", inst2.Name); } [Fact] public void OverrideTests_Resolve_Factory_NonGeneric_DerivedParam() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst1 = new Test1 { Name = "test" }; var factory = container.ResolveFactory(typeof(ITest2), parameterTypes: inst1.GetType()); var inst2 = (ITest2)factory.DynamicInvoke(inst1); Assert.NotNull(inst2); Assert.Equal("test", inst2.Name); } [Fact] public void OverrideTests_Resolve_Factory() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); var inst1 = container.Resolve(); inst1.Name = "test1"; container.Resolve(); var factory = container.Resolve>(); var inst2 = factory(inst1); Assert.NotNull(inst2); Assert.IsType(inst2); Assert.Equal("test1", inst2.Name); } [Fact] public void OverrideTests_Resolve_Factory_NonGeneric_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); var inst1 = container.Resolve(); inst1.Name = "test1"; container.Resolve(); var factory = container.ResolveFactory(typeof(Lazy), parameterTypes: typeof(ITest1)); var inst2 = ((Func>)factory)(inst1); Assert.NotNull(inst2); Assert.IsType>(inst2); Assert.Equal("test1", inst2.Value.Name); } [Fact] public void OverrideTests_Resolve_Factory_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); var inst1 = container.Resolve(); inst1.Name = "test1"; container.Resolve(); var factory = container.Resolve>>(); var inst2 = factory(inst1); Assert.NotNull(inst2); Assert.IsType>(inst2); Assert.Equal("test1", inst2.Value.Name); } [Fact] public void OverrideTests_Resolve_Parallel_Lazy() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(context => context.WithInjectionParameter("test1", new Test1 { Name = "fakeName" })); var inst2 = container.Resolve>(); Assert.IsType(inst2.Value); Assert.Equal("fakeName", inst2.Value.Name); container.Register(); Parallel.For(0, 50000, (i) => { var inst1 = container.Resolve>(); inst1.Value.Name = "test1"; var factory = container.Resolve>>(); var inst3 = factory(inst1.Value, inst2.Value); Assert.NotNull(inst3); Assert.IsType(inst3.Value); Assert.Equal("test1fakeNametest1", inst3.Value.Name); Assert.True(inst3.Value.MethodInvoked); }); } [Fact] public void OverrideTests_Resolve_Multiple() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var inst4 = container.Resolve(); Assert.NotNull(inst4); Assert.IsType(inst4); Assert.Equal("test2Test6", inst4.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = container.Resolve(dependencyOverrides: [new Test1 { Name = "test" }]); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride_Should_Not_Cached() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = container.Resolve(dependencyOverrides: [new Test1 { Name = "test" }]); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); var inst2 = container.Resolve(dependencyOverrides: [new Test1 { Name = "test2" }]); Assert.NotNull(inst2); Assert.IsType(inst2); Assert.Equal("test2", inst2.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride_Named() { IStashboxContainer container = new StashboxContainer(); container.Register(c => c.WithDependencyBinding("test")); var inst = container.Resolve( [ Override.Of(typeof(ITest1), new Test1 { Name = "test" }, "test"), Override.Of(new Test1 { Name = "test2" }, "test2") ]); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); Assert.Throws(() => { container.Resolve( [ Override.Of(new Test1 { Name = "test" }), Override.Of(new Test1 { Name = "test2" }) ]); }); } [Fact] public void OverrideTests_Resolve_DependencyOverride_NotNamed() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = container.Resolve(dependencyOverrides: [ Override.Of(new Test1 { Name = "test" }) ]); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride_NonGeneric() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = (ITest2)container.Resolve(typeof(ITest2), dependencyOverrides: [new Test1 { Name = "test" }]); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride_All() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = container.ResolveAll([new Test1 { Name = "test" }]).First(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void OverrideTests_Resolve_DependencyOverride_All_NonGeneric() { IStashboxContainer container = new StashboxContainer(); container.Register(); var inst = (ITest2)container.ResolveAll(typeof(ITest2), [new Test1 { Name = "test" }]).First(); Assert.NotNull(inst); Assert.IsType(inst); Assert.Equal("test", inst.Name); } [Fact] public void OverrideTests_Resolve_Func() { using var container = new StashboxContainer(); container.RegisterFunc((name, dr) => new Test1 { Name = name }); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.Equal("testfromfunc", inst.Name); } [Fact] public void OverrideTests_Resolve_Func_From_Parameter() { using var container = new StashboxContainer(); container.Register().Register(); container.Resolve(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.Equal(nameof(Test9), inst.Name); } private interface ITest1 { string Name { get; set; } } private interface ITest2 { string Name { get; set; } } private interface ITest3 { string Name { get; set; } bool MethodInvoked { get; } } private interface ITest4 { string Name { get; set; } } private interface ITest5 { string Name { get; set; } } private interface ITest6 { string Name { get; set; } } private class Test4 : ITest4 { public string Name { get; set; } public Test4(Func test1, Func test2) { this.Name = test1(new Test1 { Name = "test2" }).Name + test2(new Test5 { Name = "Test6" }).Name; } } private class Test5 : ITest5 { public string Name { get; set; } } private class Test6 : ITest6 { public string Name { get; set; } public Test6(ITest5 test1) { this.Name = test1.Name; } } private class Test1 : ITest1 { public string Name { get; set; } } private class Test2 : ITest2 { public string Name { get; set; } public Test2(ITest1 test1) { this.Name = test1.Name; } } private class Test3 : ITest3 { public string Name { get; set; } public bool MethodInvoked { get; set; } public Test3(ITest1 test1, ITest2 test2) { Name = test1.Name + test2.Name; } [InjectionMethod] public void Inject(ITest1 test) { Shield.EnsureNotNull(test, nameof(test)); this.MethodInvoked = true; Name += test.Name; } } private class Test7 { public string Name { get; set; } public Test7(Func func) { this.Name = func("testfromfunc").Name; } } private class Test8 { public string Name { get; set; } public Test8(string name = "") { this.Name = name; } } private class Test9 { public string Name { get; set; } public Test9(Func func) { this.Name = func(nameof(Test9)).Name; } } } ================================================ FILE: test/PerRequestResolutionTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Tests.Utils; using System.Linq; using Xunit; namespace Stashbox.Tests; public class PerRequestResolutionTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()).Register().Register(); using var scope = container.BeginScope(); var c1 = scope.Resolve(); var c2 = scope.Resolve(); IA preA = new A2(); var c3 = scope.Resolve(dependencyOverrides: [preA]); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.IsType(c3.A); Assert.IsType(c3.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); Assert.Same(c3.A, c3.B.A); Assert.Same(c3.A, preA); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()).Register().Register(); var c1 = container.Resolve(); var c2 = container.Resolve(); IA preA = new A2(); var c3 = container.Resolve(dependencyOverrides: [preA]); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.IsType(c3.A); Assert.IsType(c3.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); Assert.Same(c3.A, c3.B.A); Assert.Same(c3.A, preA); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest_Named(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime().WithName("A1")) .Register(c => c.WithPerRequestLifetime().WithName("A2")) .Register(c => c.WithDependencyBinding("a", "A1")) .Register(c => c.WithName("C").WithDependencyBinding("a", "A1")); using var scope = container.BeginScope(); var c1 = scope.Resolve("C"); var c2 = scope.Resolve("C"); IA preA = new A2(); var c3 = scope.Resolve("C", dependencyOverrides: [preA]); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.IsType(c3.A); Assert.IsType(c3.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); Assert.Same(c3.A, c3.B.A); Assert.NotSame(c3.A, preA); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest_Named(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime().WithName("A1")) .Register(c => c.WithPerRequestLifetime().WithName("A2")) .Register(c => c.WithDependencyBinding("a", "A1")) .Register(c => c.WithName("C").WithDependencyBinding("a", "A1")); var c1 = container.Resolve("C"); var c2 = container.Resolve("C"); IA preA = new A2(); var c3 = container.Resolve("C", dependencyOverrides: [preA]); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.IsType(c3.A); Assert.IsType(c3.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); Assert.Same(c3.A, c3.B.A); Assert.NotSame(c3.A, preA); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_GetService(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register() .Register() .Register(); using var scope = container.BeginScope(); var c1 = (C)scope.GetService(typeof(C)); var c2 = (C)scope.GetService(typeof(C)); Assert.NotSame(c1.A, c1.B.A); Assert.NotSame(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_GetService_Null(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); Assert.Null(scope.GetService(typeof(C))); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest_GetService(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); using var scope = container.BeginScope(); var c1 = (C)scope.GetService(typeof(C)); var c2 = (C)scope.GetService(typeof(C)); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_GetService(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register() .Register() .Register(); var c1 = (C)container.GetService(typeof(C)); var c2 = (C)container.GetService(typeof(C)); Assert.NotSame(c1.A, c1.B.A); Assert.NotSame(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_GetService_Null(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); Assert.Null(container.GetService(typeof(C))); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest_GetService(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); var c1 = (C)container.GetService(typeof(C)); var c2 = (C)container.GetService(typeof(C)); Assert.IsType(c1.A); Assert.IsType(c1.B.A); Assert.IsType(c2.A); Assert.IsType(c2.B.A); Assert.Same(c1.A, c1.B.A); Assert.Same(c2.A, c2.B.A); Assert.NotSame(c1.A, c2.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest_ResolveAll(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); using var scope = container.BeginScope(); var each = scope.ResolveAll(typeof(C)).Cast().ToArray(); Assert.Same(each[0].A, each[0].B.A); IA preA = new A2(); each = scope.ResolveAll(typeof(C), [preA]).Cast().ToArray(); Assert.IsType(each[0].A); Assert.Same(each[0].A, each[0].B.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest_ResolveAll_Generic(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); using var scope = container.BeginScope(); var each = scope.ResolveAll().ToArray(); Assert.Same(each[0].A, each[0].B.A); IA preA = new A2(); each = scope.ResolveAll([preA]).ToArray(); Assert.IsType(each[0].A); Assert.Same(each[0].A, each[0].B.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest_ResolveAll(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); var each = container.ResolveAll(typeof(C)).Cast().ToArray(); Assert.Same(each[0].A, each[0].B.A); IA preA = new A2(); each = container.ResolveAll(typeof(C), [preA]).Cast().ToArray(); Assert.IsType(each[0].A); Assert.Same(each[0].A, each[0].B.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest_ResolveAll_Generic(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register() .Register(); var each = container.ResolveAll().ToArray(); Assert.Same(each[0].A, each[0].B.A); IA preA = new A2(); each = container.ResolveAll([preA]).ToArray(); Assert.IsType(each[0].A); Assert.Same(each[0].A, each[0].B.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromScope_PerRequest_Activate(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register(); using var scope = container.BeginScope(); var c = scope.Activate(); Assert.IsType(c.A); Assert.IsType(c.B.A); Assert.Same(c.A, c.B.A); IA preA = new A2(); c = scope.Activate([preA]); Assert.IsType(c.A); Assert.IsType(c.B.A); Assert.Same(c.A, c.B.A); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void FromContainer_PerRequest_Activate(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithPerRequestLifetime()) .Register(); var c = container.Activate(); Assert.IsType(c.A); Assert.IsType(c.B.A); Assert.Same(c.A, c.B.A); IA preA = new A2(); c = container.Activate([preA]); Assert.IsType(c.A); Assert.IsType(c.B.A); Assert.Same(c.A, c.B.A); } interface IA; class A1 : IA; class A2 : IA; class B { public B(IA a) { A = a; } public IA A { get; } } class C { public C(B b, IA a) { B = b; A = a; } public B B { get; } public IA A { get; } } } ================================================ FILE: test/ReMapTests.cs ================================================ using Stashbox.Exceptions; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class ReMapTests { [Fact] public void ReMapTests_Replace_SingleResolve() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var test1 = container.Resolve("test"); var test2 = container.Resolve("test2"); Assert.IsType(test1); Assert.IsType(test2); container.Register(context => context.WithName("test").ReplaceExisting()); var test11 = container.Resolve("test"); var test12 = container.Resolve("test2"); Assert.IsType(test11); Assert.IsType(test12); } [Fact] public void ReMapTests_Replace_PreserveOrder() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var toReplace = container.GetRegistrationMappings() .Single(r => "test".Equals(r.Value.Name)); container.Register(context => context.WithName("test").ReplaceExisting()); var newReg = container.GetRegistrationMappings() .Single(r => "test".Equals(r.Value.Name)); Assert.Equal(toReplace.Value.RegistrationOrder, newReg.Value.RegistrationOrder); } [Fact] public void ReMapTests_Replace_Enumerable_Named() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var coll = container.Resolve>().ToArray(); Assert.IsType(coll[0]); Assert.IsType(coll[1]); container.Register(context => context.WithName("test").ReplaceExisting()); var coll2 = container.Resolve>().ToArray(); Assert.IsType(coll2[0]); Assert.IsType(coll2[1]); } [Fact] public void ReMapTests_ReMap_Replace_Only_If_Exists() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var test1 = container.Resolve("test"); var test2 = container.Resolve("test2"); Assert.IsType(test1); Assert.IsType(test2); container.ReMap(context => context.WithName("test").ReplaceOnlyIfExists()); Assert.Throws(() => container.Resolve("test2")); var test11 = container.Resolve("test"); Assert.IsType(test11); } [Fact] public void ReMapTests_Replace_Only_If_Exists() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var test1 = container.Resolve("test"); var test2 = container.Resolve("test2"); Assert.IsType(test1); Assert.IsType(test2); container.Register(context => context.WithName("test").ReplaceOnlyIfExists()); var test11 = container.Resolve("test"); var test12 = container.Resolve("test2"); Assert.IsType(test11); Assert.IsType(test12); } [Fact] public void ReMapTests_Dont_Replace_If_Not_Exists() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); var test1 = container.Resolve("test"); Assert.IsType(test1); container.Register(context => context.WithName("test2").ReplaceOnlyIfExists()); var test11 = container.Resolve("test"); Assert.IsType(test11); Assert.Throws(() => container.Resolve("test2")); } [Fact] public void ReMapTests_ReMap_Replaces_Every_Registration() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); var test1 = container.Resolve("test"); Assert.IsType(test1); container.ReMap(context => context.WithName("test2").ReplaceOnlyIfExists()); Assert.Throws(() => container.Resolve("test")); var test11 = container.Resolve("test2"); Assert.IsType(test11); } [Fact] public void ReMapTests_ReMap_Without_Name() { using var container = new StashboxContainer(); container.Register(); var test1 = container.Resolve(); Assert.IsType(test1); container.ReMap(context => context.ReplaceOnlyIfExists()); var test11 = container.Resolve(); Assert.IsType(test11); } [Fact] public void ReMapTests_Replace_Only_If_Exists_Instance() { using var container = new StashboxContainer(); var t1 = new Test1(); container.Register(c => c.WithInstance(t1)); var i1 = container.Resolve(); var i2 = container.Resolve(); Assert.Same(t1, i1); Assert.Same(i1, i2); var t2 = new Test1(); container.Register(c => c.WithInstance(t2).ReplaceOnlyIfExists()); i1 = container.Resolve(); i2 = container.Resolve(); Assert.Same(t2, i1); Assert.Same(i1, i2); Assert.NotSame(t1, i1); Assert.NotSame(t1, i2); } [Fact] public void ReMapTests_ReMap_Replace_Only_If_Exists_Instance() { using var container = new StashboxContainer(); var t1 = new Test1(); container.Register(c => c.WithInstance(t1)); var i1 = container.Resolve(); var i2 = container.Resolve(); Assert.Same(t1, i1); Assert.Same(i1, i2); var t2 = new Test1(); container.ReMap(c => c.WithInstance(t2).ReplaceOnlyIfExists()); i1 = container.Resolve(); i2 = container.Resolve(); Assert.Same(t2, i1); Assert.Same(i1, i2); Assert.NotSame(t1, i1); Assert.NotSame(t1, i2); } [Fact] public void ReMapTests_Dont_Replace_If_Instance_Is_Not_Existing() { using var container = new StashboxContainer(); var t1 = new Test1(); container.Register(c => c.WithInstance(t1).ReplaceOnlyIfExists()); Assert.Throws(() => container.Resolve()); } [Fact] public void ReMapTests_ReMap_Dont_Replace_If_Instance_Is_Not_Existing() { using var container = new StashboxContainer(); var t1 = new Test1(); container.ReMap(c => c.WithInstance(t1).ReplaceOnlyIfExists()); Assert.Throws(() => container.Resolve()); } [Fact] public void ReMapTests_Enumerable_Named() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test")); container.Register(context => context.WithName("test2")); var coll = container.Resolve>().ToArray(); Assert.Equal(2, coll.Length); Assert.IsType(coll[0]); Assert.IsType(coll[1]); container.ReMap(); var coll2 = container.Resolve>().ToArray(); Assert.Single(coll2); Assert.IsType(coll2[0]); } [Fact] public void ReMapTests_Func_Named() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test")); container.Resolve>("test2"); var func = container.Resolve>("test"); Assert.IsType(func()); container.ReMap(context => context.WithName("test")); var func2 = container.Resolve>("test"); Assert.IsType(func2()); } [Fact] public void ReMapTests_Lazy_Named() { using var container = new StashboxContainer(); container.Register(context => context.WithName("test2")); container.Register(context => context.WithName("test")); container.Resolve>("test"); var lazy = container.Resolve>("test"); Assert.IsType(lazy.Value); container.ReMap(config => config.WithName("test")); var lazy2 = container.Resolve>("test"); Assert.IsType(lazy2.Value); } [Fact] public void ReMapTests_Enumerable() { using var container = new StashboxContainer(); container.Register(); var coll = container.Resolve>().ToArray(); Assert.IsType(coll[0]); container.ReMap(); var coll2 = container.Resolve>().ToArray(); Assert.IsType(coll2[0]); } [Fact] public void ReMapTests_Func() { using var container = new StashboxContainer(); container.Register(); var func = container.Resolve>(); Assert.IsType(func()); container.ReMap(); var func2 = container.Resolve>(); Assert.IsType(func2()); } [Fact] public void ReMapTests_Lazy() { using var container = new StashboxContainer(); container.Register(); var lazy = container.Resolve>(); Assert.IsType(lazy.Value); container.ReMap(); var lazy2 = container.Resolve>(); Assert.IsType(lazy2.Value); } [Fact] public void ReMapTests_DependencyResolve() { using var container = new StashboxContainer(); container.Register(); container.Register(); var test2 = container.Resolve(); Assert.NotNull(test2.Test1); Assert.IsType(test2.Test1); container.ReMap(typeof(Test11)); var test22 = container.Resolve(); Assert.NotNull(test22.Test1); Assert.IsType(test22.Test1); } [Fact] public void ReMapTests_DependencyResolve_WithoutService() { using var container = new StashboxContainer(); container.RegisterSingleton(); container.Register(); var inst = container.Resolve(); var dep = inst.Test1; Assert.NotNull(dep); Assert.IsType(dep); container.ReMap(); inst = container.Resolve(); Assert.NotNull(inst.Test1); Assert.IsType(inst.Test1); Assert.NotSame(dep, inst.Test1); } [Fact] public void ReMapTests_DependencyResolve_Fluent() { using var container = new StashboxContainer(); container.Register(typeof(Test1)); container.Register(); var test2 = container.Resolve(); Assert.NotNull(test2.Test1); Assert.IsType(test2.Test1); container.ReMap(typeof(Test11)); var test22 = container.Resolve(); Assert.NotNull(test22.Test1); Assert.IsType(test22.Test1); } [Fact] public void ReMapTests_Throws_When_TypeMap_Invalid() { using var container = new StashboxContainer(); Assert.Throws(() => container.ReMap(typeof(Test2))); Assert.Throws(() => container.ReMap(typeof(ITest1), typeof(Test2))); Assert.Throws(() => container.ReMap()); Assert.Throws(() => container.ReMapDecorator(typeof(ITest1), typeof(Test2))); } interface ITest1; interface ITest2 { ITest1 Test1 { get; } } class Test1 : ITest1; class Test11 : ITest1; class Test12 : ITest1; class Test2 : ITest2 { public ITest1 Test1 { get; } public Test2(ITest1 test1) { this.Test1 = test1; } } class Test3 { public Test11 Test1 { get; } public Test3(Test11 test1) { this.Test1 = test1; } } } ================================================ FILE: test/RegisterTypesTests.cs ================================================ using Stashbox.Exceptions; using Stashbox.Lifetime; using System; using System.Collections.Generic; using System.Linq; using Xunit; namespace Stashbox.Tests; public class RegistersTests { [Fact] public void RegistersTests_RegistersAs() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypesAs(new[] { typeof(Test1), typeof(Test11), typeof(Test12) }); var all = container.Resolve>(); Assert.Equal(3, all.Count()); } [Fact] public void RegistersTests_RegistersAs_Assembly() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypesAs(typeof(ITest1).Assembly, t => t == typeof(Test1) || t == typeof(Test11) || t == typeof(Test12)); var all = container.Resolve>(); Assert.Equal(3, all.Count()); } [Fact] public void RegistersTests_RegistersAs_Selector() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypesAs(new[] { typeof(Test1), typeof(Test11), typeof(Test12) }, type => type == typeof(Test12)); var all = container.Resolve>(); Assert.Single(all); } [Fact] public void RegistersTests_RegistersAsSelf_Selector() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypes(new[] { typeof(Test1), typeof(Test11), typeof(Test12) }, type => type == typeof(Test12)); Assert.NotNull(container.Resolve()); } [Fact] public void RegistersTests_RegistersAs_Scoped() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypesAs(new[] { typeof(Test1), typeof(Test11), typeof(Test12), typeof(Test2) }, configurator: context => context.WithScopedLifetime()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().ToArray(); Assert.Equal(3, regs.Length); Assert.True(regs.All(reg => reg.Value.Lifetime is ScopedLifetime)); } [Fact] public void RegistersTests_RegistersAsSelf_Scoped_Selector() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypes(new[] { typeof(Test1), typeof(Test11), typeof(Test12) }, type => type == typeof(Test12), configurator: context => context.WithScopedLifetime()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().ToArray(); Assert.Equal(4, regs.Length); Assert.True(regs.All(reg => reg.Value.Lifetime is ScopedLifetime)); } [Fact] public void RegistersTests_Registers() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypes(new[] { typeof(Test), typeof(Test1), typeof(Test11), typeof(Test12) }); var test = container.ResolveAll(); var test1 = container.ResolveAll(); var test2 = container.ResolveAll(); Assert.Equal(3, test.Count()); Assert.Equal(3, test1.Count()); Assert.Equal(2, test2.Count()); } [Fact] public void RegistersTests_Registers_Selector() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypes(new[] { typeof(Test), typeof(Test1), typeof(Test11), typeof(Test12) }, type => type == typeof(Test12)); var test = container.ResolveAll(); var test1 = container.ResolveAll(); var test2 = container.ResolveAll(); Assert.Single(test); Assert.Single(test1); Assert.Single(test2); } [Fact] public void RegistersTests_Registers_Configurator() { IStashboxContainer container = new StashboxContainer(); container.RegisterTypes(new[] { typeof(Test), typeof(Test1), typeof(Test11), typeof(Test12) }, configurator: context => { if (context.HasServiceType(typeof(ITest2))) context.WithScopedLifetime(); }); using var scope = container.BeginScope(); var test = scope.ResolveAll(); var test1 = scope.ResolveAll(); var test2 = scope.ResolveAll(); var scopeds = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .Where(r => r.Value.Lifetime is ScopedLifetime).ToArray(); Assert.Equal(3, test.Count()); Assert.Equal(3, test1.Count()); Assert.Equal(2, test2.Count()); Assert.Equal(7, scopeds.Length); } [Fact] public void RegistersTests_ComposeBy() { IStashboxContainer container = new StashboxContainer(); container.ComposeBy(typeof(TestCompositionRoot)); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Value.RegistrationId).ToArray(); Assert.Equal(2, regs.Length); Assert.Same(regs[0].Value.ImplementationType, typeof(Test)); Assert.Same(regs[1].Value.ImplementationType, typeof(Test1)); } [Fact] public void RegistersTests_ComposeBy_Throw_DoesntImplement_ICompositionRoot() { IStashboxContainer container = new StashboxContainer(); Assert.Throws(() => container.ComposeBy(typeof(Test))); } [Fact] public void RegistersTests_ComposeBy_Generic() { IStashboxContainer container = new StashboxContainer(); container.ComposeBy(); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Value.RegistrationId).ToArray(); Assert.Equal(2, regs.Length); Assert.Same(regs[0].Value.ImplementationType, typeof(Test)); Assert.Same(regs[1].Value.ImplementationType, typeof(Test1)); } [Fact] public void RegistersTests_ComposeAssembly() { IStashboxContainer container = new StashboxContainer(); container.ComposeAssembly(this.GetType().Assembly, type => !type.FullName.Contains("IssueTests")); var regs = container .GetRegistrationMappings() .OrderBy(r => r.Value.RegistrationId) .ToArray(); Assert.Equal(4, regs.Length); Assert.Same(regs[0].Value.ImplementationType, typeof(Test)); Assert.Same(regs[1].Value.ImplementationType, typeof(Test1)); Assert.Same(regs[2].Value.ImplementationType, typeof(Test11)); Assert.Same(regs[3].Value.ImplementationType, typeof(Test12)); } [Fact] public void RegistersTests_ComposeAssemblies() { IStashboxContainer container = new StashboxContainer(); container.ComposeAssemblies(new[] { this.GetType().Assembly }, type => !type.FullName.Contains("IssueTests")); var regs = container.ContainerContext.RegistrationRepository .GetRegistrationMappings() .OrderBy(r => r.Value.RegistrationId) .ToArray(); Assert.Equal(4, regs.Length); Assert.Same(regs[0].Value.ImplementationType, typeof(Test)); Assert.Same(regs[1].Value.ImplementationType, typeof(Test1)); Assert.Same(regs[2].Value.ImplementationType, typeof(Test11)); Assert.Same(regs[3].Value.ImplementationType, typeof(Test12)); } [Fact] public void RegistersTests_ComposeAssembly_CompositionRootNotFound() { IStashboxContainer container = new StashboxContainer(); Assert.Throws(() => container.ComposeAssemblies(new[] { typeof(IStashboxContainer).Assembly })); } [Fact] public void RegistersTests_AsImplementedTypes_Interfaces() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Key.Name).ToArray(); Assert.Equal(4, regs.Length); Assert.Same(regs[0].Key, typeof(ITest)); Assert.Same(regs[1].Key, typeof(ITest1)); Assert.Same(regs[2].Key, typeof(ITest2)); Assert.Same(regs[3].Key, typeof(Test12)); } [Fact] public void RegistersTests_AsImplementedTypes_Interfaces_NoDuplicate() { var container = new StashboxContainer(); container.Register(context => context .AsImplementedTypes().AsImplementedTypes().AsServiceAlso().AsImplementedTypes()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Key.Name).ToArray(); Assert.Equal(4, regs.Length); Assert.Same(regs[0].Key, typeof(ITest)); Assert.Same(regs[1].Key, typeof(ITest1)); Assert.Same(regs[2].Key, typeof(ITest2)); Assert.Same(regs[3].Key, typeof(Test12)); } [Fact] public void RegistersTests_AsImplementedTypes_Interfaces_ReMap() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().OrderBy(r => r.Key.Name).ToArray(); container.ReMap(context => context.AsImplementedTypes()); var regs2 = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().OrderBy(r => r.Key.Name).ToArray(); Assert.Equal(regs.Length, regs2.Length); Assert.NotEqual(regs[0].Value.RegistrationId, regs2[0].Value.RegistrationId); Assert.NotEqual(regs[1].Value.RegistrationId, regs2[1].Value.RegistrationId); Assert.NotEqual(regs[2].Value.RegistrationId, regs2[2].Value.RegistrationId); Assert.NotEqual(regs[3].Value.RegistrationId, regs2[3].Value.RegistrationId); } [Fact] public void RegistersTests_AsImplementedTypes_BaseType() { var container = new StashboxContainer(); container.Register(typeof(Test14), context => context.AsImplementedTypes()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Key.Name).ToArray(); Assert.Equal(6, regs.Length); Assert.Same(regs[0].Key, typeof(ITest)); Assert.Same(regs[1].Key, typeof(ITest1)); Assert.Same(regs[2].Key, typeof(ITest2)); Assert.Same(regs[3].Key, typeof(Test12)); Assert.Same(regs[4].Key, typeof(Test13)); Assert.Same(regs[5].Key, typeof(Test14)); } [Fact] public void RegistersTests_AsImplementedTypes_BaseType_ReMap() { var container = new StashboxContainer(); container.Register(context => context.AsImplementedTypes()); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Key.Name).ToArray(); container.ReMap(context => context.AsImplementedTypes()); var regs2 = container.ContainerContext.RegistrationRepository.GetRegistrationMappings() .OrderBy(r => r.Key.Name).ToArray(); Assert.Equal(regs.Length, regs2.Length); Assert.NotEqual(regs[0].Value.RegistrationId, regs2[0].Value.RegistrationId); Assert.NotEqual(regs[1].Value.RegistrationId, regs2[1].Value.RegistrationId); Assert.NotEqual(regs[2].Value.RegistrationId, regs2[2].Value.RegistrationId); Assert.NotEqual(regs[3].Value.RegistrationId, regs2[3].Value.RegistrationId); Assert.NotEqual(regs[4].Value.RegistrationId, regs2[4].Value.RegistrationId); Assert.NotEqual(regs[5].Value.RegistrationId, regs2[5].Value.RegistrationId); } [Fact] public void RegistersTests_Generic_ByInterface() { var container = new StashboxContainer(); container.RegisterTypesAs(typeof(IGenTest<>), typeof(IGenTest<>).Assembly); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().OrderBy(r => r.Value.RegistrationId).ToArray(); Assert.Equal(7, regs.Length); Assert.Equal(typeof(IGenTest<>), regs[0].Key); Assert.Equal(typeof(IGenTest), regs[1].Key); Assert.Equal(typeof(IGenTest), regs[2].Key); Assert.Equal(typeof(IGenTest), regs[3].Key); Assert.Equal(typeof(IGenTest), regs[4].Key); Assert.Equal(typeof(IGenTest), regs[5].Key); Assert.Equal(typeof(IGenTest), regs[6].Key); Assert.Equal(typeof(GenTest<>), regs[0].Value.ImplementationType); Assert.Equal(typeof(GenTest1), regs[1].Value.ImplementationType); Assert.Equal(typeof(GenTest2), regs[2].Value.ImplementationType); Assert.Equal(typeof(GenTest3), regs[3].Value.ImplementationType); Assert.Equal(typeof(GenTest4), regs[4].Value.ImplementationType); Assert.Equal(typeof(GenTest5), regs[5].Value.ImplementationType); Assert.Equal(typeof(GenTest6), regs[6].Value.ImplementationType); } [Fact] public void RegistersTests_Generic_ByBase() { var container = new StashboxContainer(); container.RegisterTypesAs(typeof(GenTest<>), typeof(IGenTest<>).Assembly); var regs = container.ContainerContext.RegistrationRepository.GetRegistrationMappings().OrderBy(r => r.Value.RegistrationId).ToArray(); Assert.Equal(4, regs.Length); Assert.Equal(typeof(GenTest<>), regs[0].Key); Assert.Equal(typeof(GenTest), regs[1].Key); Assert.Equal(typeof(GenTest), regs[2].Key); Assert.Equal(typeof(GenTest), regs[3].Key); Assert.Equal(typeof(GenTest<>), regs[0].Value.ImplementationType); Assert.Equal(typeof(GenTest1), regs[1].Value.ImplementationType); Assert.Equal(typeof(GenTest2), regs[2].Value.ImplementationType); Assert.Equal(typeof(GenTest3), regs[3].Value.ImplementationType); } [Fact] public void RegistersTests_AsServiceAlso_Generic_Fail() { var container = new StashboxContainer(); Assert.Throws(() => container.Register(context => context.AsServiceAlso())); } [Fact] public void RegistersTests_AsServiceAlso_Fail() { var container = new StashboxContainer(); Assert.Throws(() => container.Register(typeof(Test1), context => context.AsServiceAlso())); } [Fact] public void RegistersTests_AsServiceAlso_Transient() { var container = new StashboxContainer(); container.Register(context => context.AsServiceAlso()); var inst = container.Resolve(); var inst1 = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst1); Assert.IsType(inst); Assert.IsType(inst1); } [Fact] public void RegistersTests_AsServiceAlso_Singleton() { var container = new StashboxContainer(); container.Register(typeof(ITest), typeof(Test1), context => context.AsServiceAlso().WithSingletonLifetime()); var inst = container.Resolve(); var inst1 = container.Resolve(); Assert.Same(inst, inst1); } interface ITest; interface ITest1; interface ITest2; class Test : ITest; interface IGenTest; class GenTest : IGenTest; class GenTest1 : GenTest; class GenTest2 : GenTest; class GenTest3 : GenTest; class GenTest4 : IGenTest; class GenTest5 : IGenTest; class GenTest6 : IGenTest; class Test2; class Test1 : ITest, ITest1; class Test11 : ITest1, ITest2; class Test12 : ITest, ITest1, ITest2; class Test13 : Test12; class Test14 : Test13; class TestCompositionRoot : ICompositionRoot { public void Compose(IStashboxContainer container) { container.Register(); container.Register(); } } class TestCompositionRoot2 : ICompositionRoot { public void Compose(IStashboxContainer container) { container.Register(); container.Register(); } } } ================================================ FILE: test/ResolveFactoryTests.cs ================================================ using Stashbox.Configuration; using Stashbox.Tests.Utils; using System; using Xunit; namespace Stashbox.Tests; public class ResolveFactoryTests { [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_ParameterLess(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); var factory = container.Resolve>(); Assert.NotNull(factory()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_ParameterLess_Named(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(c => c.WithName("service")); container.Register(c => c.WithName("service1")); var factory = container.Resolve>("service"); Assert.IsType(factory()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_ParameterLess_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); Assert.NotNull(factory()); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_OneParam(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); var factory = container.Resolve>(); var test = new Test(); var inst = factory(test); Assert.Same(test, inst.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_OneParam_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var test = new Test(); var inst = factory(test); Assert.Same(test, inst.Test); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_TwoParams(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); var factory = container.Resolve>(); var test = new Test(); var test1 = new Test1(test); var inst = factory(test, test1); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_TwoParams_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var test = new Test(); var test1 = new Test1(test); var inst = factory(test, test1); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_ThreeParams(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); var factory = container.Resolve>(); var test = new Test(); var test1 = new Test1(test); var test2 = new Test2(test1, test); var inst = factory(test, test1, test2); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); Assert.Same(test2, inst.Test2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_ThreeParams_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var test = new Test(); var test1 = new Test1(test); var test2 = new Test2(test1, test); var inst = factory(test, test1, test2); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); Assert.Same(test2, inst.Test2); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_FourParams(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); var factory = container.Resolve>(); var test = new Test(); var test1 = new Test1(test); var test2 = new Test2(test1, test); var test3 = new Test3(test1, test, test2); var inst = factory(test, test1, test2, test3); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); Assert.Same(test2, inst.Test2); Assert.Same(test3, inst.Test3); } [Theory] [ClassData(typeof(CompilerTypeTestData))] public void ResolveFactoryTests_FourParams_Scoped(CompilerType compilerType) { using var container = new StashboxContainer(c => c.WithCompiler(compilerType)); container.Register(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var test = new Test(); var test1 = new Test1(test); var test2 = new Test2(test1, test); var test3 = new Test3(test1, test, test2); var inst = factory(test, test1, test2, test3); Assert.Same(test, inst.Test); Assert.Same(test1, inst.Test1); Assert.Same(test2, inst.Test2); Assert.Same(test3, inst.Test3); } interface IService; class Service : IService; class Service1 : IService; class Test; class Test1 { public Test1(Test test) { this.Test = test; } public Test Test { get; } } class Test2 { public Test2(Test1 test1, Test test) { this.Test1 = test1; this.Test = test; } public Test1 Test1 { get; } public Test Test { get; } } class Test3 { public Test3(Test1 test1, Test test, Test2 test2) { this.Test1 = test1; this.Test = test; this.Test2 = test2; } public Test1 Test1 { get; } public Test Test { get; } public Test2 Test2 { get; } } class Test4 { public Test4(Test1 test1, Test test, Test2 test2, Test3 test3) { this.Test1 = test1; this.Test = test; this.Test2 = test2; this.Test3 = test3; } public Test1 Test1 { get; } public Test Test { get; } public Test2 Test2 { get; } public Test3 Test3 { get; } } } ================================================ FILE: test/ResolverTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using System.Linq; using Xunit; namespace Stashbox.Tests; public class ResolverTests { [Fact] public void ResolverTests_DefaultValue() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.Equal(default, inst.I); } [Fact] public void ResolverTests_DefaultValue_WithOptional() { using var container = new StashboxContainer(); container.Register(); var inst = container.Resolve(); Assert.Equal(5, inst.I); } [Fact] public void ResolverTests_DefaultValue_WithOptional_LateConfig() { using var container = new StashboxContainer(); container.Register(); container.Configure(config => config .WithDefaultValueInjection()); var inst = container.Resolve(); Assert.Equal(5, inst.I); } [Fact] public void ResolverTests_DefaultValue_RefType_WithOptional() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.Null(inst.I); } [Fact] public void ResolverTests_DefaultValue_RefType_WithOutOptional() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); Assert.Throws(() => container.Resolve()); } [Fact] public void ResolverTests_DefaultValue_RefType_WithOutOptional_AllowNull() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); var result = container.ResolveOrDefault(); Assert.Null(result); } [Fact] public void ResolverTests_DefaultValue_String() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.Null(inst.I); } [Fact] public void ResolverTests_DefaultValue_Null() { using var container = new StashboxContainer(); container.Register(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void ResolverTests_DefaultValue_Nullable() { using var container = new StashboxContainer(config => config.WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.Null(inst.I); } [Fact] public void ResolverTests_UnknownType() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); var inst = container.Resolve(); Assert.NotNull(inst); } [Fact] public void ResolverTests_UnknownType_ChildContainer() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); using var child = container.CreateChildContainer(); var inst = child.Resolve(); Assert.Single(child.ContainerContext.RegistrationRepository.GetRegistrationMappings()); Assert.Empty(container.ContainerContext.RegistrationRepository.GetRegistrationMappings()); } [Fact] public void ResolverTests_UnknownType_Dependency() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst.I); } [Fact] public void ResolverTests_UnknownType_Respects_Name() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()).Register(); container.Resolve(); var reg = container.GetRegistrationMappings().First(r => r.Value.ImplementationType == typeof(RefDep)); Assert.Equal("Ref", reg.Value.Name); } [Fact] public void ResolverTests_UnknownType_Respects_Name_Config_Override() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution(c => { if (c.ImplementationType == typeof(RefDep)) c.WithName("fromUnknownConfig"); })).Register(); container.Resolve(); var reg = container.GetRegistrationMappings().First(r => r.Value.ImplementationType == typeof(RefDep)); Assert.Equal("fromUnknownConfig", reg.Value.Name); } [Fact] public void ResolverTests_PreferDefaultValueOverUnknownType() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution().WithDefaultValueInjection()); container.Register(); var inst = container.Resolve(); Assert.Null(inst.I); } [Fact] public void ResolverTests_MemberInject_WithoutAnnotation() { using var container = new StashboxContainer(config => config .WithAutoMemberInjection() .WithUnknownTypeResolution()); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst.I); } [Fact] public void ResolverTests_MemberInject_WithoutAnnotation_LateConfig() { using var container = new StashboxContainer(); container.Register(); container.Configure(config => config.WithUnknownTypeResolution().WithAutoMemberInjection()); var inst = container.Resolve(); Assert.NotNull(inst.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(context => context.WithAutoMemberInjection()); var inst = container.Resolve(); Assert.NotNull(inst.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection_Field() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(context => context.WithName("fail").WithAutoMemberInjection()); var inst = container.Resolve("fail"); Assert.Null(inst.I); container.Register(context => context.WithName("success").WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields)); var inst1 = container.Resolve("success"); Assert.NotNull(inst1.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection_PrivateSetter() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(context => context.WithName("fail").WithAutoMemberInjection()); var inst = container.Resolve("fail"); Assert.Null(inst.I); container.Register(context => context.WithName("success").WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)); var inst1 = container.Resolve("success"); Assert.NotNull(inst1.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection_Mixed() { using var container = new StashboxContainer(config => config.WithUnknownTypeResolution()); container.Register(context => context.WithName("justfield").WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields)); var inst = container.Resolve("justfield"); Assert.NotNull(inst.I); Assert.Null(inst.I1); container.Register(context => context.WithName("justprivatesetter").WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess)); var inst1 = container.Resolve("justprivatesetter"); Assert.NotNull(inst1.I1); Assert.Null(inst1.I); container.Register(context => context.WithName("mixed") .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess | Rules.AutoMemberInjectionRules.PrivateFields)); var inst2 = container.Resolve("mixed"); Assert.NotNull(inst2.I1); Assert.NotNull(inst2.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection_Mixed_ContainerConfig() { using var container = new StashboxContainer(config => config .WithUnknownTypeResolution() .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess | Rules.AutoMemberInjectionRules.PrivateFields)); container.Register(context => context.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess | Rules.AutoMemberInjectionRules.PrivateFields)); var inst2 = container.Resolve(); Assert.NotNull(inst2.I1); Assert.NotNull(inst2.I); } [Fact] public void ResolverTests_MemberInject_WithAutoMemberInjection_Mixed_PreferRegistrationRuleOverContainerRule() { using var container = new StashboxContainer(config => config .WithUnknownTypeResolution() .WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PropertiesWithLimitedAccess | Rules.AutoMemberInjectionRules.PrivateFields)); container.Register(context => context.WithAutoMemberInjection(Rules.AutoMemberInjectionRules.PrivateFields)); var inst2 = container.Resolve(); Assert.Null(inst2.I1); Assert.NotNull(inst2.I); } class Test { public int I { get; set; } public Test(int i) { this.I = i; } } class Test1 { public int I { get; private set; } public Test1(int i = 5) { this.I = i; } } class Test2 { public RefDep I { get; set; } public Test2(RefDep i = null) { this.I = i; } } class Test3 { public RefDep I { get; set; } public Test3(RefDep i) { this.I = i; } } class Test4 { public string I { get; set; } public Test4(string i) { this.I = i; } } class Test5 { public RefDep I { get; set; } } class Test6 { public RefDep I => this.i; private RefDep i = null; } class Test7 { public RefDep I { get; private set; } } class Test8 { public RefDep I1 { get; private set; } public RefDep I => this.i; private RefDep i = null; } class Test9 { public Test9(int? i = null) { this.I = i; } public int? I { get; private set; } } class Test10 { public Test10([Dependency("Ref")] RefDep refDep) { } } class RefDep; } ================================================ FILE: test/ScopeTests.cs ================================================ using System.Linq; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class ScopeTests { [Fact] public void GetOrAdd_Ensure_Evaluator_DoesNotThrow() { for (int i = 0; i < 5000; i++) { using var scope = (IResolutionScope)new StashboxContainer().BeginScope(); Parallel.For(0, 50, i => { var inst = scope.GetOrAddScopedObject(1, (_, _) => new object(), null, typeof(object)); Assert.NotNull(inst); }); } } [Fact] public void Enusre_Put_Instance_Creates_New_Cache() { using var container = new StashboxContainer(); using var scope = container.BeginScope(); scope.PutInstanceInScope(new A()); scope.PutInstanceInScope(new B()); var cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.Resolve(); scope.Resolve(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(2, cache.ToArray().Length); scope.Resolve(); scope.Resolve(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(2, cache.ToArray().Length); scope.PutInstanceInScope(new C()); cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.Resolve(); scope.Resolve(); scope.Resolve(); scope.Resolve(); scope.Resolve(); scope.Resolve(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(3, cache.ToArray().Length); } [Fact] public void Enusre_Put_Instance_Creates_New_Cache_ResolveOrDefault() { using var container = new StashboxContainer(); using var scope = container.BeginScope(); scope.PutInstanceInScope(new A()); scope.PutInstanceInScope(new B()); var cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.ResolveOrDefault(); scope.ResolveOrDefault(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(2, cache.ToArray().Length); scope.ResolveOrDefault(); scope.ResolveOrDefault(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(2, cache.ToArray().Length); scope.PutInstanceInScope(new C()); cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.ResolveOrDefault(); scope.ResolveOrDefault(); scope.ResolveOrDefault(); scope.ResolveOrDefault(); scope.ResolveOrDefault(); scope.ResolveOrDefault(); cache = scope.GetDelegateCacheEntries(); Assert.Equal(3, cache.ToArray().Length); } [Fact] public void Enusre_Dependency_Overrides_Disables_Cache() { using var container = new StashboxContainer().Register(); using var scope = container.BeginScope(); var cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.Resolve(); cache = scope.GetDelegateCacheEntries(); Assert.Single(cache); } [Fact] public void Enusre_Dependency_Overrides_Disables_Cache_ResolveOrDefault() { using var container = new StashboxContainer().Register(); using var scope = container.BeginScope(); scope.ResolveOrDefault(dependencyOverrides: [new A()]); var cache = scope.GetDelegateCacheEntries(); Assert.Empty(cache); scope.ResolveOrDefault(); cache = scope.GetDelegateCacheEntries(); Assert.Single(cache); } private class A; private class B; private class C; } ================================================ FILE: test/ServiceProviderTests.cs ================================================ using System; using Xunit; namespace Stashbox.Tests; public class ServiceProviderTests { [Fact] public void ServiceProviderTests_Resolve_Self() { using IStashboxContainer container = new StashboxContainer(); Assert.Same(container.ContainerContext.RootScope, container.Resolve()); using var scope = container.BeginScope(); Assert.Same(scope, scope.Resolve()); } [Fact] public void ServiceProviderTests_Resolve_Override() { using var container = new StashboxContainer() .Register(c => c.WithFactory(dr => new CustomSp(dr)).AsServiceAlso().AsServiceAlso()) .Register(); Assert.IsType(container.Resolve()); Assert.Same(container.ContainerContext.RootScope, ((CustomSp)container.Resolve()).DependencyResolver); using var scope = container.BeginScope(); Assert.IsType(scope.Resolve()); Assert.Same(scope, ((CustomSp)scope.Resolve()).DependencyResolver); Assert.IsType(container.Resolve().Test); } [Fact] public void ServiceProviderTests_Resolve_MultiReg() { using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register(c => c.WithFactory(dr => new CustomSp(dr)).AsServiceAlso().AsServiceAlso()) .Register(); Assert.IsType(container.Resolve().Test); Assert.IsType(container.Resolve().Test2); } [Fact] public void ServiceProviderTests_Resolve_MultiReg_AllImplemented() { using var container = new StashboxContainer(c => c.WithDisposableTransientTracking()) .Register(c => c.WithFactory(dr => new CustomSp(dr)).AsImplementedTypes()) .Register(); Assert.IsType(container.Resolve().Test); Assert.IsType(container.Resolve().Test2); } interface ITest; interface ITest2; class CustomSp : IServiceProvider, ITest, ITest2, IDisposable { public IDependencyResolver DependencyResolver { get; } public CustomSp(IDependencyResolver dependencyResolver) { DependencyResolver = dependencyResolver; } public object GetService(Type serviceType) { return null; } public void Dispose() { } } class SpAware { public IServiceProvider ServiceProvider { get; } public ITest Test { get; } public ITest2 Test2 { get; } public SpAware(IServiceProvider serviceProvider, ITest test, ITest2 test2) { ServiceProvider = serviceProvider; Test = test; Test2 = test2; } } } ================================================ FILE: test/StandardResolveTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Configuration; using Stashbox.Exceptions; using Stashbox.Utils; using System; using System.Threading.Tasks; using Xunit; namespace Stashbox.Tests; public class StandardResolveTests { [Fact] public void StandardResolveTests_Resolve() { using IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); var test3 = container.Resolve(); var test2 = container.Resolve(); var test1 = container.Resolve(); Assert.NotNull(test3); Assert.NotNull(test2); Assert.NotNull(test1); Assert.IsType(test1); Assert.IsType(test2); Assert.IsType(test3); } [Fact] public void StandardResolveTests_Ensure_DependencyResolver_CanBeResolved() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); using var scope = container.BeginScope(); var resolver = scope.Resolve(); var test = scope.Resolve(); Assert.Same(resolver, test.DependencyResolver); using var scope1 = container.BeginScope(); var scopedResolver = scope1.Resolve(); var test1 = scope1.Resolve(); Assert.Same(scope1, scopedResolver); Assert.Same(scope1, test1.DependencyResolver); } [Fact] public void StandardResolveTests_Ensure_DependencyResolver_CanBeResolved_FromRoot() { using IStashboxContainer container = new StashboxContainer(); container.Register(); var resolver = container.Resolve(); var test = container.Resolve(); Assert.Same(resolver, test.DependencyResolver); Assert.Same(test.DependencyResolver, container.ContainerContext.RootScope); } [Fact] public void StandardResolveTests_Factory() { using IStashboxContainer container = new StashboxContainer(); container.Register(); var test1 = container.ResolveFactory(typeof(ITest1)).DynamicInvoke(); Assert.NotNull(test1); Assert.IsType(test1); } [Fact] public void StandardResolveTests_Factory_Scoped() { using IStashboxContainer container = new StashboxContainer(); container.Register(); using var child = container.BeginScope(); var test1 = child.ResolveFactory(typeof(ITest1)).DynamicInvoke(); Assert.NotNull(test1); Assert.IsType(test1); } [Fact] public void StandardResolveTests_Factory_ResolutionFailed() { using IStashboxContainer container = new StashboxContainer(); Assert.Throws(() => container.ResolveFactory(typeof(ITest1)).DynamicInvoke()); } [Fact] public void StandardResolveTests_Factory_ResolutionFailed_Null() { using IStashboxContainer container = new StashboxContainer(); var factory = container.ResolveFactoryOrDefault(typeof(ITest1)); Assert.Null(factory); } [Fact] public void StandardResolveTests_DependencyResolve_ResolutionFailed() { using IStashboxContainer container = new StashboxContainer(); container.Register(); Assert.Throws(() => container.Resolve()); } [Fact] public void StandardResolveTests_DependencyResolve_ResolutionFailed_AllowNull() { using IStashboxContainer container = new StashboxContainer(); container.Register(); var result = container.ResolveOrDefault(); Assert.Null(result); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed() { using IStashboxContainer container = new StashboxContainer(); Assert.Throws(() => container.Resolve()); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault()); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.NotNull(container.ResolveOrDefault()); Assert.NotNull(container.ResolveOrDefault()); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull_Override() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault([new Dummy()])); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull_Override() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.NotNull(container.ResolveOrDefault([new Test1()])); Assert.NotNull(container.ResolveOrDefault([new Test1()])); } [Fact] public void StandardResolveTests_DependencyResolve_ResolutionFailed_NullName() { using IStashboxContainer container = new StashboxContainer(); container.Register(); Assert.Throws(() => container.Resolve((object)null)); } [Fact] public void StandardResolveTests_DependencyResolve_ResolutionFailed_AllowNull_NullName() { using IStashboxContainer container = new StashboxContainer(); container.Register(); var result = container.ResolveOrDefault((object)null); Assert.Null(result); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_NullName() { using IStashboxContainer container = new StashboxContainer(); Assert.Throws(() => container.Resolve((object)null)); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull_NullName() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault((object)null)); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull_NullName() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.NotNull(container.ResolveOrDefault((object)null)); Assert.NotNull(container.ResolveOrDefault((object)null)); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull_Override_NullName() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault(null, [new Dummy()])); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull_Override_NullName() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.NotNull(container.ResolveOrDefault(null, [new Test1()])); Assert.NotNull(container.ResolveOrDefault(null, [new Test1()])); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull_Named() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault("test")); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull_Named() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.Null(container.ResolveOrDefault("test")); Assert.Null(container.ResolveOrDefault("test")); } [Fact] public void StandardResolveTests_Resolve_ResolutionFailed_AllowNull_Named_Override() { using IStashboxContainer container = new StashboxContainer(); Assert.Null(container.ResolveOrDefault("test", [new Dummy()])); } [Fact] public void StandardResolveTests_Resolve_ResolutionSuccess_AllowNull_Named_Override() { using IStashboxContainer container = new StashboxContainer().Register(); Assert.Null(container.ResolveOrDefault("test", [new Test1()])); Assert.Null(container.ResolveOrDefault("test", [new Test1()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault()); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.NotNull(scope.ResolveOrDefault()); Assert.NotNull(scope.ResolveOrDefault()); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull_Override() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault([new Dummy()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull_Override() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.NotNull(scope.ResolveOrDefault([new Test1()])); Assert.NotNull(scope.ResolveOrDefault([new Test1()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull_NullName() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault((object)null)); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull_NullName() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.NotNull(scope.ResolveOrDefault((object)null)); Assert.NotNull(scope.ResolveOrDefault((object)null)); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull_Override_NullName() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault(null, [new Dummy()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull_Override_NullName() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.NotNull(scope.ResolveOrDefault(null, [new Test1()])); Assert.NotNull(scope.ResolveOrDefault(null, [new Test1()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull_Named() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault("test")); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull_Named() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.Null(scope.ResolveOrDefault("test")); Assert.Null(scope.ResolveOrDefault("test")); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionFailed_AllowNull_Named_Override() { using var scope = new StashboxContainer().BeginScope(); Assert.Null(scope.ResolveOrDefault("test", [new Dummy()])); } [Fact] public void StandardResolveTests_Scope_Resolve_ResolutionSuccess_AllowNull_Named_Override() { using var scope = new StashboxContainer().Register().BeginScope(); Assert.Null(scope.ResolveOrDefault("test", [new Test1()])); Assert.Null(scope.ResolveOrDefault("test", [new Test1()])); } [Fact] public void StandardResolveTests_Resolve_Parallel() { IStashboxContainer container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); Parallel.For(0, 50000, (i) => { if (i % 100 == 0) { container.Register(context => context.WithName(i.ToString())); container.Register(context => context.WithName($"ITest3{i}")); var test33 = container.Resolve($"ITest3{i}"); var test11 = container.Resolve(typeof(ITest1), i.ToString()); Assert.NotNull(test33); Assert.NotNull(test11); Assert.IsType(test11); Assert.IsType(test33); } var test3 = container.Resolve(); var test2 = container.Resolve(); var test1 = container.Resolve(); Assert.NotNull(test3); Assert.NotNull(test2); Assert.NotNull(test1); Assert.IsType(test1); Assert.IsType(test2); Assert.IsType(test3); }); } [Fact] public void StandardResolveTests_Resolve_Parallel_Lazy() { var container = new StashboxContainer(); container.Register(); container.Register(); container.Register(); Parallel.For(0, 50000, (i) => { if (i % 100 == 0) { container.Register(); container.Register(); } var test3 = container.Resolve>(); var test2 = container.Resolve>(); var test1 = container.Resolve>(); Assert.NotNull(test3.Value); Assert.NotNull(test2.Value); Assert.NotNull(test1.Value); }); } [Fact] public void StandardResolveTests_Resolve_Singleton() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton(); var inst = container.Resolve(); var inst2 = container.Resolve(); Assert.Same(inst, inst2); } [Fact] public void StandardResolveTests_Resolve_Singleton_Named() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton("A"); var inst = container.Resolve("A"); var inst2 = container.Resolve("A"); Assert.Same(inst, inst2); } [Fact] public void StandardResolveTests_Resolve_Singleton_Self() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton(); var inst = container.Resolve(); var inst2 = container.Resolve(); Assert.Same(inst, inst2); } [Fact] public void StandardResolveTests_Resolve_Singleton_Self_Named() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton("A"); var inst = container.Resolve("A"); var inst2 = container.Resolve("A"); Assert.Same(inst, inst2); } [Fact] public void StandardResolveTests_Resolve_Scoped() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); using var scope = container.BeginScope(); var inst = scope.Resolve(); var inst2 = scope.Resolve(); Assert.Same(inst, inst2); using var child = container.BeginScope(); var inst3 = child.Resolve(); var inst4 = child.Resolve(); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Resolve_Scoped_Self() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); using var scope = container.BeginScope(); var inst = scope.Resolve(); var inst2 = scope.Resolve(); Assert.Same(inst, inst2); using var child = container.BeginScope(); var inst3 = child.Resolve(); var inst4 = child.Resolve(); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Resolve_Scoped_Self_Named() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped("A"); using var scope = container.BeginScope(); var inst = scope.Resolve("A"); var inst2 = scope.Resolve("A"); Assert.Same(inst, inst2); using var child = container.BeginScope(); var inst3 = child.Resolve("A"); var inst4 = child.Resolve("A"); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Resolve_Scoped_Named() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped("A"); using var scope = container.BeginScope(); var inst = scope.Resolve("A"); var inst2 = scope.Resolve("A"); Assert.Same(inst, inst2); using var child = container.BeginScope(); var inst3 = child.Resolve("A"); var inst4 = child.Resolve("A"); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Register_Scoped_Named_Non_Generic() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(typeof(ITest1), typeof(Test1), "A"); using var scope = container.BeginScope(); var inst = scope.Resolve("A"); var inst2 = scope.Resolve("A"); Assert.Same(inst, inst2); using var child = container.BeginScope(); var inst3 = child.Resolve("A"); var inst4 = child.Resolve("A"); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Resolve_Scoped_Factory() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var inst = factory(); var inst2 = factory(); Assert.Same(inst, inst2); using var child = container.BeginScope(); var scopeFactory = child.Resolve>(); var inst3 = scopeFactory(); var inst4 = scopeFactory(); Assert.NotSame(inst, inst3); Assert.Same(inst3, inst4); } [Fact] public void StandardResolveTests_Resolve_Scoped_Injection() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(typeof(ITest1), typeof(Test1)); container.RegisterScoped(); using var scope = container.BeginScope(); var inst = scope.Resolve(); var inst2 = scope.Resolve(); Assert.Same(inst.Test, inst2.Test); Assert.Same(inst.Test2, inst2.Test2); Assert.Same(inst.Test, inst2.Test2); using var child = container.BeginScope(); var inst3 = child.Resolve(); var inst4 = child.Resolve(); Assert.NotSame(inst.Test, inst4.Test); Assert.NotSame(inst.Test2, inst4.Test2); Assert.NotSame(inst.Test, inst4.Test2); Assert.Same(inst3.Test, inst4.Test); Assert.Same(inst3.Test2, inst4.Test2); Assert.Same(inst3.Test, inst4.Test2); } [Fact] public void StandardResolveTests_Resolve_Scoped_Injection_Factory() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(typeof(ITest1), typeof(Test1)); container.RegisterScoped(); using var scope = container.BeginScope(); var factory = scope.Resolve>(); var inst = factory(); var inst2 = factory(); Assert.Same(inst.Test, inst2.Test); Assert.Same(inst.Test2, inst2.Test2); Assert.Same(inst.Test, inst2.Test2); using var child = container.BeginScope(); var scopedFactory = child.Resolve>(); var inst3 = scopedFactory(); var inst4 = scopedFactory(); Assert.NotSame(inst.Test, inst4.Test); Assert.NotSame(inst.Test2, inst4.Test2); Assert.NotSame(inst.Test, inst4.Test2); Assert.Same(inst3.Test, inst4.Test); Assert.Same(inst3.Test2, inst4.Test2); Assert.Same(inst3.Test, inst4.Test2); } [Fact] public void StandardResolveTests_Resolve_LastService() { using IStashboxContainer container = new StashboxContainer(); container.Register(typeof(ITest1), typeof(Test1)); container.Register(typeof(ITest1), typeof(Test11)); container.Register(typeof(ITest1), typeof(Test12)); var inst = container.Resolve(); Assert.IsType(inst); } [Fact] public void StandardResolveTests_Resolve_MostParametersConstructor_WithoutDefault() { using IStashboxContainer container = new StashboxContainer(config => config.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferMostParameters)); container.Register(typeof(ITest1), typeof(Test1)); container.Register(typeof(ITest2), typeof(Test22)); Assert.NotNull(container.Resolve()); } [Fact] public void StandardResolveTests_Resolve_MostParametersConstructor() { using IStashboxContainer container = new StashboxContainer(config => config.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferMostParameters)); container.Register(typeof(ITest1), typeof(Test1), context => context.WithName("test1")); container.Register(typeof(ITest1), typeof(Test12), context => context.WithName("test12")); container.Register(typeof(ITest2), typeof(Test222)); Assert.NotNull(container.Resolve()); } [Fact] public void StandardResolveTests_Resolve_LeastParametersConstructor() { using IStashboxContainer container = new StashboxContainer(config => config.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters)); container.Register(typeof(ITest1), typeof(Test1)); container.Register(typeof(ITest2), typeof(Test2222)); Assert.NotNull(container.Resolve()); } [Fact] public void StandardResolveTests_Resolve_None_Of_The_Constructors_Selected() { using IStashboxContainer container = new StashboxContainer(config => config.WithConstructorSelectionRule(Rules.ConstructorSelection.PreferLeastParameters)); container.Register(typeof(ITest2), typeof(Test222)); var exception = Assert.Throws(() => container.Resolve()); Assert.Equal(typeof(Test222), exception.Type); } [Fact] public void StandardResolveTests_Resolve_Scoped_Ok() { using IStashboxContainer container = new StashboxContainer(config => config.WithLifetimeValidation()); container.RegisterScoped(); var inst = container.BeginScope().ResolveOrDefault(); Assert.NotNull(inst); } [Fact] public void StandardResolveTests_Resolve_Scoped_NullDependency() { using IStashboxContainer container = new StashboxContainer(); container.RegisterScoped(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void StandardResolveTests_Resolve_Singleton_NullDependency() { using IStashboxContainer container = new StashboxContainer(); container.RegisterSingleton(); var inst = container.ResolveOrDefault(); Assert.Null(inst); } [Fact] public void StandardResolveTests_Resolve_WithFinalizer() { var finalized = false; using (IStashboxContainer container = new StashboxContainer()) { container.Register(context => context.WithFinalizer(_ => finalized = true)); container.Resolve(); } Assert.True(finalized); } [Fact] public void StandardResolveTests_ResolveAll_Returns_Empty_When_No_Registered() { using var container = new StashboxContainer().Register(c => c.InNamedScope("A")); Assert.Empty(container.ResolveAll()); } [Fact] public void StandardResolveTests_ServiceProvider() { var inst = new StashboxContainer() .Register() .GetService(typeof(ITest1)); Assert.NotNull(inst); Assert.IsType(inst); } [Fact] public void StandardResolveTests_ServiceProvider_Scope_Self() { var scope = new StashboxContainer() .Register() .BeginScope(); Assert.Same(scope, scope.Resolve()); Assert.Same(scope, scope.Resolve().ServiceProvider); } class ScopeDependent { public ScopeDependent(IServiceProvider serviceProvider) { ServiceProvider = serviceProvider; } public IServiceProvider ServiceProvider { get; } } interface ITest1 { string Name { get; set; } } interface ITest2 { string Name { get; set; } } interface ITest3 { string Name { get; set; } } interface ITest4 { ITest1 Test { get; } ITest1 Test2 { get; } } class Test1 : ITest1 { public string Name { get; set; } } class Test11 : ITest1 { public string Name { get; set; } } class Test12 : ITest1 { public string Name { get; set; } } class Test2 : ITest2 { public string Name { get; set; } public Test2(ITest1 test1) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureTypeOf(test1); } } class Test22 : ITest2 { public string Name { get; set; } public Test22(ITest1 test1) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureTypeOf(test1); } public Test22(ITest1 test1, int index) { Assert.True(false, "Wrong constructor selected."); } } class Test222 : ITest2 { public string Name { get; set; } public Test222(ITest1 test1) { Assert.True(false, "Wrong constructor selected."); } public Test222([Dependency("test1")] ITest1 test1, [Dependency("test12")] ITest1 test2) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureNotNull(test2, nameof(test2)); Shield.EnsureTypeOf(test1); Shield.EnsureTypeOf(test2); } } class Test2222 : ITest2 { public string Name { get; set; } public Test2222(ITest1 test1) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureTypeOf(test1); } public Test2222(ITest1 test1, [Dependency("test12")] ITest1 test2) { Assert.True(false, "Wrong constructor selected."); } } class Test3 : ITest3 { public string Name { get; set; } public Test3(ITest1 test1, ITest2 test2) { Shield.EnsureNotNull(test1, nameof(test1)); Shield.EnsureNotNull(test2, nameof(test2)); Shield.EnsureTypeOf(test1); Shield.EnsureTypeOf(test2); } } class Test4 : ITest4 { public ITest1 Test { get; } [Dependency] public ITest1 Test2 { get; set; } public Test4(ITest1 test) { this.Test = test; } } class Test5 { public Test5(ITest1 test) { } } class ResolverTest { public IDependencyResolver DependencyResolver { get; } public ResolverTest(IDependencyResolver dependencyResolver) { this.DependencyResolver = dependencyResolver; } } class Dummy; } ================================================ FILE: test/Utils/CompilerType.cs ================================================ namespace Stashbox.Tests.Utils; public enum CompilerType { Default, Microsoft, Stashbox, FastExpressionCompiler, } ================================================ FILE: test/Utils/CompilerTypeTestData.cs ================================================ using System.Collections; using System.Collections.Generic; namespace Stashbox.Tests.Utils; public class CompilerTypeTestData : IEnumerable { public IEnumerator GetEnumerator() { yield return [CompilerType.Default]; yield return [CompilerType.Microsoft]; yield return [CompilerType.Stashbox]; yield return [CompilerType.FastExpressionCompiler]; } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); } ================================================ FILE: test/Utils/ContainerConfiguratorExtensions.cs ================================================ using FastExpressionCompiler; using Stashbox.Tests.Utils; namespace Stashbox.Configuration; public static class ContainerConfiguratorExtensions { public static ContainerConfigurator WithCompiler(this ContainerConfigurator configurator, CompilerType compilerType) { return compilerType switch { CompilerType.Microsoft => configurator.WithExpressionCompiler(Rules.ExpressionCompilers.MicrosoftExpressionCompiler), CompilerType.Stashbox => configurator.WithExpressionCompiler(Rules.ExpressionCompilers.StashboxExpressionCompiler), CompilerType.FastExpressionCompiler => configurator.WithExpressionCompiler(lambda => lambda.CompileFast()), _ => configurator, }; } } ================================================ FILE: test/Utils/TypeGen.cs ================================================ using System; using System.Collections.Generic; namespace Stashbox.Tests.Utils; public static class TypeGen { public static (Type, Type) GetCollidingTypes() { var hashes = new Dictionary(); uint n = 0; while (true) { var type = GenerateType(n++); var hash = System.Runtime.CompilerServices.RuntimeHelpers.GetHashCode(type); if (hashes.TryGetValue(hash, out var hash1)) { return (hash1, type); } hashes.Add(hash, type); } } private static Type GenerateType(uint n) { var current = typeof(Nil); for (var i=0; i < sizeof(uint) * 8; i++) { var leading = n >> i; if (leading == 0) { return current; } var bit = (n >> i) & 0x01; current = bit == 0 ? typeof(Zero<>).MakeGenericType(current) : typeof(One<>).MakeGenericType(current); } return current; } private class Nil; private class Zero; private class One; } ================================================ FILE: test/WireUpTests.cs ================================================ using Stashbox.Attributes; using Stashbox.Utils; using Xunit; namespace Stashbox.Tests; public class WireUpTests { [Fact] public void WireUp_Multiple() { using var container = new StashboxContainer(); var test1 = new Test1(); container.WireUp(test1); var test2 = new Test(); container.WireUp(test2); var inst = container.Resolve(); var inst2 = container.Resolve(); Assert.Same(test1, inst); Assert.Same(test2, inst2); } [Fact] public void WireUp_Multiple_Named() { using var container = new StashboxContainer(); var test1 = new Test(); container.WireUp(test1, "test1"); var test2 = new Test(); container.WireUp(test2, "test2"); var inst = container.Resolve("test1"); var inst2 = container.Resolve("test2"); Assert.Same(test1, inst); Assert.Same(test2, inst2); } [Fact] public void WireUpTests_InjectionMember() { using var container = new StashboxContainer(); container.RegisterSingleton(); var test1 = new Test1(); container.WireUp(test1); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test1); Assert.IsType(inst); Assert.IsType(inst.Test1); Assert.IsType(inst.Test1.Test); } [Fact] public void WireUpTests_InjectionMember_ServiceUpdated() { using var container = new StashboxContainer(); container.RegisterSingleton(); var test1 = new Test1(); container.WireUp(test1); container.ReMap(c => c.WithSingletonLifetime()); container.Register(); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test1); Assert.IsType(inst); Assert.IsType(inst.Test1); Assert.IsType(inst.Test1.Test); } [Fact] public void WireUpTests_InjectionMember_WithoutService() { using var container = new StashboxContainer(); container.RegisterSingleton(); var test1 = new Test1(); container.WireUp(test1); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test); Assert.IsType(inst); Assert.IsType(inst.Test); } [Fact] public void WireUpTests_WithoutService_NonGeneric() { using var container = new StashboxContainer(); container.RegisterSingleton(); object test1 = new Test1(); container.WireUp(test1, typeof(Test1)); var inst = container.Resolve(); Assert.NotNull(inst); Assert.NotNull(inst.Test); Assert.NotNull(inst.test); Assert.IsType(inst); Assert.IsType(inst.Test); Assert.IsType(inst.test); } interface ITest; interface ITest1 { ITest Test { get; } } class Test : ITest; class Test1 : ITest1 { [Dependency] #pragma warning disable 649 public ITest test; #pragma warning restore 649 [Dependency] public ITest Test { get; set; } [InjectionMethod] public void Init() { Shield.EnsureNotNull(Test, nameof(Test)); } } class Test2 { public ITest1 Test1 { get; set; } public Test2(ITest1 test1) { this.Test1 = test1; } } } ================================================ FILE: test/WithDynamicResolutionTests.cs ================================================ using System; using Xunit; namespace Stashbox.Tests; public class WithDynamicResolutionTests { [Fact] public void WithDynamicResolutionTests_TopRequest() { using var container = new StashboxContainer() .Register(c => c.WithDynamicResolution()); Assert.NotNull(container.Resolve()); } [Fact] public void WithDynamicResolutionTests_Dependency() { using var container = new StashboxContainer() .Register(c => c.WithDynamicResolution()) .Register(c => c.WithDependencyBinding("A")); Assert.NotNull(container.Resolve().A); } [Fact] public void WithDynamicResolutionTests_Dependency_Override() { using var container = new StashboxContainer() .Register(c => c.WithDependencyBinding("A").WithDynamicResolution()) .Register(c => c.WithDependencyBinding("B")); var @override = new A(); var inst = container.Resolve([@override]); Assert.Same(@override, inst.B.A); } [Fact] public void WithDynamicResolutionTests_Circle() { using var container = new StashboxContainer() .Register(c => c.WithDependencyBinding("Circle2").WithDynamicResolution()) .Register(c => c.WithDependencyBinding("Circle1").WithDynamicResolution()); var circle1 = container.Resolve(); var circle2 = container.Resolve(); Assert.NotNull(circle1.Circle2.Value); Assert.NotNull(circle1.Circle2.Value.Circle1); Assert.NotNull(circle2.Circle1.Value); Assert.NotNull(circle2.Circle1.Value.Circle2); } class A; class B { public A A { get; set; } } class C { public B B { get; set; } } class Circle1 { public Lazy Circle2 { get; set; } } class Circle2 { public Lazy Circle1 { get; set; } } } ================================================ FILE: test/stashbox.tests.csproj ================================================  net5.0;net6.0;net7.0;net8.0;net9.0;net10.0 Stashbox.Tests Stashbox.Tests true ../sn.snk latest false HAS_ASYNC_DISPOSABLE HAS_ASYNC_DISPOSABLE;HAS_REQUIRED all runtime; build; native; contentfiles; analyzers; buildtransitive all runtime; build; native; contentfiles; analyzers ================================================ FILE: test/testassembly/Composition.cs ================================================ using Stashbox; namespace TestAssembly { public class Composition : ICompositionRoot { public void Compose(IStashboxContainer container) { container.Register(c => c.WithName("Comp")); } } public interface IComp { } class Comp : IComp { } } ================================================ FILE: test/testassembly/TestClasses.cs ================================================ namespace TestAssembly { public interface ITA_T1 { } public interface ITA_T2 { } public interface ITA_TG { } public abstract class TA_A { } public interface ITA_TGM { } public class TA_T1 : ITA_T1 { } public class TA_TM1 : ITA_T1 { } public class TA_TM2 : ITA_T1 { } public class TA_TM3 : ITA_T1 { } public class TA_T2 : ITA_T1, ITA_T2 { } public class TA_T3 : ITA_T1, ITA_T2 { } public class TA_TG : ITA_TG { } public class TA_TGC1 : ITA_TG { } public class TA_TGC2 : ITA_TG { } public class TA_TGC3 : ITA_TG { } public class TA_TGM : ITA_TGM { } public class TA_TGMI : ITA_TGM { } public class TA_AI1 : TA_A { } public class TA_AI2 : TA_A, ITA_T1 { } } ================================================ FILE: test/testassembly/testassembly.csproj ================================================  netstandard2.0 TestAssembly TestAssembly true ../../sn.snk