Skip to content

Commit

Permalink
Add option to turn off generic covariance and contravariance check
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed Jul 25, 2024
1 parent dfd7e75 commit 23523be
Show file tree
Hide file tree
Showing 20 changed files with 180 additions and 26 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.14.1
5.15.0
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
6 changes: 5 additions & 1 deletion docs/docs/advanced/generics.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,8 @@ Despite the fact that only `IEventHandler<IGeneralEvent>` implementations were r
If we request `IEventHandler<IUpdatedEvent>`, only `UpdatedEventHandler` would be returned, because `IGeneralEvent` is less derived, so `IEventHandler<IGeneralEvent>` implementations are not fit into `IEventHandler<IUpdatedEvent>` collections.

</TabItem>
</Tabs>
</Tabs>

:::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).
:::
16 changes: 16 additions & 0 deletions docs/docs/configuration/container-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,22 @@ new StashboxContainer(options => options
</div>
</CodeDescPanel>

## Generic variance
<CodeDescPanel>
<div>

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_.

</div>
<div>

```cs
new StashboxContainer(options => options
.WithVariantGenericTypes());
```

</div>
</CodeDescPanel>

## Conventional resolution
<CodeDescPanel>
Expand Down
6 changes: 3 additions & 3 deletions docs/docs/getting-started/introduction.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,23 @@ 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
```

</TabItem>
<TabItem value="dotnet CLI" label="dotnet CLI">

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
```

</TabItem>
<TabItem value="PackageReference" label="PackageReference">

You can add the package into the package references of your `.csproj`:
```xml
<PackageReference Include="Stashbox" Version="5.14.1" />
<PackageReference Include="Stashbox" Version="5.15.0" />
```

</TabItem>
Expand Down
2 changes: 1 addition & 1 deletion docs/docs/getting-started/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion docs/src/pages/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function HomepageHeader() {

<div className={styles.installContainer}>
<div className={styles.install}>
<pre><span className={styles.command_start}>{'$'} </span><span className={styles.command}>dotnet</span><span> add package Stashbox</span><span className={styles.options}> --version</span> 5.14.1<span className={styles.cursor}></span></pre>
<pre><span className={styles.command_start}>{'$'} </span><span className={styles.command}>dotnet</span><span> add package Stashbox</span><span className={styles.options}> --version</span> 5.15.0<span className={styles.cursor}></span></pre>
</div>
</div>

Expand Down
5 changes: 3 additions & 2 deletions sandbox/stashbox.benchmarks/EnumerableBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
extern alias from_nuget;
extern alias from_project;
using System.Collections.Generic;
using BenchmarkDotNet.Attributes;

namespace Stashbox.Benchmarks
Expand Down Expand Up @@ -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<IA>));
}

[Benchmark]
public object New()
{
return this.newContainer.ResolveAll(typeof(IA));
return this.newContainer.Resolve(typeof(IEnumerable<IA>));
}

interface IA { }
Expand Down
3 changes: 2 additions & 1 deletion sandbox/stashbox.benchmarks/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
10 changes: 6 additions & 4 deletions sandbox/stashbox.benchmarks/Stashbox.Benchmarks.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net7.0</TargetFramework>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Stashbox.Benchmarks</RootNamespace>
<AssemblyName>Stashbox.Benchmarks</AssemblyName>
<Configurations>Debug;Release;Benchmark</Configurations>
<LangVersion>latest</LangVersion>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>../../sn.snk</AssemblyOriginatorKeyFile>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Benchmark|AnyCPU'">
Expand All @@ -15,12 +17,12 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.5" />
<PackageReference Include="Stashbox" Version="5.9.1" />
<PackageReference Include="Stashbox" Version="5.14.1" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net7.0'">
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<Reference Include="Stashbox.Benchmark">
<HintPath>..\..\src\bin\Benchmark\net7.0\Stashbox.Benchmark.dll</HintPath>
<HintPath>..\..\src\bin\Benchmark\net8.0\Stashbox.Benchmark.dll</HintPath>
<Aliases>from_project</Aliases>
<Private>true</Private>
</Reference>
Expand Down
79 changes: 79 additions & 0 deletions sandbox/stashbox.benchmarks/TreeBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -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<Type, string> imDict;

private ImmutableTree<Type, string> imTree;

[Params(10, 100, 1000)]
public int Count;

[GlobalSetup]
public void Setup()
{
imDict = ImmutableDictionary<Type, string>.Empty;
imTree = ImmutableTree<Type, string>.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<Type, string>.Empty;

foreach (var key in keys)
{
imDictToAdd = imDictToAdd.Add(key, "value");
}
return imDictToAdd;
}

