Skip to content

Commit

Permalink
Named overrides
Browse files Browse the repository at this point in the history
  • Loading branch information
z4kn4fein committed Aug 16, 2024
1 parent f7ec0ae commit 0f5d910
Show file tree
Hide file tree
Showing 12 changed files with 165 additions and 39 deletions.
2 changes: 1 addition & 1 deletion .version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
5.15.0
5.16.0
4 changes: 2 additions & 2 deletions src/Expressions/ExpressionFactory.Member.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ private static Expression GetMemberExpression(
if (!serviceContext.IsEmpty() || resolutionContext.NullResultAllowed) return serviceContext.ServiceExpression;

var memberType = member is PropertyInfo ? "property" : "field";
throw new ResolutionFailedException(memberTypeInfo.ParentType, memberTypeInfo.DependencyName,
$"Unresolvable {memberType}: ({memberTypeInfo.Type.FullName}){memberTypeInfo.ParameterOrMemberName}.");
throw new ResolutionFailedException(memberTypeInfo.ParentType, null,
$"Unresolvable {memberType}: ({memberTypeInfo.Type.FullName}){memberTypeInfo.ParameterOrMemberName}{(memberTypeInfo.DependencyName != null ? $" with name '{memberTypeInfo.DependencyName}'" : string.Empty)}.");

}
}
6 changes: 4 additions & 2 deletions src/IStashboxContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ public interface IStashboxContainer : IDependencyRegistrator, IDependencyResolve
/// Registers an <see cref="IResolver"/>.
/// </summary>
/// <param name="resolver">The resolver implementation.</param>
void RegisterResolver(IResolver resolver);
/// <returns>The container itself.</returns>
IStashboxContainer RegisterResolver(IResolver resolver);

/// <summary>
/// Creates a child container.
Expand Down Expand Up @@ -69,7 +70,8 @@ public interface IStashboxContainer : IDependencyRegistrator, IDependencyResolve
/// Configures the container.
/// </summary>
/// <param name="config">The action delegate which will configure the container.</param>
void Configure(Action<ContainerConfigurator> config);
/// <returns>The container itself.</returns>
IStashboxContainer Configure(Action<ContainerConfigurator> config);

