Skip to content

Commit

Permalink
exposing the Request.SelectedConstuctor to support user-level checkin…
Browse files Browse the repository at this point in the history
…g constuctor when injecting the properties for #563
  • Loading branch information
dadhi committed Apr 14, 2023
1 parent e244b33 commit 87792b3
Show file tree
Hide file tree
Showing 3 changed files with 126 additions and 41 deletions.
94 changes: 54 additions & 40 deletions src/DryIoc/Container.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8637,9 +8637,12 @@ public static T New<T>(this IResolver resolver, Made.TypedMade<T> made,
public static Expression CreateResolutionExpression(Request request,
bool openResolutionScope = false, bool asResolutionCall = false)
{
if (request.Rules.DependencyResolutionCallExprs != null &&
request.Factory != null && !request.Factory.HasRuntimeState)
PopulateDependencyResolutionCallExpressions(request);
if (request.Rules.DependencyResolutionCallExprs != null)
{
var f = request.Factory;
if (f != null && !f.HasRuntimeState)
PopulateDependencyResolutionCallExpressions(request);
}

var container = request.Container;
var serviceType = request.ServiceType;
Expand Down Expand Up @@ -9466,18 +9469,18 @@ public static Request Create(IContainer container, ServiceInfo serviceInfo,

// inherit some flags and service details from parent (if any)
preResolveParent = preResolveParent ?? Empty;
if (!preResolveParent.IsEmpty)
if (preResolveParent.IsEmpty)
flags |= preResolveParent.Flags; //inherits the OpensResolutionScope flag in case of Request.EmptyOpensResolutionScope
else
{
var parentServiceInfo = preResolveParent.ServiceTypeOrInfo;
if (parentServiceInfo is ServiceInfo ps && ps.Details != null && ps.Details != ServiceDetails.Default)
serviceInfo = serviceInfo.InheritInfoFromDependencyOwner(ps.ServiceType, ps.Details, container, preResolveParent.FactoryType);

flags |= preResolveParent.Flags & InheritedFlags;
flags |= preResolveParent.Flags & InheritedFlags; // filter out flags which are not inherited
}
else
flags |= preResolveParent.Flags; //inherits the OpensResolutionScope flag

var inputArgExprs = inputArgs?.Map(a => Constant(a)); // todo: @check what happens if `a == null`, does the `object` type for is fine
var inputArgExprs = inputArgs?.Map(static a => Constant(a)); // todo: @check what happens if `a == null`, does the `object` type for is fine

// we are re-starting the dependency depth count from `1`
var stack = RequestStack.Create();
Expand Down Expand Up @@ -9513,64 +9516,68 @@ public static Request Create(IContainer container, Type serviceType,
Request preResolveParent = null, RequestFlags flags = default, object[] inputArgs = null) =>
Create(container, ServiceInfo.Of(serviceType, requiredServiceType, ifUnresolved, serviceKey), preResolveParent, flags, inputArgs);

/// <summary>Available in runtime only, provides access to container initiated the request.</summary>
/// <summary>Available at runtime only, provides an access to container initiated the request.</summary>
public IContainer Container { get; private set; }

/// <summary>Request immediate parent.</summary>
public Request DirectParent;

internal RequestStack RequestStack;

// mutable because of RequestFlags.AddedToResolutionExpressions
/// <summary>Persisted request conditions</summary>
public RequestFlags Flags; // todo: @perf combine with the FactoryType or other numeric fields

// todo: @perf should we unpack the info to the ServiceType and Details (or at least the Details), because we are accessing them via Virtual Calls (and it is a lot)
// The field is mutable so that the ServiceKey or IfUnresolved can be changed in place.
// todo: @perf should we unpack the info to the ServiceType and Details (or at least the Details), because we are accessing them via virtual calls (and there are a lot)
// The field is mutable so that the service details ServiceKey or IfUnresolved can be changed in place.
internal object ServiceTypeOrInfo; // the Type or the ServiceInfo
private Type _actualServiceType; // the actual service type may in fact be the same as ServiceTypeOrInfo

/// <summary>Input arguments provided with `Resolve`</summary>
internal Expression[] InputArgExprs;

/// <summary>Runtime known resolve factory, otherwise is <c>null</c></summary>
internal Factory Factory => _factoryOrImplType as Factory;
/// <summary>Service reuse.</summary>
public IReuse Reuse { get; private set; }

internal object _factoryOrImplType;

/// <summary>Constructor selected by the reflection factory</summary>
public ConstructorInfo SelectedConstructor { get; internal set; }

/// <summary>Resolved factory ID, used to identify applied decorator.</summary>
public int FactoryID { get; private set; }

/// <summary>ID of decorated factory in case of decorator factory type</summary>
public int DecoratedFactoryID { get; private set; } // todo: @perf can we remove or combine it with the other fields?

// based on the parent(s) and current request FactoryID
private int _hashCode; // todo: @perf do we need to calculate and store the hash code if it is not used

/// <summary>Type of factory: Service, Wrapper, or Decorator.</summary>
public FactoryType FactoryType { get; private set; }

// mutable because of RequestFlags.AddedToResolutionExpressions
/// <summary>Persisted request conditions</summary>
public RequestFlags Flags; // todo: @perf combine with the FactoryType or other numeric fields

/// <summary>Number of nested dependencies. Set with each new Push.</summary>
public int DependencyDepth; // todo: @perf combine with the DependencyCount or other fields, use the ObjectLayoutInspector to check the layout

/// <summary>The total dependency count</summary>
public int DependencyCount; // todo: @perf combine with the DependencyDepth or other fields

/// <summary>Combines decorator and <see cref="DecoratedFactoryID"/></summary>
public int CombineDecoratorWithDecoratedFactoryID() => FactoryID | (DecoratedFactoryID << 16);

/// <summary>Service implementation type if known.</summary>
public Type ImplementationType => _factoryOrImplType as Type ?? Factory?.ImplementationType;

internal object _factoryOrImplType;
/// <summary>Runtime known resolve factory, otherwise is <c>null</c></summary>
internal Factory Factory => _factoryOrImplType as Factory;

/// <summary>Service implementation type if known.</summary>
public Type ImplementationType => _factoryOrImplType as Type ?? (_factoryOrImplType as Factory)?.ImplementationType;

/// <summary>Sets the service factory already resolved by the wrapper to save for the future factory resolution</summary>
public Request WithWrappedServiceFactory(Factory f)
{
_factoryOrImplType = f;
return this;
}

/// <summary>Service reuse.</summary>
public IReuse Reuse { get; private set; }

/// <summary>ID of decorated factory in case of decorator factory type</summary>
public int DecoratedFactoryID { get; private set; } // todo: @perf can we remove or combine it with the other fields?

/// <summary>Number of nested dependencies. Set with each new Push.</summary>
public int DependencyDepth; // todo: @perf combine with theDependencyCount or other fields, use the ObjectLayoutInspector to check the layout

/// <summary>The total dependency count</summary>
public int DependencyCount; // todo: @perf combine with the DependencyDepth or other fields

internal void DecreaseTrackedDependencyCountForParents(int dependencyCount)
{
for (var p = DirectParent; !p.IsEmpty; p = p.DirectParent)
Expand Down Expand Up @@ -9642,7 +9649,6 @@ public Request Parent

/// <summary>Compatible required or service type.</summary>
public Type GetActualServiceType() => _actualServiceType;
private Type _actualServiceType;

/// <summary>Get the details</summary>
public ServiceDetails GetServiceDetails() => ServiceTypeOrInfo is ServiceInfo i ? i.Details : ServiceDetails.Default;
Expand All @@ -9660,7 +9666,7 @@ public Request Parent
public int ReuseLifespan => Reuse?.Lifespan ?? 0;

/// <summary>Known implementation, or otherwise actual service type.</summary>
public Type GetKnownImplementationOrServiceType() => _factoryOrImplType as Type ?? Factory?.ImplementationType ?? _actualServiceType;
public Type GetKnownImplementationOrServiceType() => ImplementationType ?? _actualServiceType;

private ref Request GetOrPushPooledRequest(RequestStack stack, int indexInStack)
{
Expand Down Expand Up @@ -10120,8 +10126,9 @@ public StringBuilder PrintCurrent(StringBuilder s = null)

s.Append(ServiceTypeOrInfo is ParameterInfo pi ? ParameterServiceInfo.Of(pi) : ServiceTypeOrInfo);

if (Factory != null && Factory is ReflectionFactory == false)
s.Append(' ').Append(Factory.GetType().Name).Append(' ');
var f = Factory;
if (f != null && f is ReflectionFactory == false)
s.Append(' ').Append(f.GetType().Name).Append(' ');

if (FactoryID != 0)
s.Append(" FactoryId=").Append(FactoryID);
Expand Down Expand Up @@ -10250,6 +10257,7 @@ private void SetServiceInfo(IContainer container, Request parent, int dependency
Flags = flags;
// resets the factory info:
_factoryOrImplType = null;
SelectedConstructor = null;
Reuse = null;
FactoryID = 0;
DecoratedFactoryID = 0;
Expand All @@ -10260,6 +10268,7 @@ private void SetServiceInfo(IContainer container, Request parent, int dependency
private void SetResolvedFactory(object factoryOrImplType, int factoryID, FactoryType factoryType, IReuse reuse, int decoratedFactoryID)
{
_factoryOrImplType = factoryOrImplType;
SelectedConstructor = null;
FactoryID = factoryID;
FactoryType = factoryType;
Reuse = reuse;
Expand Down Expand Up @@ -11830,14 +11839,17 @@ public override Expression CreateExpressionOrDefault(Request request)
var ctorOrMember = factoryMethod.ConstructorOrMethodOrMember;
if (factoryMethod.ResolvedParameterExpressions != null)
{
ctor = (ConstructorInfo)ctorOrMember;
request.SelectedConstructor = ctor;

if (rules.UsedForValidation)
{
TryGetMemberAssignments(ref failedToGetMember, request, container, rules);
return request.GetActualServiceType().GetDefaultValueExpression();
}

var assignements = TryGetMemberAssignments(ref failedToGetMember, request, container, rules);
var newExpr = New((ConstructorInfo)ctorOrMember, factoryMethod.ResolvedParameterExpressions);
var newExpr = New(ctor, factoryMethod.ResolvedParameterExpressions);
return failedToGetMember ? null : assignements == null ? newExpr : (Expression)MemberInit(newExpr, assignements);
}

Expand All @@ -11846,10 +11858,12 @@ public override Expression CreateExpressionOrDefault(Request request)
return ConvertExpressionIfNeeded(
ctorOrMember is PropertyInfo p ? Property(factoryExpr, p) : Field(factoryExpr, (FieldInfo)ctorOrMember), request, ctorOrMember);

ctor = ctorOrMember as ConstructorInfo;
method = ctorOrMember as MethodInfo;
ctor = ctorOrMethod as ConstructorInfo;
method = ctorOrMethod as MethodInfo;
}

request.SelectedConstructor = ctor;

var parameters = ctorOrMethod.GetParameters();
if (parameters.Length == 0)
{
Expand Down
4 changes: 4 additions & 0 deletions test/DryIoc.TestRunner/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ public static void Main()
{
RunAllTests();

// new PropertyResolutionTests().Run();

// new GHIssue391_Deadlock_during_Resolve().Run();
// new GHIssue559_Possible_inconsistent_behaviour().Run();
// new GHIssue557_WithFactorySelector_allows_to_Resolve_the_keyed_service_as_non_keyed().Run();
// new GHIssue555_ConcreteTypeDynamicRegistrations_is_not_working_with_MicrosoftDependencyInjectionRules().Run();
Expand Down Expand Up @@ -52,6 +55,7 @@ void Run(Func<int> run, string name = null)
new ContainerTests(),
new OpenGenericsTests(),
new DynamicRegistrationsTests(),
new PropertyResolutionTests(),
new SelectConstructorWithAllResolvableArgumentTests(),
new Issue107_NamedScopesDependingOnResolvedTypes(),
new GHIssue378_InconsistentResolutionFailure(),
Expand Down
69 changes: 68 additions & 1 deletion test/DryIoc.UnitTests/PropertyResolutionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
namespace DryIoc.UnitTests
{
[TestFixture]
public class PropertyResolutionTests
public class PropertyResolutionTests : ITest
{
public int Run()
{
Can_access_the_constructor_when_resolving_the_property();
return 1;
}

[Test]
public void Resolving_unregistered_property_should_NOT_throw_and_should_preserve_original_property_value()
{
Expand Down Expand Up @@ -162,6 +168,49 @@ private static PropertyOrFieldServiceInfo GetImportedPropertiesAndFields(MemberI
return import == null ? null : PropertyOrFieldServiceInfo.Of(m)
.WithDetails(ServiceDetails.Of(import.ContractType, import.ContractName));
}

[Test]
public void Can_access_the_constructor_when_resolving_the_property()
{
var container = new Container(r => r.With(
factoryMethod: FactoryMethod.ConstructorWithResolvableArguments,
propertiesAndFields: PropertiesAndFields.All(
withFields: false,
serviceInfo: (m, req) =>
{
var import = (ImportAttribute)m.GetAttributes(typeof(ImportAttribute)).FirstOrDefault();
if (import == null)
return null;

// if the used constructor already contains (the injected) parameter of the same type as the property,
// then we skip the property injection.
// In C# 11 (.NET 7) it may be used to determine the constructor.SetsRequiredMembers to avoid injection of the required properties, #563
var ctor = req.SelectedConstructor;
if (ctor != null)
{
var ctorParams = ctor.GetParameters();
if (ctorParams.Length != 0)
{
var propType = m.GetReturnTypeOrDefault();
foreach (var p in ctorParams)
{
if (p.ParameterType == propType)
return null;
}
}
}

return PropertyOrFieldServiceInfo.Of(m).WithDetails(ServiceDetails.Of(import.ContractType, import.ContractName));
})));

container.Register<Cuerpo>();
container.Register<Guts>();
container.Register<Brain>();

var cuerpo = container.Resolve<Cuerpo>();

Assert.IsNotNull(cuerpo.Brain);
}
}

#region CUT
Expand Down Expand Up @@ -242,5 +291,23 @@ public class Brain
{
}

public class Cuerpo
{
[Import]
public Brain Brain { get; set; }

public readonly Guts Guts;
public Cuerpo(Guts guts)
{
Guts = guts;
}

public Cuerpo(Guts guts, Brain brain)
{
Guts = guts;
Brain = brain;
}
}

#endregion
}

0 comments on commit 87792b3

Please sign in to comment.