Repository: autofac/Autofac

Issue:
Support for ```required``` properties
## Problem Statement
Traditionally, Autofac creates objects by calling one of their constructors.  The constructor accepts dependencies and then assigns/initializes values.

Starting in C#11, dependencies can also be expressed using the ```required``` keyword.  When a property is ```required```, if the constructor that was called does not have the ```[SetsRequiredMembers]``` attribute, the property must have a value set immediately following construction.

Currently AutoFac does not support this core enhancement to object construction.

## Desired Solution
When Autofac constructs an object, the constructor should be examined for ```[SetsRequiredMembers]```.  If it is not found, all writable properties containing the ```RequiredMemberAttribute``` attribute should be injected.

```csharp
    public class Foo1 { 
        //This should be injected
        public required Dependency? Dep { protected get; init; }
    }

    public class Foo2 {
        //This should not
        public required Dependency? Dep { protected get; init; }

        [SetsRequiredMembers]
        public Foo2() {
            this.Dep = null;
        }

    }
```

## Additional Context
Because C#11 introduces this core-level enhancement to object construction, the above rules should always be enabled.

The following code will be helpful in implementing this enhancement:
```csharp
    public class PS : IPropertySelector {
        public static IPropertySelector Instance { get; }

        private static string RequiredAttributeName { get; }
        private static string SetsRequiredMembersAttributeName { get; }

        static PS() {
            Instance = new PS();

            //The type names are stored as strings so that this can also be used with legacy .NET platforms
            RequiredAttributeName = "System.Runtime.CompilerServices.RequiredMemberAttribute";
            SetsRequiredMembersAttributeName = "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute";
        }

        public bool NeedsPropertyInjection(ConstructorInfo constructorInfo) {
            var SetsRequiredMembers = constructorInfo.GetCustomAttributesData().Any(x => string.Equals(x.AttributeType.FullName, SetsRequiredMembersAttributeName, StringComparison.InvariantCultureIgnoreCase));

            var ret = !SetsRequiredMembers;

            return ret;

        }

        public bool InjectProperty(PropertyInfo propertyInfo, object instance) {
            var ret = propertyInfo.CanWrite
                && propertyInfo.GetCustomAttributesData().Any(x => string.Equals(x.AttributeType.FullName, RequiredAttributeName, StringComparison.InvariantCultureIgnoreCase))
                ;

            return ret;

        }
    }
```





Hints:
Generally in favour of the feature to reduce user confusion when combined with the NRT analyser, I'm guessing it will determine that any required properties are non-null.

Got some random questions/thoughts though of the top of my head...

- Currently you have to invoke `PropertiesAutowired` on a registration to apply any property injection behaviour. Are you suggesting this is still the case? Or would this be applied to all registrations automatically? 

- Is there a compiler-generated attribute that indicates "this type has required properties"?

- Does this feature combine with nullable reference types in any interesting ways? If someone has a `required ServiceA?` property, does that mean it _must_ be populated? Or is still a bit optional?

- Do we _only_ apply this to `required` attributes? Or do non-required properties become optional injection automatically?

Just trying to tease out some of the detail on this one...
Hi @alistairjevans -
**RE ```PropertiesAutowired```:**
I believe that required properties should always be injected and should essentially be treated like "Constructor 2.0"

**RE compiler-generated attribute**:
Yes.  See the code above.  There are two attributes that work together:
```
            RequiredAttributeName = "System.Runtime.CompilerServices.RequiredMemberAttribute";
            SetsRequiredMembersAttributeName = "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute";
```

**RE nullable reference types**:
A required property _MUST be set although ```null``` is a valid value to set it to.  I believe that if the nullable reference type is annotated, then autofac should throw an exception if it cannot provid a value since null is an invalid value in that case.

**RE: Do we only apply this to required attributes?**
This should be thought of as part of maintaining compatibility with the current .net object construction methodologies and should not be optional.  The current ```PropertiesAutowired``` should continue to exist and operate as is since it provides similar (but not identical) capabilities.


It might be worth finding out what, if anything, the Microsoft container is going to do here. They sometimes make unspoken/undocumented assumptions on how things will work. If we do something different we'll need a way to "switch to MS compatible behavior" (see: lifetime scopes aren't hierarchical).
@tillig, looks like the answer to that is not yet known: https://github.com/dotnet/runtime/issues/76854

I think I'm a little reluctant to enable property-autowiring "by default", mostly due to user confusion from the different behaviour between 'required' properties and 'optional' properties.

Say I have two properties on a component, of type `A` and `B`. Type `A` is marked as `required`, and `B` is not. I *always* register a service of type `A`, but `B` is condtionally registered (e.g. by tenant). 

When I resolve my component, in this scenario `A` would always be populated, and `B` would never be, however if I call `PropertiesAutowired()` on the registration, then `B` *will* sometimes be populated, but the behaviour of `A` will be the same. 

This feels like it will absolutely result in users raising issues where "this property is set, but this one isn't, even though it's registered...".

So it still feels like property injection needs to be opt-in somehow...there probably is a small number of changes we could make that gives us a decent compromise:

- We can check for the `RequiredMemberAttribute` on a type when we build the registration; if it is present, but `PropertiesAutowired` has not been called, we can throw a suitably informative exception.

- When executing `AutowiringPropertyInjector.InjectProperties`, if a property is marked as `required`, but is excluded by a custom property selector, we can throw an exception.

- If we fail to resolve a service for a property marked with `RequiredMemberAttribute`, we throw an exception.

- Optionally, to enable users who genuinely want this behaviour *everywhere*, we could add a `ContainerBuilder.GlobalPropertiesAutowired()` method that injects `PropertiesAutowired()` onto every registration. Technically this is already possible with a registered event handler, but a shortcut may be useful.


Related to this, how would I enable ```PropertiesAutowired``` on ```AnyConcreteTypeNotAlreadyRegisteredSource```?
@TonyValenti good point with ACTNARS.

@alistairjevans My gut says both that you're right, inconsistent behavior will be a problem. But I also think if a property is marked required then it's part of object initialization so it probably has to happen.

This seems particularly to affect reflection-based construction (individual types, open generics) since the compiler would ostensibly stop a lambda from returning an incomplete object.

I haven't thought this totally through, but I wonder if part of the constructor locator process needs to also locate required properties and add those to a list of always-initialized properties. Not full autowired properties, but also not something you have to opt into. If props autowiring is opted in, then the set of properties to wire is the _union_ of required and whatever else can be populated.

If a required property can't be filled in, a similar exception to the constructor exception gets thrown, but with an explanation about it being a property.

I think this could be cached like we cache constructors to save a bit on perf, but it's definitely going to be a non-zero hit since, at a minimum, we'll always have to check the cache for the (likely empty) list of required properties.

I'm reluctant to make a final decision until we know what M.E.DI is doing simply because the path of least resistance is to be compatible.
I added a comment to that dotnet issue. I don't know if they'll even address it, looks like it's been there for a bit with no other commentary. We may need to make a decision here and then just post to them what we did as a way to try to inform what they do, then hope for compatibility.
Thanks @tillig -
I'm really hopeful this gets implemented. Being able to have required properties be injected would enable me to remove a ton of code in my application. 

I have a set of base classes that are extended 3-4 levels deep. Currently, Every constructor at every level of the inheritance chain has to collect and forward parameters to its base constructor.  That's nothing unique to me, I'm just particularly affected by it.

Anyways, injecting to required properties would allow me to remove close to 1600 constructors in my code base and enable me to remove about 600 auxiliary classes.

I really hope this gets some traction. 


**_Just adding to the thread what I wrote in the original "required properties proposal" in C#, it explains very well some benefits:_**

With **required** properties, we end up creating an alternative way to specify a constructor without bloat the ctor with a lot of parameters.
```csharp
public class Person
{
    public required string FirstName { get; init; }
    public required string MiddleName { get; init; }
    public required string LastName { get; init; }
}
```
Now, assuming Dependency Injectors can understand what I am asking, we can change code like this
```csharp
public class SomeClass
{
    private readonly IDbContext _dbContext;
    private readonly ILogger _logger;
    private readonly IOptions _options;

    public SomeClass(IDbContext dbContext, ILogger logger, IOptions options)
    {
        _dbContext = dbContext;
        _logger = logger;
        _options = options;
    }
}

public class SomeDerivedClass : SomeClass
{
    private readonly IMailService _mailService;

    public SomeDerivedClass(IDbContext dbContext, ILogger logger, IOptions options, IMailService mailService)
        :base(dbContext, logger, options)
    {
        _mailService = mailService;
    }
}
```
in this instead:
```csharp
public class SomeClass
{
    public required IDbContext DbContext { get; init; }
    public required ILogger Logger { get; init; }
    public required IOptions<AppSettings> Options { get; init; }
}

public class SomeDerivedClass : SomeClass
{
    public required IMailService MailService { get; init; }
}
```

This is a LOT of less boilerplate error-prone code! And the "required" is the key here.  I know that currently it is partially supported by Dependency Injectors (property injection), but it is rarely used and I think if the Injector finds a "required" property it will be better than using a settable property.
I don't think we need to be sold on the value of the feature. At this point it's more a question of how it should work and how we can remain compatible with M.E.DI. I'm almost more concerned about that compatibility issue because trying to retrofit compatibility has caused us a lot of trouble in the past. Folks, for whatever reason, choose Autofac as the underlying container and then complain when it isn't identical to the Microsoft container.
Hi @tillig -
What is the right way to get input from MS regarding this?  I can really use this feature and want to do whatever is necessary to drive this enhancement forward.
@TonyValenti if I had a way, I'd have used it. I don't have any more visibility or leverage into their process than you do. I commented on the issue linked earlier, that's all I can do. You can comment there, too, if you like.

Working through things that may affect every user of a container or framework is not a fast process.
@tillig - We have a response from Microsoft!

https://github.com/dotnet/runtime/issues/76854

Patch:
diff --git a/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj b/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
index 248ba87d6..165a5eac8 100644
--- a/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
+++ b/bench/Autofac.BenchmarkProfiling/Autofac.BenchmarkProfiling.csproj
@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net7.0</TargetFramework>
     <PlatformTarget>x64</PlatformTarget>
     <ImplicitUsings>enable</ImplicitUsings>
   </PropertyGroup>