[Benchmark]
public object ImmutableTree_AddOrUpdate()
{
var imTreeToAdd = ImmutableTree<Type, string>.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);
}
}
}
8 changes: 8 additions & 0 deletions src/Configuration/ContainerConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,11 @@ public class ContainerConfiguration
/// </summary>
public bool LifetimeValidationEnabled { get; internal set; }

/// <summary>
/// When it's true, the container checks for generic covariance and contravariance during the resolution of generic type collections.
/// </summary>
public bool VariantGenericTypesEnabled { get; internal set; }

internal object? UniversalName { get; set; }

internal ExpandableArray<Type>? AdditionalDependencyNameAttributeTypes { get; set; }
Expand All @@ -117,6 +122,7 @@ private ContainerConfiguration(bool trackTransientsForDisposalEnabled,
bool treatingParameterAndMemberNameAsDependencyNameEnabled,
bool namedDependencyResolutionForUnNamedRequestsEnabled,
bool reBuildSingletonsInChildContainerEnabled,
bool variantGenericTypesEnabled,
Rules.AutoMemberInjectionRules autoMemberInjectionRule,
Func<IEnumerable<ConstructorInfo>, IEnumerable<ConstructorInfo>> constructorSelectionRule,
Action<UnknownRegistrationConfigurator>? unknownTypeConfigurator,
Expand All @@ -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;
Expand All @@ -159,6 +166,7 @@ internal ContainerConfiguration Clone() =>
this.TreatingParameterAndMemberNameAsDependencyNameEnabled,
this.NamedDependencyResolutionForUnNamedRequestsEnabled,
this.ReBuildSingletonsInChildContainerEnabled,
this.VariantGenericTypesEnabled,
this.AutoMemberInjectionRule,
this.ConstructorSelectionRule,
this.UnknownTypeConfigurator,
Expand Down
11 changes: 11 additions & 0 deletions src/Configuration/ContainerConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,17 @@ public ContainerConfigurator WithReBuildSingletonsInChildContainer(bool enabled
this.ContainerConfiguration.ReBuildSingletonsInChildContainerEnabled = enabled;
return this;
}

/// <summary>
/// Enables or disables the check for generic covariance and contravariance during the resolution of generic type collections.
/// </summary>
/// <param name="enabled">True when the feature should be enabled, otherwise false.</param>
/// <returns>The container configurator.</returns>
public ContainerConfigurator WithVariantGenericTypes(bool enabled = true)
{
this.ContainerConfiguration.VariantGenericTypesEnabled = enabled;
return this;
}

/// <summary>
/// Sets an external expression tree compiler used by the container to compile the generated expressions.
Expand Down
2 changes: 1 addition & 1 deletion src/ContainerContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
6 changes: 5 additions & 1 deletion src/Registration/DecoratorRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Type, ImmutableBucket<Type, ServiceRegistration>> repository = ImmutableTree<Type, ImmutableBucket<Type, ServiceRegistration>>.Empty;

Expand Down Expand Up @@ -52,6 +53,9 @@ public IEnumerable<KeyValuePair<Type, ServiceRegistration>> 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() &&
Expand Down
13 changes: 5 additions & 8 deletions src/Registration/RegistrationRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@

namespace Stashbox.Registration;

internal class RegistrationRepository : IRegistrationRepository
internal class RegistrationRepository(ContainerConfiguration containerConfiguration) : IRegistrationRepository
{
private ImmutableTree<Type, ImmutableBucket<ServiceRegistration>> serviceRepository = ImmutableTree<Type, ImmutableBucket<ServiceRegistration>>.Empty;
private readonly ContainerConfiguration containerConfiguration;

private readonly IRegistrationSelectionRule[] filters =
[
Expand Down Expand Up @@ -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))
Expand Down Expand Up @@ -111,7 +105,7 @@ public bool AddOrUpdateRegistration(ServiceRegistration registration, Type servi
registration,
serviceType,
new ImmutableBucket<ServiceRegistration>(registration),
this.containerConfiguration.RegistrationBehavior);
containerConfiguration.RegistrationBehavior);
}

public bool AddOrReMapRegistration(ServiceRegistration registration, Type serviceType) =>
Expand Down Expand Up @@ -152,6 +146,9 @@ public IEnumerable<KeyValuePair<Type, ServiceRegistration>> 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() &&
Expand Down
2 changes: 1 addition & 1 deletion src/ResolutionScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.");
}
Expand Down
Loading

0 comments on commit 23523be

Please sign in to comment.