/// <summary>
/// Validates the current state of the container.
Expand Down
4 changes: 2 additions & 2 deletions src/Multitenant/TenantDistributor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public void ConfigureTenant(object tenantId, Action<IStashboxContainer> tenantCo
/// <inheritdoc />
public IEnumerable<ReadOnlyKeyValue<object, IStashboxContainer>> ChildContainers => rootContainer.ChildContainers;
/// <inheritdoc />
public void RegisterResolver(IResolver resolver) => rootContainer.RegisterResolver(resolver);
public IStashboxContainer RegisterResolver(IResolver resolver) => rootContainer.RegisterResolver(resolver);
/// <inheritdoc />
public IStashboxContainer CreateChildContainer(Action<ContainerConfigurator>? config = null, bool attachToParent = true) => rootContainer.CreateChildContainer(config, attachToParent);
/// <inheritdoc />
Expand All @@ -51,7 +51,7 @@ public IStashboxContainer CreateChildContainer(object identifier, Action<IStashb
/// <inheritdoc />
public bool IsRegistered(Type typeFrom, object? name = null) => rootContainer.IsRegistered(typeFrom, name);
/// <inheritdoc />
public void Configure(Action<ContainerConfigurator> config) => rootContainer.Configure(config);
public IStashboxContainer Configure(Action<ContainerConfigurator> config) => rootContainer.Configure(config);
/// <inheritdoc />
public void Validate() => rootContainer.Validate();
/// <inheritdoc />
Expand Down
49 changes: 49 additions & 0 deletions src/Override.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using Stashbox.Utils;

namespace Stashbox;

/// <summary>
/// Represents a specialized resolution override.
/// </summary>
public class Override
{
private Override(object instance, object? dependencyName, Type type)
{
this.Instance = instance;
this.DependencyName = dependencyName;
this.Type = type;
}

/// <summary>
/// The name of the override used for named resolution.
/// </summary>
public object? DependencyName { get; }

/// <summary>
/// The instance used as dependency override.
/// </summary>
public object Instance { get; }

/// <summary>
/// The type of the dependency override.
/// </summary>
public Type Type { get; }

/// <summary>
/// Creates a new <see cref="Override"/> instance.
/// </summary>
/// <param name="instance">The instance used as dependency override.</param>
/// <param name="name">The optional name of the instance used as dependency override.</param>
/// <returns>The constructed override instance.</returns>
public static Override Of<TType>(TType instance, object? name = null) where TType: notnull => new(instance, name, TypeCache<TType>.Type);

/// <summary>
/// Creates a new <see cref="Override"/> instance.
/// </summary>
/// <param name="type">The type of the override.</param>
/// <param name="instance">The instance used as dependency override.</param>
/// <param name="name">The optional name of the instance used as dependency override.</param>
/// <returns>The constructed override instance.</returns>
public static Override Of(Type type, object instance, object? name = null) => new(instance, name, type);
}
46 changes: 30 additions & 16 deletions src/Resolution/ResolutionContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ internal class AutoLifetimeTracker
internal readonly PerRequestConfiguration RequestConfiguration;
internal readonly Utils.Data.Stack<int> CircularDependencyBarrier;
internal readonly Tree<Func<IResolutionScope, IRequestContext, object>> FactoryCache;
internal readonly HashTree<object, ConstantExpression>? ExpressionOverrides;
internal readonly HashTree<Type, ExpandableArray<Override>>? ExpressionOverrides;
internal readonly ExpandableArray<Expression> SingleInstructions;
internal readonly Tree<ParameterExpression> DefinedVariables;
internal readonly ExpandableArray<Pair<bool, ParameterExpression>[]> ParameterExpressions;
Expand Down Expand Up @@ -87,7 +87,7 @@ private ResolutionContext(IEnumerable<object> initialScopeNames,
bool nullResultAllowed,
bool isValidationContext,
object[]? dependencyOverrides,
ImmutableTree<object, object>? knownInstances,
ImmutableTree<Type, ImmutableBucket<Override>>? knownInstances,
ParameterExpression[]? initialParameters)
{
this.RequestConfiguration = new PerRequestConfiguration();
Expand Down Expand Up @@ -132,7 +132,7 @@ private ResolutionContext(PerRequestConfiguration perRequestConfiguration,
ParameterExpression requestContextParameter,
IContainerContext currentContainerContext,
IContainerContext requestInitiatorContainerContext,
HashTree<object, ConstantExpression>? expressionOverrides,
HashTree<Type, ExpandableArray<Override>>? expressionOverrides,
ExpandableArray<Pair<bool, ParameterExpression>[]> parameterExpressions,
RequestContext requestContext,
AutoLifetimeTracker? autoLifetimeTracker,
Expand Down Expand Up @@ -208,7 +208,7 @@ internal static ResolutionContext BeginTopLevelContext(
bool isRequestedFromRoot,
bool nullResultAllowed,
object[]? dependencyOverrides = null,
ImmutableTree<object, object>? knownInstances = null,
ImmutableTree<Type, ImmutableBucket<Override>>? knownInstances = null,
ParameterExpression[]? initialParameters = null) =>
new(initialScopeNames,
currentContainerContext,
Expand Down Expand Up @@ -277,28 +277,42 @@ internal ResolutionContext BeginAutoLifetimeTrackingContext(AutoLifetimeTracker
singleInstructions: new ExpandableArray<Expression>(),
cachedExpressions: new Tree<Expression>());

private static HashTree<object, ConstantExpression> ProcessDependencyOverrides(object[]? dependencyOverrides, ImmutableTree<object, object>? knownInstances)
private static HashTree<Type, ExpandableArray<Override>> ProcessDependencyOverrides(object[]? dependencyOverrides, ImmutableTree<Type, ImmutableBucket<Override>>? knownInstances)
{
var result = new HashTree<object, ConstantExpression>();
var overrides = new HashTree<Type, ExpandableArray<Override>>();

if (knownInstances is { IsEmpty: false })
foreach (var lateKnownInstance in knownInstances.Walk())
result.Add(lateKnownInstance.Key, lateKnownInstance.Value.AsConstant());
overrides.Add(lateKnownInstance.Key, new ExpandableArray<Override>(lateKnownInstance.Value.Repository));

if (dependencyOverrides == null) return result;
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();
var expression = dependencyOverride.AsConstant();

result.Add(type, expression);

foreach (var baseType in type.GetRegisterableInterfaceTypes().Concat(type.GetRegisterableBaseTypes()))
result.Add(baseType, expression);
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<Override>(new [] {expOverride}));
}
}

return result;
return overrides;
}

private ResolutionContext Clone(
Expand Down Expand Up @@ -345,4 +359,4 @@ private ResolutionContext Clone(
unknownTypeCheckDisabled ?? this.UnknownTypeCheckDisabled,
shouldFallBackToRequestInitiator ?? this.shouldFallBackToRequestInitiatorContext,
this.IsValidationContext);
}
}
24 changes: 18 additions & 6 deletions src/Resolution/ResolutionStrategy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,22 +80,30 @@ public ServiceContext BuildExpressionForType(ResolutionContext resolutionContext
resolutionContext.BeginDecoratingContext(typeInformation.Type, decorators), typeInformation, decorators).AsServiceContext();
}

