From 23523be4545b1e730261967770fce9b5b5554f49 Mon Sep 17 00:00:00 2001 From: Peter Csajtai Date: Fri, 26 Jul 2024 01:47:59 +0200 Subject: [PATCH] Add option to turn off generic covariance and contravariance check --- .version | 2 +- CHANGELOG.md | 5 ++ README.md | 2 +- docs/docs/advanced/generics.md | 6 +- .../configuration/container-configuration.md | 16 ++++ docs/docs/getting-started/introduction.md | 6 +- docs/docs/getting-started/overview.md | 2 +- docs/src/pages/index.js | 2 +- .../EnumerableBenchmarks.cs | 5 +- sandbox/stashbox.benchmarks/Program.cs | 3 +- .../Stashbox.Benchmarks.csproj | 10 ++- sandbox/stashbox.benchmarks/TreeBenchmarks.cs | 79 +++++++++++++++++++ src/Configuration/ContainerConfiguration.cs | 8 ++ src/Configuration/ContainerConfigurator.cs | 11 +++ src/ContainerContext.cs | 2 +- src/Registration/DecoratorRepository.cs | 6 +- src/Registration/RegistrationRepository.cs | 13 ++- src/ResolutionScope.cs | 2 +- src/stashbox.csproj | 1 + .../16_Extensions_Identity_OptionsMonitor.cs | 25 ++++++ 20 files changed, 180 insertions(+), 26 deletions(-) create mode 100644 sandbox/stashbox.benchmarks/TreeBenchmarks.cs create mode 100644 test/IssueTests/16_Extensions_Identity_OptionsMonitor.cs diff --git a/.version b/.version index 4bcbe369..e0cb0046 100644 --- a/.version +++ b/.version @@ -1 +1 @@ -5.14.1 \ No newline at end of file +5.15.0 \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 432e25bd..7f4989b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v5.15.0] - 2024-07-26 +### Added +- Option to turn off generic covariance and contravariance check during the resolution of generic type collections. + ## [v5.14.1] - 2024-04-08 ### Fixed - [#163](https://github.com/z4kn4fein/stashbox/issues/163): Last-write win problem when hash collision happens. @@ -418,6 +422,7 @@ The validation was executed only at the expression tree building phase, so an al - Removed the legacy container extension functionality. - Removed the support of PCL v259. +[v5.15.0]: https://github.com/z4kn4fein/stashbox/compare/5.14.1...5.15.0 [v5.14.1]: https://github.com/z4kn4fein/stashbox/compare/5.14.0...5.14.1 [v5.14.0]: https://github.com/z4kn4fein/stashbox/compare/5.13.0...5.14.0 [v5.13.0]: https://github.com/z4kn4fein/stashbox/compare/5.12.2...5.13.0 diff --git a/README.md b/README.md index 32e689fe..294bdcf7 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ Stashbox is a lightweight, fast, and portable dependency injection framework for Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (pre-release) --- | --- |---------------------------------------------------------------------------------------------------------------------------------| --- -[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=5.14.1)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/) +[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=5.15.0)](https://www.fuget.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. diff --git a/docs/docs/advanced/generics.md b/docs/docs/advanced/generics.md index e0bcc482..9e5be4d8 100644 --- a/docs/docs/advanced/generics.md +++ b/docs/docs/advanced/generics.md @@ -224,4 +224,8 @@ Despite the fact that only `IEventHandler` implementations were r If we request `IEventHandler`, only `UpdatedEventHandler` would be returned, because `IGeneralEvent` is less derived, so `IEventHandler` implementations are not fit into `IEventHandler` collections. - \ No newline at end of file + + +:::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). +::: \ No newline at end of file diff --git a/docs/docs/configuration/container-configuration.md b/docs/docs/configuration/container-configuration.md index ce4a4291..f228a50e 100644 --- a/docs/docs/configuration/container-configuration.md +++ b/docs/docs/configuration/container-configuration.md @@ -328,6 +328,22 @@ new StashboxContainer(options => options +## 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()); +``` + +
+
## Conventional resolution diff --git a/docs/docs/getting-started/introduction.md b/docs/docs/getting-started/introduction.md index 29a08ac5..74dc9689 100644 --- a/docs/docs/getting-started/introduction.md +++ b/docs/docs/getting-started/introduction.md @@ -12,7 +12,7 @@ Stashbox and its extensions are distributed via [NuGet](https://www.nuget.org/pa You can install the package by typing the following into the Package Manager Console: ```powershell -Install-Package Stashbox -Version 5.14.1 +Install-Package Stashbox -Version 5.15.0 ``` @@ -20,7 +20,7 @@ Install-Package Stashbox -Version 5.14.1 You can install the package by using the dotnet cli: ```bash -dotnet add package Stashbox --version 5.14.1 +dotnet add package Stashbox --version 5.15.0 ``` @@ -28,7 +28,7 @@ dotnet add package Stashbox --version 5.14.1 You can add the package into the package references of your `.csproj`: ```xml - + ``` diff --git a/docs/docs/getting-started/overview.md b/docs/docs/getting-started/overview.md index 32bd2d25..4b6f9009 100644 --- a/docs/docs/getting-started/overview.md +++ b/docs/docs/getting-started/overview.md @@ -17,7 +17,7 @@ These are the latest available stable and pre-release versions: Github (stable) | NuGet (stable) | Fuget (stable) | NuGet (daily) --- | --- |---------------------------------------------------------------------------------------------------------------------------------| --- -[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=5.14.1)](https://www.fuget.org/packages/Stashbox) | [![Nuget pre-release](https://img.shields.io/nuget/vpre/Stashbox)](https://www.nuget.org/packages/Stashbox/) +[![Github release](https://img.shields.io/github/release/z4kn4fein/stashbox.svg)](https://github.com/z4kn4fein/stashbox/releases) | [![NuGet Version](https://buildstats.info/nuget/Stashbox)](https://www.nuget.org/packages/Stashbox/) | [![Stashbox on fuget.org](https://www.fuget.org/packages/Stashbox/badge.svg?v=5.15.0)](https://www.fuget.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. diff --git a/docs/src/pages/index.js b/docs/src/pages/index.js index eef8209b..99f4fc16 100644 --- a/docs/src/pages/index.js +++ b/docs/src/pages/index.js @@ -24,7 +24,7 @@ function HomepageHeader() {
-
{'$'} dotnet add package Stashbox --version 5.14.1
+
{'$'} dotnet add package Stashbox --version 5.15.0
diff --git a/sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs b/sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs index 4807e0b2..381a6a7e 100644 --- a/sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs +++ b/sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs @@ -1,5 +1,6 @@ extern alias from_nuget; extern alias from_project; +using System.Collections.Generic; using BenchmarkDotNet.Attributes; namespace Stashbox.Benchmarks @@ -30,13 +31,13 @@ public void Setup() [Benchmark(Baseline = true)] public object Old() { - return this.oldContainer.ResolveAll(typeof(IA)); + return this.oldContainer.Resolve(typeof(IEnumerable)); } [Benchmark] public object New() { - return this.newContainer.ResolveAll(typeof(IA)); + return this.newContainer.Resolve(typeof(IEnumerable)); } interface IA { } diff --git a/sandbox/stashbox.benchmarks/Program.cs b/sandbox/stashbox.benchmarks/Program.cs index c5eeb99a..2b922a6e 100644 --- a/sandbox/stashbox.benchmarks/Program.cs +++ b/sandbox/stashbox.benchmarks/Program.cs @@ -24,7 +24,8 @@ static void Main() // BenchmarkConverter.TypeToBenchmarks( typeof(FuncBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(FinalizerBenchmarks), config), // BenchmarkConverter.TypeToBenchmarks( typeof(NullableBenchmarks), config), - BenchmarkConverter.TypeToBenchmarks( typeof(BeginScopeBenchmarks), 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), diff --git a/sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj b/sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj index 09d6c631..7bbf7d0b 100644 --- a/sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj +++ b/sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj @@ -2,11 +2,13 @@ Exe - net7.0 + net8.0 Stashbox.Benchmarks Stashbox.Benchmarks Debug;Release;Benchmark latest + true + ../../sn.snk @@ -15,12 +17,12 @@ - + - + - ..\..\src\bin\Benchmark\net7.0\Stashbox.Benchmark.dll + ..\..\src\bin\Benchmark\net8.0\Stashbox.Benchmark.dll from_project true diff --git a/sandbox/stashbox.benchmarks/TreeBenchmarks.cs b/sandbox/stashbox.benchmarks/TreeBenchmarks.cs new file mode 100644 index 00000000..77ba6b81 --- /dev/null +++ b/sandbox/stashbox.benchmarks/TreeBenchmarks.cs @@ -0,0 +1,79 @@ +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); + } + } +} \ No newline at end of file diff --git a/src/Configuration/ContainerConfiguration.cs b/src/Configuration/ContainerConfiguration.cs index f6e67e26..3f012baf 100644 --- a/src/Configuration/ContainerConfiguration.cs +++ b/src/Configuration/ContainerConfiguration.cs @@ -96,6 +96,11 @@ public class ContainerConfiguration /// 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; } + internal object? UniversalName { get; set; } internal ExpandableArray? AdditionalDependencyNameAttributeTypes { get; set; } @@ -117,6 +122,7 @@ private ContainerConfiguration(bool trackTransientsForDisposalEnabled, bool treatingParameterAndMemberNameAsDependencyNameEnabled, bool namedDependencyResolutionForUnNamedRequestsEnabled, bool reBuildSingletonsInChildContainerEnabled, + bool variantGenericTypesEnabled, Rules.AutoMemberInjectionRules autoMemberInjectionRule, Func, IEnumerable> constructorSelectionRule, Action? unknownTypeConfigurator, @@ -137,6 +143,7 @@ private ContainerConfiguration(bool trackTransientsForDisposalEnabled, this.TreatingParameterAndMemberNameAsDependencyNameEnabled = treatingParameterAndMemberNameAsDependencyNameEnabled; this.NamedDependencyResolutionForUnNamedRequestsEnabled = namedDependencyResolutionForUnNamedRequestsEnabled; this.ReBuildSingletonsInChildContainerEnabled = reBuildSingletonsInChildContainerEnabled; + this.VariantGenericTypesEnabled = variantGenericTypesEnabled; this.AutoMemberInjectionRule = autoMemberInjectionRule; this.ConstructorSelectionRule = constructorSelectionRule; this.UnknownTypeConfigurator = unknownTypeConfigurator; @@ -159,6 +166,7 @@ internal ContainerConfiguration Clone() => this.TreatingParameterAndMemberNameAsDependencyNameEnabled, this.NamedDependencyResolutionForUnNamedRequestsEnabled, this.ReBuildSingletonsInChildContainerEnabled, + this.VariantGenericTypesEnabled, this.AutoMemberInjectionRule, this.ConstructorSelectionRule, this.UnknownTypeConfigurator, diff --git a/src/Configuration/ContainerConfigurator.cs b/src/Configuration/ContainerConfigurator.cs index 606249f6..6518b111 100644 --- a/src/Configuration/ContainerConfigurator.cs +++ b/src/Configuration/ContainerConfigurator.cs @@ -170,6 +170,17 @@ public ContainerConfigurator WithReBuildSingletonsInChildContainer(bool enabled this.ContainerConfiguration.ReBuildSingletonsInChildContainerEnabled = enabled; return this; } + + /// + /// Enables or disables the check for generic covariance and contravariance during the resolution of generic type collections. + /// + /// True when the feature should be enabled, otherwise false. + /// The container configurator. + public ContainerConfigurator WithVariantGenericTypes(bool enabled = true) + { + this.ContainerConfiguration.VariantGenericTypesEnabled = enabled; + return this; + } /// /// Sets an external expression tree compiler used by the container to compile the generated expressions. diff --git a/src/ContainerContext.cs b/src/ContainerContext.cs index 72070c89..a6b40247 100644 --- a/src/ContainerContext.cs +++ b/src/ContainerContext.cs @@ -13,7 +13,7 @@ public ContainerContext(IContainerContext? parentContext, this.ParentContext = parentContext; this.RootScope = new ResolutionScope(this); this.RegistrationRepository = new RegistrationRepository(containerConfiguration); - this.DecoratorRepository = new DecoratorRepository(); + this.DecoratorRepository = new DecoratorRepository(containerConfiguration); this.ResolutionStrategy = resolutionStrategy; } diff --git a/src/Registration/DecoratorRepository.cs b/src/Registration/DecoratorRepository.cs index 61877ac5..b87a93ac 100644 --- a/src/Registration/DecoratorRepository.cs +++ b/src/Registration/DecoratorRepository.cs @@ -6,10 +6,11 @@ using System; using System.Collections.Generic; using System.Linq; +using Stashbox.Configuration; namespace Stashbox.Registration; -internal class DecoratorRepository : IDecoratorRepository +internal class DecoratorRepository(ContainerConfiguration containerConfiguration) : IDecoratorRepository { private ImmutableTree> repository = ImmutableTree>.Empty; @@ -52,6 +53,9 @@ public IEnumerable> GetRegistrationMappi 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() && diff --git a/src/Registration/RegistrationRepository.cs b/src/Registration/RegistrationRepository.cs index c0dec9d5..34f0538b 100644 --- a/src/Registration/RegistrationRepository.cs +++ b/src/Registration/RegistrationRepository.cs @@ -11,10 +11,9 @@ namespace Stashbox.Registration; -internal class RegistrationRepository : IRegistrationRepository +internal class RegistrationRepository(ContainerConfiguration containerConfiguration) : IRegistrationRepository { private ImmutableTree> serviceRepository = ImmutableTree>.Empty; - private readonly ContainerConfiguration containerConfiguration; private readonly IRegistrationSelectionRule[] filters = [ @@ -42,11 +41,6 @@ internal class RegistrationRepository : IRegistrationRepository RegistrationSelectionRules.MetadataFilter ]; - public RegistrationRepository(ContainerConfiguration containerConfiguration) - { - this.containerConfiguration = containerConfiguration; - } - public bool AddOrUpdateRegistration(ServiceRegistration registration, Type serviceType) { if (registration.Options.IsOn(RegistrationOption.ReplaceExistingRegistrationOnlyIfExists)) @@ -111,7 +105,7 @@ public bool AddOrUpdateRegistration(ServiceRegistration registration, Type servi registration, serviceType, new ImmutableBucket(registration), - this.containerConfiguration.RegistrationBehavior); + containerConfiguration.RegistrationBehavior); } public bool AddOrReMapRegistration(ServiceRegistration registration, Type serviceType) => @@ -152,6 +146,9 @@ public IEnumerable> GetRegistrationMappi 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() && diff --git a/src/ResolutionScope.cs b/src/ResolutionScope.cs index 4914cae7..30f34177 100644 --- a/src/ResolutionScope.cs +++ b/src/ResolutionScope.cs @@ -40,7 +40,7 @@ private object WaitForEvaluation(Type serviceType) var currentTime = (uint)Environment.TickCount; if (MaxWaitTimeInMs <= currentTime - startTime) throw new ResolutionFailedException(serviceType, - $"The service {serviceType} was unavailable after {MaxWaitTimeInMs} ms. " + + message: $"The service {serviceType} was unavailable after {MaxWaitTimeInMs} ms. " + "It's possible that the thread used to construct it crashed by a handled exception." + "This exception is supposed to prevent other caller threads from infinite waiting for service construction."); } diff --git a/src/stashbox.csproj b/src/stashbox.csproj index f5442b7f..25f807cd 100644 --- a/src/stashbox.csproj +++ b/src/stashbox.csproj @@ -97,6 +97,7 @@ + diff --git a/test/IssueTests/16_Extensions_Identity_OptionsMonitor.cs b/test/IssueTests/16_Extensions_Identity_OptionsMonitor.cs new file mode 100644 index 00000000..f82b0ede --- /dev/null +++ b/test/IssueTests/16_Extensions_Identity_OptionsMonitor.cs @@ -0,0 +1,25 @@ +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; +} \ No newline at end of file