diff --git a/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj b/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
index eb53fe099..a3de60a98 100644
--- a/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
+++ b/bench/Autofac.Benchmarks/Autofac.Benchmarks.csproj
@@ -1,7 +1,7 @@
 ﻿<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
-    <TargetFramework>net6.0</TargetFramework>
+    <TargetFramework>net7.0</TargetFramework>
     <NoWarn>$(NoWarn);CS1591</NoWarn>
     <OutputType>Exe</OutputType>
     <GenerateProgramFile>false</GenerateProgramFile>
diff --git a/bench/Autofac.Benchmarks/Benchmarks.cs b/bench/Autofac.Benchmarks/Benchmarks.cs
index c4bd6f04c..93f20013a 100644
--- a/bench/Autofac.Benchmarks/Benchmarks.cs
+++ b/bench/Autofac.Benchmarks/Benchmarks.cs
@@ -6,27 +6,28 @@ public static class Benchmarks
 {
     public static readonly Type[] All =
     {
-            typeof(ChildScopeResolveBenchmark),
-            typeof(ConcurrencyBenchmark),
-            typeof(ConcurrencyNestedScopeBenchmark),
-            typeof(KeyedGenericBenchmark),
-            typeof(KeyedNestedBenchmark),
-            typeof(KeyedSimpleBenchmark),
-            typeof(KeylessGenericBenchmark),
-            typeof(KeylessNestedBenchmark),
-            typeof(KeylessNestedSharedInstanceBenchmark),
-            typeof(KeylessNestedLambdaBenchmark),
-            typeof(KeylessNestedSharedInstanceLambdaBenchmark),
-            typeof(KeylessSimpleBenchmark),
-            typeof(KeylessSimpleSharedInstanceBenchmark),
-            typeof(KeylessSimpleLambdaBenchmark),
-            typeof(KeylessSimpleSharedInstanceLambdaBenchmark),
-            typeof(DeepGraphResolveBenchmark),
-            typeof(EnumerableResolveBenchmark),
-            typeof(PropertyInjectionBenchmark),
-            typeof(RootContainerResolveBenchmark),
-            typeof(OpenGenericBenchmark),
-            typeof(MultiConstructorBenchmark),
-            typeof(LambdaResolveBenchmark),
-        };
+        typeof(ChildScopeResolveBenchmark),
+        typeof(ConcurrencyBenchmark),
+        typeof(ConcurrencyNestedScopeBenchmark),
+        typeof(KeyedGenericBenchmark),
+        typeof(KeyedNestedBenchmark),
+        typeof(KeyedSimpleBenchmark),
+        typeof(KeylessGenericBenchmark),
+        typeof(KeylessNestedBenchmark),
+        typeof(KeylessNestedSharedInstanceBenchmark),
+        typeof(KeylessNestedLambdaBenchmark),
+        typeof(KeylessNestedSharedInstanceLambdaBenchmark),
+        typeof(KeylessSimpleBenchmark),
+        typeof(KeylessSimpleSharedInstanceBenchmark),
+        typeof(KeylessSimpleLambdaBenchmark),
+        typeof(KeylessSimpleSharedInstanceLambdaBenchmark),
+        typeof(DeepGraphResolveBenchmark),
+        typeof(EnumerableResolveBenchmark),
+        typeof(PropertyInjectionBenchmark),
+        typeof(RootContainerResolveBenchmark),
+        typeof(OpenGenericBenchmark),
+        typeof(MultiConstructorBenchmark),
+        typeof(LambdaResolveBenchmark),
+        typeof(RequiredPropertyBenchmark),
+    };
 }