var exprOverride = resolutionContext.ExpressionOverrides?.GetOrDefault(typeInformation.DependencyName ?? typeInformation.Type);
var exprOverride = resolutionContext.ExpressionOverrides?.GetOrDefault(typeInformation.Type);
if (exprOverride != null)
return exprOverride.AsServiceContext();
{
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 (typeInformation.IsDependency && registration != null && isResolutionCallRequired)
if (isResolutionCallRequired && typeInformation.IsDependency && registration != null)
return resolutionContext.CurrentScopeParameter
.ConvertTo(TypeCache<IDependencyResolver>.Type)
.CallMethod(Constants.ResolveMethod,
typeInformation.Type.AsConstant(), typeInformation.DependencyName.AsConstant(),
resolutionContext.ExpressionOverrides?.Walk().Select(c => c.Value).ToArray().AsConstant() ?? TypeCache.EmptyArray<object>().AsConstant(),
resolutionContext.ExpressionOverrides?.Walk().SelectMany(c =>
c.Select(ov => ov)).ToArray().AsConstant() ?? TypeCache.EmptyArray<object>().AsConstant(),
resolutionContext.ResolutionBehavior.AsConstant())
.ConvertTo(typeInformation.Type)
.AsServiceContext(registration);
Expand Down Expand Up @@ -177,8 +185,12 @@ public bool IsTypeResolvable(ResolutionContext resolutionContext, TypeInformatio
this.IsWrappedTypeRegistered(typeInformation, resolutionContext))
return true;

var exprOverride = resolutionContext.ExpressionOverrides?.GetOrDefault(typeInformation.DependencyName ?? typeInformation.Type);
return exprOverride != null || this.CanLookupService(typeInformation, resolutionContext);
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) =>
Expand Down
12 changes: 8 additions & 4 deletions src/ResolutionScope.Resolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using Stashbox.Utils.Data;

namespace Stashbox;

