diff --git a/src/DryIoc/Container.cs b/src/DryIoc/Container.cs index 15bc4812e..f3069dc7e 100644 --- a/src/DryIoc/Container.cs +++ b/src/DryIoc/Container.cs @@ -8637,9 +8637,12 @@ public static T New(this IResolver resolver, Made.TypedMade 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; @@ -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(); @@ -9513,7 +9516,7 @@ 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); - /// Available in runtime only, provides access to container initiated the request. + /// Available at runtime only, provides an access to container initiated the request. public IContainer Container { get; private set; } /// Request immediate parent. @@ -9521,37 +9524,53 @@ public static Request Create(IContainer container, Type serviceType, internal RequestStack RequestStack; - // mutable because of RequestFlags.AddedToResolutionExpressions - /// Persisted request conditions - 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 /// Input arguments provided with `Resolve` internal Expression[] InputArgExprs; - /// Runtime known resolve factory, otherwise is null - internal Factory Factory => _factoryOrImplType as Factory; + /// Service reuse. + public IReuse Reuse { get; private set; } + + internal object _factoryOrImplType; + + /// Constructor selected by the reflection factory + public ConstructorInfo SelectedConstructor { get; internal set; } /// Resolved factory ID, used to identify applied decorator. public int FactoryID { get; private set; } + /// ID of decorated factory in case of decorator factory type + 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 /// Type of factory: Service, Wrapper, or Decorator. public FactoryType FactoryType { get; private set; } + // mutable because of RequestFlags.AddedToResolutionExpressions + /// Persisted request conditions + public RequestFlags Flags; // todo: @perf combine with the FactoryType or other numeric fields + + /// Number of nested dependencies. Set with each new Push. + public int DependencyDepth; // todo: @perf combine with the DependencyCount or other fields, use the ObjectLayoutInspector to check the layout + + /// The total dependency count + public int DependencyCount; // todo: @perf combine with the DependencyDepth or other fields + /// Combines decorator and public int CombineDecoratorWithDecoratedFactoryID() => FactoryID | (DecoratedFactoryID << 16); - /// Service implementation type if known. - public Type ImplementationType => _factoryOrImplType as Type ?? Factory?.ImplementationType; - - internal object _factoryOrImplType; + /// Runtime known resolve factory, otherwise is null + internal Factory Factory => _factoryOrImplType as Factory; + /// Service implementation type if known. + public Type ImplementationType => _factoryOrImplType as Type ?? (_factoryOrImplType as Factory)?.ImplementationType; + /// Sets the service factory already resolved by the wrapper to save for the future factory resolution public Request WithWrappedServiceFactory(Factory f) { @@ -9559,18 +9578,6 @@ public Request WithWrappedServiceFactory(Factory f) return this; } - /// Service reuse. - public IReuse Reuse { get; private set; } - - /// ID of decorated factory in case of decorator factory type - public int DecoratedFactoryID { get; private set; } // todo: @perf can we remove or combine it with the other fields? - - /// Number of nested dependencies. Set with each new Push. - public int DependencyDepth; // todo: @perf combine with theDependencyCount or other fields, use the ObjectLayoutInspector to check the layout - - /// The total dependency count - 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) @@ -9642,7 +9649,6 @@ public Request Parent /// Compatible required or service type. public Type GetActualServiceType() => _actualServiceType; - private Type _actualServiceType; /// Get the details public ServiceDetails GetServiceDetails() => ServiceTypeOrInfo is ServiceInfo i ? i.Details : ServiceDetails.Default; @@ -9660,7 +9666,7 @@ public Request Parent public int ReuseLifespan => Reuse?.Lifespan ?? 0; /// Known implementation, or otherwise actual service type. - public Type GetKnownImplementationOrServiceType() => _factoryOrImplType as Type ?? Factory?.ImplementationType ?? _actualServiceType; + public Type GetKnownImplementationOrServiceType() => ImplementationType ?? _actualServiceType; private ref Request GetOrPushPooledRequest(RequestStack stack, int indexInStack) { @@ -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); @@ -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; @@ -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; @@ -11830,6 +11839,9 @@ 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); @@ -11837,7 +11849,7 @@ public override Expression CreateExpressionOrDefault(Request request) } 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); } @@ -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) { diff --git a/test/DryIoc.TestRunner/Program.cs b/test/DryIoc.TestRunner/Program.cs index ebfdb26b5..bcf301b4b 100644 --- a/test/DryIoc.TestRunner/Program.cs +++ b/test/DryIoc.TestRunner/Program.cs @@ -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(); @@ -52,6 +55,7 @@ void Run(Func run, string name = null) new ContainerTests(), new OpenGenericsTests(), new DynamicRegistrationsTests(), + new PropertyResolutionTests(), new SelectConstructorWithAllResolvableArgumentTests(), new Issue107_NamedScopesDependingOnResolvedTypes(), new GHIssue378_InconsistentResolutionFailure(), diff --git a/test/DryIoc.UnitTests/PropertyResolutionTests.cs b/test/DryIoc.UnitTests/PropertyResolutionTests.cs index a6eea4e9b..e5cb520cd 100644 --- a/test/DryIoc.UnitTests/PropertyResolutionTests.cs +++ b/test/DryIoc.UnitTests/PropertyResolutionTests.cs @@ -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() { @@ -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(); + container.Register(); + container.Register(); + + var cuerpo = container.Resolve(); + + Assert.IsNotNull(cuerpo.Brain); + } } #region CUT @@ -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 }