diff --git a/bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs b/bench/Autofac.Benchmarks/ConcurrencyBenchmark.cs
similarity index 100%
rename from bench/Autofac.Benchmarks/ConcurrencyBenchmak.cs
rename to bench/Autofac.Benchmarks/ConcurrencyBenchmark.cs
diff --git a/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs b/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs
new file mode 100644
index 000000000..7b343afd2
--- /dev/null
+++ b/bench/Autofac.Benchmarks/RequiredPropertyBenchmark.cs
@@ -0,0 +1,61 @@
+﻿using Autofac.Core;
+
+namespace Autofac.Benchmarks;
+
+public class RequiredPropertyBenchmark
+{
+    private IContainer _container;
+
+    [GlobalSetup]
+    public void Setup()
+    {
+        var builder = new ContainerBuilder();
+
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<ConstructorComponent>();
+        builder.RegisterType<RequiredPropertyComponent>();
+
+        _container = builder.Build();
+    }
+
+    [Benchmark(Baseline = true)]
+    public void NormalConstructor()
+    {
+        _container.Resolve<ConstructorComponent>();
+    }
+
+    [Benchmark]
+    public void RequiredProperties()
+    {
+        _container.Resolve<RequiredPropertyComponent>();
+    }
+
+    public class ServiceA
+    {
+    }
+
+    public class ServiceB
+    {
+    }
+
+    public class ConstructorComponent
+    {
+        public ConstructorComponent(ServiceA serviceA, ServiceB serviceB)
+        {
+            ServiceA = serviceA;
+            ServiceB = serviceB;
+        }
+
+        public ServiceA ServiceA { get; }
+
+        public ServiceB ServiceB { get; }
+    }
+
+    public class RequiredPropertyComponent
+    {
+        required public ServiceA ServiceA { get; set; }
+
+        required public ServiceA ServiceB { get; set; }
+    }
+}
diff --git a/global.json b/global.json
index 1982217ab..ed6506965 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
 {
   "sdk": {
-    "version": "7.0.100",
+    "version": "7.0.101",
     "rollForward": "latestFeature"
   },
 
diff --git a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
index 3488b2e12..ad323b592 100644
--- a/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
+++ b/src/Autofac/Core/Activators/Reflection/BoundConstructor.cs
@@ -71,6 +71,12 @@ internal BoundConstructor(ConstructorBinder binder, ParameterInfo firstNonBindab
     /// </summary>
     public ConstructorBinder Binder { get; }
 
+    /// <summary>
+    /// Gets a value indicating whether the constructor has the SetsRequiredMembers attribute,
+    /// indicating we can skip population of required properties.
+    /// </summary>
+    public bool SetsRequiredMembers => Binder.SetsRequiredMembers;
+
     /// <summary>
     /// Gets the constructor on the target type. The actual constructor used
     /// might differ, e.g. if using a dynamic proxy.
diff --git a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
index 08d07c4d9..bb70bb5b3 100644
--- a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
+++ b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
@@ -26,6 +26,10 @@ public ConstructorBinder(ConstructorInfo constructorInfo)
         Constructor = constructorInfo ?? throw new ArgumentNullException(nameof(constructorInfo));
         _constructorArgs = constructorInfo.GetParameters();
 
+#if NET7_0_OR_GREATER
+        SetsRequiredMembers = constructorInfo.GetCustomAttribute<SetsRequiredMembersAttribute>() is not null;
+#endif
+
         // If any of the parameters are unsafe, do not create an invoker, and store the parameter
         // that broke the rule.
         _illegalParameter = DetectIllegalParameter(_constructorArgs);
@@ -44,6 +48,12 @@ public ConstructorBinder(ConstructorInfo constructorInfo)
     /// </summary>
     public ConstructorInfo Constructor { get; }
 
+    /// <summary>
+    /// Gets a value indicating whether the constructor has the SetsRequiredMembers attribute,
+    /// indicating we can skip population of required properties.
+    /// </summary>
+    public bool SetsRequiredMembers { get; }
+
     /// <summary>
     /// Gets the set of parameters to bind against.
     /// </summary>
diff --git a/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs
new file mode 100644
index 000000000..f439f4cd5
--- /dev/null
+++ b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs
@@ -0,0 +1,69 @@
+﻿// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System.Globalization;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
+using System.Text;
+using Autofac.Core.Resolving;
+using Autofac.Core.Resolving.Pipeline;
+using Autofac.Util;
+using Autofac.Util.Cache;
+
+namespace Autofac.Core.Activators.Reflection;
+
+/// <summary>
+/// Holds an instance of a known property on a type instantiated by the <see cref="ReflectionActivator"/>.
+/// </summary>
+internal class InjectableProperty
+{
+    private readonly MethodInfo _setter;
+    private readonly ParameterInfo _setterParameter;
+
+    /// <summary>
+    /// Initializes a new instance of the <see cref="InjectableProperty"/> class.
+    /// </summary>
+    /// <param name="prop">The property info.</param>
+    public InjectableProperty(PropertyInfo prop)
+    {
+        Property = prop;
+
+        _setter = prop.SetMethod!;
+
+        _setterParameter = _setter.GetParameters()[0];
+
+#if NET7_0_OR_GREATER
+        IsRequired = prop.GetCustomAttribute<RequiredMemberAttribute>() is not null;
+#endif
+    }
+
+    /// <summary>
+    /// Gets the underlying property.
+    /// </summary>
+    public PropertyInfo Property { get; }
+
+    /// <summary>
+    /// Gets a value indicating whether this field is marked as required.
+    /// </summary>
+    public bool IsRequired { get; }
+
+    /// <summary>
+    /// Try and supply a value for this property using the given parameter.
+    /// </summary>
+    /// <param name="instance">The object instance.</param>
+    /// <param name="p">The parameter that may provide the value.</param>
+    /// <param name="ctxt">The component context.</param>
+    /// <returns>True if the parameter could provide a value, and the property was set. False otherwise.</returns>
+    public bool TrySupplyValue(object instance, Parameter p, IComponentContext ctxt)
+    {
+        if (p.CanSupplyValue(_setterParameter, ctxt, out var vp))
+        {
+            Property.SetValue(instance, vp(), null);
+
+            return true;
+        }
+
+        return false;
+    }
+}
diff --git a/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs b/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs
new file mode 100644
index 000000000..d5d55b860
--- /dev/null
+++ b/src/Autofac/Core/Activators/Reflection/InjectablePropertyState.cs
@@ -0,0 +1,30 @@
+﻿// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+namespace Autofac.Core.Activators.Reflection;
+
+/// <summary>
+/// Structure used to track whether or not we have set a property during activation.
+/// </summary>
+internal struct InjectablePropertyState
+{
+    /// <summary>
+    /// Initializes a new instance of the <see cref="InjectablePropertyState"/> struct.
+    /// </summary>
+    /// <param name="property">The property.</param>
+    public InjectablePropertyState(InjectableProperty property)
+    {
+        Property = property;
+        Set = false;
+    }
+
+    /// <summary>
+    /// Gets the property.
+    /// </summary>
+    public InjectableProperty Property { get; }
+
+    /// <summary>
+    /// Gets or sets a value indicating whether this property has already been set.
+    /// </summary>
+    public bool Set { get; set; }
+}
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
index 0561d060b..1c0ad351c 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
@@ -4,9 +4,12 @@
 using System.Globalization;
 using System.Reflection;
 using System.Runtime.CompilerServices;
+using System.Runtime.Serialization;
 using System.Text;
 using Autofac.Core.Resolving;
 using Autofac.Core.Resolving.Pipeline;
+using Autofac.Util;
+using Autofac.Util.Cache;
 
 namespace Autofac.Core.Activators.Reflection;
 
@@ -21,6 +24,8 @@ public class ReflectionActivator : InstanceActivator, IInstanceActivator
     private readonly Parameter[] _defaultParameters;
 
     private ConstructorBinder[]? _constructorBinders;
+    private bool _anyRequiredMembers;
+    private InjectablePropertyState[]? _defaultFoundPropertySet;
 
     /// <summary>
     /// Initializes a new instance of the <see cref="ReflectionActivator"/> class.
@@ -78,6 +83,40 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
             throw new ArgumentNullException(nameof(pipelineBuilder));
         }
 
+#if NET7_0_OR_GREATER
+        // The RequiredMemberAttribute has Inherit = false on its AttributeUsage options,
+        // so we can't use the expected GetCustomAttribute(inherit: true) option, and must walk the tree.
+        var currentType = _implementationType;
+        while (currentType is not null && !currentType.Equals(typeof(object)))
+        {
+            if (currentType.GetCustomAttribute<RequiredMemberAttribute>() is not null)
+            {
+                _anyRequiredMembers = true;
+                break;
+            }
+
+            currentType = currentType.BaseType;
+        }
+#else
+        _anyRequiredMembers = false;
+#endif
+
+        if (_anyRequiredMembers || _configuredProperties.Length > 0)
+        {
+            // Get the full set of properties.
+            var actualProperties = _implementationType
+                .GetRuntimeProperties()
+                .Where(pi => pi.CanWrite)
+                .ToList();
+
+            _defaultFoundPropertySet = new InjectablePropertyState[actualProperties.Count];
+
+            for (int idx = 0; idx < actualProperties.Count; idx++)
+            {
+                _defaultFoundPropertySet[idx] = new InjectablePropertyState(new InjectableProperty(actualProperties[idx]));
+            }
+        }
+
         // Locate the possible constructors at container build time.
         var availableConstructors = ConstructorFinder.FindConstructors(_implementationType);
 
@@ -96,6 +135,7 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
         if (binders.Length == 1)
         {
             UseSingleConstructorActivation(pipelineBuilder, binders[0]);
+
             return;
         }
         else if (ConstructorSelector is IConstructorSelectorWithEarlyBinding earlyBindingSelector)
@@ -147,7 +187,7 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil
 
                 var instance = boundConstructor.Instantiate();
 
-                InjectProperties(instance, ctxt);
+                InjectProperties(instance, ctxt, boundConstructor, GetAllParameters(ctxt.Parameters));
 
                 ctxt.Instance = instance;
 
@@ -171,7 +211,7 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil
 
                 var instance = bound.Instantiate();
 
-                InjectProperties(instance, ctxt);
+                InjectProperties(instance, ctxt, bound, prioritisedParameters);
 
                 ctxt.Instance = instance;
 
@@ -204,7 +244,9 @@ private object ActivateInstance(IComponentContext context, IEnumerable<Parameter
 
         CheckNotDisposed();
 
-        var allBindings = GetAllBindings(_constructorBinders!, context, parameters);
+        var prioritisedParameters = GetAllParameters(parameters);
+
+        var allBindings = GetAllBindings(_constructorBinders!, context, prioritisedParameters);
 
         var selectedBinding = ConstructorSelector.SelectConstructorBinding(allBindings, parameters);
 
@@ -218,21 +260,19 @@ private object ActivateInstance(IComponentContext context, IEnumerable<Parameter
 
         var instance = selectedBinding.Instantiate();
 
-        InjectProperties(instance, context);
+        InjectProperties(instance, context, selectedBinding, prioritisedParameters);
 
         return instance;
     }
 
-    private BoundConstructor[] GetAllBindings(ConstructorBinder[] availableConstructors, IComponentContext context, IEnumerable<Parameter> parameters)
+    private BoundConstructor[] GetAllBindings(ConstructorBinder[] availableConstructors, IComponentContext context, IEnumerable<Parameter> allParameters)
     {
-        var prioritisedParameters = GetAllParameters(parameters);
-
         var boundConstructors = new BoundConstructor[availableConstructors.Length];
         var validBindings = availableConstructors.Length;
 
         for (var idx = 0; idx < availableConstructors.Length; idx++)
         {
-            var bound = availableConstructors[idx].Bind(prioritisedParameters, context);
+            var bound = availableConstructors[idx].Bind(allParameters, context);
 
             boundConstructors[idx] = bound;
 
@@ -310,33 +350,112 @@ private string GetBindingFailureMessage(BoundConstructor[] constructorBindings)
         }
     }
 
-    private void InjectProperties(object instance, IComponentContext context)
+    private void InjectProperties(object instance, IComponentContext context, BoundConstructor constructor, IEnumerable<Parameter> allParameters)
     {
-        if (_configuredProperties.Length == 0)
+        // We only need to do any injection if we have a set of property information.
+        if (_defaultFoundPropertySet is null)
         {
             return;
         }
 
-        var actualProperties = instance
-            .GetType()
-            .GetRuntimeProperties()
-            .Where(pi => pi.CanWrite)
-            .ToList();
+        // If this constructor sets all required members,
+        // and we have no configured properties, we can just jump out.
+        if (_configuredProperties.Length == 0 && constructor.SetsRequiredMembers)
+        {
+            return;
+        }
+
+        var workingSetOfProperties = (InjectablePropertyState[])_defaultFoundPropertySet.Clone();
 
         foreach (var configuredProperty in _configuredProperties)
         {
-            foreach (var actualProperty in actualProperties)
+            for (var propIdx = 0; propIdx < workingSetOfProperties.Length; propIdx++)
             {
-                var setter = actualProperty.SetMethod;
+                ref var prop = ref workingSetOfProperties[propIdx];
+
+                if (prop.Set)
+                {
+                    // Skip, already seen.
+                    continue;
+                }
 
-                if (setter != null &&
-                    configuredProperty.CanSupplyValue(setter.GetParameters()[0], context, out var vp))
+                if (prop.Property.TrySupplyValue(instance, configuredProperty, context))
                 {
-                    actualProperties.Remove(actualProperty);
-                    actualProperty.SetValue(instance, vp(), null);
+                    prop.Set = true;
                     break;
                 }
             }
         }
+
+        if (_anyRequiredMembers && !constructor.SetsRequiredMembers)
+        {
+            List<InjectableProperty>? failingRequiredProperties = null;
+
+            for (var propIdx = 0; propIdx < workingSetOfProperties.Length; propIdx++)
+            {
+                ref var prop = ref workingSetOfProperties[propIdx];
+
+                if (!prop.Property.IsRequired)
+                {
+                    // Only auto-populate required properties.
+                    continue;
+                }
+
+                if (prop.Set)
+                {
+                    // Only auto-populate things not already populated by a specific property
+                    // being set.
+                    continue;
+                }
+
+                foreach (var parameter in allParameters)
+                {
+                    if (parameter is NamedParameter || parameter is PositionalParameter)
+                    {
+                        // Skip Named and Positional parameters, because if someone uses 'value' as a
+                        // constructor parameter name, it would also match the property, and cause confusion.
+                        continue;
+                    }
+
+                    if (prop.Property.TrySupplyValue(instance, parameter, context))
+                    {
+                        prop.Set = true;
+
+                        break;
+                    }
+                }
+
+                if (!prop.Set)
+                {
+                    failingRequiredProperties ??= new();
+                    failingRequiredProperties.Add(prop.Property);
+                }
+            }
+
+            if (failingRequiredProperties is not null)
+            {
+                throw new DependencyResolutionException(BuildRequiredPropertyResolutionMessage(failingRequiredProperties));
+            }
+        }
+    }
+
+    private string BuildRequiredPropertyResolutionMessage(IReadOnlyList<InjectableProperty> failingRequiredProperties)
+    {
+        var propertyDescriptions = new StringBuilder();
+
+        foreach (var failed in failingRequiredProperties)
+        {
+            propertyDescriptions.AppendLine();
+            propertyDescriptions.Append(failed.Property.Name);
+            propertyDescriptions.Append(" (");
+            propertyDescriptions.Append(failed.Property.PropertyType.FullName);
+            propertyDescriptions.Append(')');
+        }
+
+        return string.Format(
+            CultureInfo.CurrentCulture,
+            ReflectionActivatorResources.RequiredPropertiesCouldNotBeBound,
+            _implementationType,
+            propertyDescriptions);
     }
 }
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx b/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
index 947acb4d6..81ffa790c 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
@@ -1,17 +1,17 @@
 ﻿<?xml version="1.0" encoding="utf-8"?>
 <root>
-  <!--
-    Microsoft ResX Schema
-
+  <!-- 
+    Microsoft ResX Schema 
+    
     Version 2.0
-
-    The primary goals of this format is to allow a simple XML format
-    that is mostly human readable. The generation and parsing of the
-    various data types are done through the TypeConverter classes
+    
+    The primary goals of this format is to allow a simple XML format 
+    that is mostly human readable. The generation and parsing of the 
+    various data types are done through the TypeConverter classes 
     associated with the data types.
-
+    
     Example:
-
+    
     ... ado.net/XML headers & schema ...
     <resheader name="resmimetype">text/microsoft-resx</resheader>
     <resheader name="version">2.0</resheader>
@@ -26,36 +26,36 @@
         <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
         <comment>This is a comment</comment>
     </data>
-
-    There are any number of "resheader" rows that contain simple
+                
+    There are any number of "resheader" rows that contain simple 
     name/value pairs.
-
-    Each data row contains a name, and value. The row also contains a
-    type or mimetype. Type corresponds to a .NET class that support
-    text/value conversion through the TypeConverter architecture.
-    Classes that don't support this are serialized and stored with the
+    
+    Each data row contains a name, and value. The row also contains a 
+    type or mimetype. Type corresponds to a .NET class that support 
+    text/value conversion through the TypeConverter architecture. 
+    Classes that don't support this are serialized and stored with the 
     mimetype set.
-
-    The mimetype is used for serialized objects, and tells the
-    ResXResourceReader how to depersist the object. This is currently not
+    
+    The mimetype is used for serialized objects, and tells the 
+    ResXResourceReader how to depersist the object. This is currently not 
     extensible. For a given mimetype the value must be set accordingly:
-
-    Note - application/x-microsoft.net.object.binary.base64 is the format
-    that the ResXResourceWriter will generate, however the reader can
+    
+    Note - application/x-microsoft.net.object.binary.base64 is the format 
+    that the ResXResourceWriter will generate, however the reader can 
     read any of the formats listed below.
-
+    
     mimetype: application/x-microsoft.net.object.binary.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
             : and then encoded with base64 encoding.
-
+    
     mimetype: application/x-microsoft.net.object.soap.base64
-    value   : The object must be serialized with
+    value   : The object must be serialized with 
             : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
             : and then encoded with base64 encoding.
 
     mimetype: application/x-microsoft.net.object.bytearray.base64
-    value   : The object must be serialized into a byte array
+    value   : The object must be serialized into a byte array 
             : using a System.ComponentModel.TypeConverter
             : and then encoded with base64 encoding.
     -->
@@ -128,4 +128,7 @@
 
 See https://autofac.rtfd.io/help/no-constructors-bindable for more info.</value>
   </data>
-</root>
+  <data name="RequiredPropertiesCouldNotBeBound" xml:space="preserve">
+    <value>One or more of the required properties on type '{0}' could not be resolved:{1}</value>
+  </data>
+</root>
\ No newline at end of file


Test patch:
diff --git a/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs
new file mode 100644
index 000000000..00dfe07b9
--- /dev/null
+++ b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs
@@ -0,0 +1,355 @@
+﻿// Copyright (c) Autofac Project. All rights reserved.
+// Licensed under the MIT License. See LICENSE in the project root for license information.
+
+using System.Diagnostics.CodeAnalysis;
+using Autofac.Core;
+
+namespace Autofac.Specification.Test.Features;
+
+#if NET7_0_OR_GREATER
+
+public class RequiredPropertyTests
+{
+    [Fact]
+    public void ResolveRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<Component>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>();
+
+        Assert.NotNull(component.ServiceA);
+        Assert.NotNull(component.ServiceB);
+    }
+
+    [Fact]
+    public void MissingRequiredPropertyServiceThrowsException()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>();
+
+        var container = builder.Build();
+
+        var exception = Assert.Throws<DependencyResolutionException>(() => container.Resolve<Component>());
+
+        Assert.Contains(nameof(Component.ServiceB), exception.InnerException.Message);
+    }
+
+    [Fact]
+    public void CanMixConstructorsAndProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<MixedConstructorAndPropertyComponent>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<MixedConstructorAndPropertyComponent>();
+
+        Assert.NotNull(component.ServiceA);
+        Assert.NotNull(component.ServiceB);
+    }
+
+    [Fact]
+    public void PropertiesPopulatedOnAllSubsequentResolves()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<Component>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>();
+
+        Assert.NotNull(component.ServiceA);
+        Assert.NotNull(component.ServiceB);
+
+        var component2 = container.Resolve<Component>();
+
+        Assert.NotNull(component2.ServiceA);
+        Assert.NotNull(component2.ServiceB);
+
+        var component3 = container.Resolve<Component>();
+
+        Assert.NotNull(component3.ServiceA);
+        Assert.NotNull(component3.ServiceB);
+    }
+
+    [Fact]
+    public void ExplicitParameterOverridesRequiredAutowiring()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<Component>().WithProperty(nameof(Component.ServiceB), new ServiceB { Tag = "custom" });
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>();
+
+        Assert.Equal("custom", component.ServiceB.Tag);
+    }
+
+    [Fact]
+    public void ExplicitParameterCanTakePlaceOfRegistration()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>().WithProperty(nameof(Component.ServiceB), new ServiceB { Tag = "custom" });
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>();
+
+        Assert.Equal("custom", component.ServiceB.Tag);
+    }
+
+    [Fact]
+    public void GeneralTypeParameterCanTakePlaceOfRegistration()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>().WithParameter(new TypedParameter(typeof(ServiceB), new ServiceB()));
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>();
+
+        Assert.NotNull(component.ServiceB);
+    }
+
+    [Fact]
+    public void ParameterPassedAtResolveUsedForRequiredProperty()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<Component>(new TypedParameter(typeof(ServiceB), new ServiceB()));
+
+        Assert.NotNull(component.ServiceB);
+    }
+
+    [Fact]
+    public void NamedParametersIgnoredForRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>().WithParameter("value", new ServiceB());
+
+        var container = builder.Build();
+
+        Assert.Throws<DependencyResolutionException>(() => container.Resolve<Component>());
+    }
+
+    [Fact]
+    public void PositionalParametersIgnoredForRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<Component>().WithParameter(new PositionalParameter(0, new ServiceB()));
+
+        var container = builder.Build();
+
+        Assert.Throws<DependencyResolutionException>(() => container.Resolve<Component>());
+    }
+
+    [Fact]
+    public void SetsRequiredMembersConstructorSkipsRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ConstructorComponent>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<ConstructorComponent>();
+
+        Assert.Null(component.ServiceA);
+        Assert.Null(component.ServiceB);
+    }
+
+    [Fact]
+    public void SetsRequiredMembersConstructorSkipsRequiredPropertiesEvenWhenRegistered()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<ConstructorComponent>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<ConstructorComponent>();
+
+        Assert.Null(component.ServiceA);
+        Assert.Null(component.ServiceB);
+    }
+
+    [Fact]
+    public void OnlySelectedConstructorConsideredForSetsRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<MultiConstructorComponent>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<MultiConstructorComponent>();
+
+        // Allowed to not be set, because empty constructor was selected.
+        Assert.Null(component.ServiceA);
+
+        using var scope = container.BeginLifetimeScope(b => b.RegisterType<ServiceC>());
+
+        // Fails, because constructor with SetsRequiredProperties attribute is selected.
+        Assert.Throws<DependencyResolutionException>(() => scope.Resolve<MultiConstructorComponent>());
+    }
+
+    [Fact]
+    public void DerivedClassResolvesPropertiesInParent()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<DerivedComponent>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<DerivedComponent>();
+
+        Assert.NotNull(component.ServiceA);
+        Assert.NotNull(component.ServiceB);
+    }
+
+    [Fact]
+    public void DerivedClassResolvesPropertiesInParentAndSelf()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterType<ServiceA>();
+        builder.RegisterType<ServiceB>();
+        builder.RegisterType<ServiceC>();
+        builder.RegisterType<DerivedComponentWithProp>();
+
+        var container = builder.Build();
+
+        var component = container.Resolve<DerivedComponentWithProp>();
+
+        Assert.NotNull(component.ServiceA);
+        Assert.NotNull(component.ServiceB);
+        Assert.NotNull(component.ServiceC);
+    }
+
+    [Fact]
+    public void CanResolveOpenGenericComponentRequiredProperties()
+    {
+        var builder = new ContainerBuilder();
+        builder.RegisterGeneric(typeof(OpenGenericService<>));
+        builder.RegisterGeneric(typeof(OpenGenericComponent<>));
+
+        var container = builder.Build();
+
+        var component = container.Resolve<OpenGenericComponent<int>>();
+
+        Assert.NotNull(component.Service);
+    }
+
+    private class OpenGenericComponent<T>
+    {
+        required public OpenGenericService<T> Service { get; set; }
+    }
+
+    private class OpenGenericService<T>
+    {
+    }
+
+    private class ConstructorComponent
+    {
+        [SetsRequiredMembers]
+        public ConstructorComponent()
+        {
+        }
+
+        required public ServiceA ServiceA { get; set; }
+
+        required public ServiceB ServiceB { get; set; }
+    }
+
+    private class MixedConstructorAndPropertyComponent
+    {
+        public MixedConstructorAndPropertyComponent(ServiceA serviceA)
+        {
+            ServiceA = serviceA;
+        }
+
+        public ServiceA ServiceA { get; set; }
+
+        required public ServiceB ServiceB { get; set; }
+    }
+
+    private class MultiConstructorComponent
+    {
+        [SetsRequiredMembers]
+        public MultiConstructorComponent()
+        {
+        }
+
+        public MultiConstructorComponent(ServiceC serviceC)
+        {
+        }
+
+        required public ServiceA ServiceA { get; set; }
+    }
+
+    private class Component
+    {
+        required public ServiceA ServiceA { get; set; }
+
+        required public ServiceB ServiceB { get; set; }
+    }
+
+    private class DerivedComponentWithProp : Component
+    {
+        public DerivedComponentWithProp()
+        {
+        }
+
+        required public ServiceC ServiceC { get; set; }
+    }
+
+    private class DerivedComponent : Component
+    {
+    }
+
+    private class ServiceA
+    {
+        public ServiceA()
+        {
+            Tag = "Default";
+        }
+
+        public string Tag { get; set; }
+    }
+
+    private class ServiceB
+    {
+        public ServiceB()
+        {
+            Tag = "Default";
+        }
+
+        public string Tag { get; set; }
+    }
+
+    private class ServiceC
+    {
+    }
+}
+
+#endif