Expand Down Expand Up @@ -176,7 +177,7 @@ public IDependencyResolver BeginScope(object? name = null, bool attachToParent =
var scope = new ResolutionScope(this, this.containerContext, this.delegateCacheProvider, name, attachToParent);

if (attachToParent)
Swap.SwapValue(ref this.childScopes, (t1, t2, _, _, childStore) =>
Swap.SwapValue(ref this.childScopes, (t1, _, _, _, childStore) =>
childStore.AddOrSkip(t1),
scope, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder);

Expand All @@ -190,10 +191,13 @@ public void PutInstanceInScope(Type typeFrom, object instance, bool withoutDispo

Shield.EnsureNotNull(typeFrom, nameof(typeFrom));
Shield.EnsureNotNull(instance, nameof(instance));

var @override = Override.Of(typeFrom, instance, name);

var key = name ?? typeFrom;
Swap.SwapValue(ref this.lateKnownInstances, (t1, t2, _, _, instances) =>
instances.AddOrUpdate(t1, t2, false), key, instance, Constants.DelegatePlaceholder, Constants.DelegatePlaceholder);
Swap.SwapValue(ref this.lateKnownInstances, (t1, t2, t3, _, instances) =>
instances.AddOrUpdate(t1, t2, false, (o, _) =>
o.Add(t3)), typeFrom, new ImmutableBucket<Override>([@override]),
@override, Constants.DelegatePlaceholder);

if (!withoutDisposalTracking && instance is IDisposable disposable)
this.AddDisposableTracking(disposable);
Expand Down
2 changes: 1 addition & 1 deletion src/ResolutionScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ private object WaitForEvaluation(Type serviceType)
private readonly DelegateCacheProvider delegateCacheProvider;
private readonly ResolutionScope? parentScope;
private ImmutableTree<ScopedEvaluator> scopedInstances = ImmutableTree<ScopedEvaluator>.Empty;
private ImmutableTree<object, object> lateKnownInstances = ImmutableTree<object, object>.Empty;
private ImmutableTree<Type, ImmutableBucket<Override>> lateKnownInstances = ImmutableTree<Type, ImmutableBucket<Override>>.Empty;
private ImmutableRefTree<IResolutionScope> childScopes = ImmutableRefTree<IResolutionScope>.Empty;


Expand Down
6 changes: 4 additions & 2 deletions src/StashboxContainer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,13 @@ private StashboxContainer(StashboxContainer? parentContainer, IResolutionStrateg
this.childContainerStore.ChildContainers.Walk();

/// <inheritdoc />
public void RegisterResolver(IResolver resolver)
public IStashboxContainer RegisterResolver(IResolver resolver)
{
this.ThrowIfDisposed();
Shield.EnsureNotNull(resolver, nameof(resolver));

this.ContainerContext.ResolutionStrategy.RegisterResolver(resolver);
return this;
}

/// <inheritdoc />
Expand Down Expand Up @@ -158,7 +159,7 @@ public IStashboxContainer CreateChildContainer(object identifier, Action<IStashb
this.childContainerStore.ChildContainers.GetOrDefaultByValue(identifier);

/// <inheritdoc />
public void Configure(Action<ContainerConfigurator> config)
public IStashboxContainer Configure(Action<ContainerConfigurator> config)
{
this.ThrowIfDisposed();
Shield.EnsureNotNull(config, "The config parameter cannot be null!");
Expand All @@ -168,6 +169,7 @@ public void Configure(Action<ContainerConfigurator> config)
.Invoke(this.containerConfigurator.ContainerConfiguration);

this.ContainerContext.RootScope.InvalidateDelegateCache();
return this;
}

/// <inheritdoc />
Expand Down
4 changes: 2 additions & 2 deletions test/IssueTests/66_Named_PutInstanceInScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public void Ensure_Named_Scoped_Instance_Working()

scope.PutInstanceInScope(a1);
scope.PutInstanceInScope(a2, name: "a");
scope.PutInstanceInScope(a2);
scope.PutInstanceInScope(a3);

Assert.Same(a2, scope.Resolve<A>("a"));
}
Expand All @@ -31,7 +31,7 @@ public void Ensure_Named_Scoped_Instance_Working()

scope.PutInstanceInScope(a1, name: "a1");
scope.PutInstanceInScope(a2, name: "a2");
scope.PutInstanceInScope(a2, name: "a3");
scope.PutInstanceInScope(a3, name: "a3");

Assert.Same(a2, scope.Resolve<A>("a2"));
}
Expand Down
Loading

0 comments on commit 0f5d910

Please sign in to comment.