Repository: autofac/Autofac

Issue:
Inconsistent error reporting when no constructors are found
## Describe the Bug

I was in the process of writing documentation to explain what to do when the set `IConstructorFinder` is unable to find any constructors on a component type and noticed we have multiple potential reporting mechanisms for the issue:

- If using the `DefaultConstructorFinder`, [that finder throws its own `NoConstructorsFoundException`](https://github.com/autofac/Autofac/blob/0c79d7bce06173e358c748a0a3f64f311a901f7f/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs#L53) using a default message: `No accessible constructors were found for the type '{0}'.`
- If using a custom `IConstructorFinder`, [the `ReflectionActivator` throws the `NoConstructorsFoundException`](https://github.com/autofac/Autofac/blob/0c79d7bce06173e358c748a0a3f64f311a901f7f/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs#L86) but with a custom error message: `No constructors on type '{0}' can be found with the constructor finder '{1}'.`

This is not only inconsistent (the interface `IConstructorFinder` indicates you can/should return an empty set and let the activator deal with it) but it also makes it hard to document or prescribe troubleshooting for since the message is so different from one report to another.

I propose:

- Remove the exception throwing from `DefaultConstructorFinder`. Let the `ReflectionActivator` handle it.
- Update the `NoConstructorsFoundException` to have a constructor that takes two parameters - the type being scanned and the type of the constructor finder - and use the message from the `ReflectionActivator` as the message for that constructor.

That would make the message (and stack trace) consistent across the default and specified constructor finder and would make documenting the issue easier.


Hints:

Patch:
diff --git a/bench/Autofac.BenchmarkProfiling/Program.cs b/bench/Autofac.BenchmarkProfiling/Program.cs
index 007338158..0bf1280ad 100644
--- a/bench/Autofac.BenchmarkProfiling/Program.cs
+++ b/bench/Autofac.BenchmarkProfiling/Program.cs
@@ -84,14 +84,14 @@ static void Main(string[] args)
 
         // Workload method is generated differently when BenchmarkDotNet actually runs; we'll need to wrap it in the set of parameters.
         // It's way slower than they way they do it, but it should still give us good profiler results.
-        Action<int> workloadAction = (repeat) =>
+        void workloadAction(int repeat)
         {
             while (repeat > 0)
             {
                 selectedCase.Descriptor.WorkloadMethod.Invoke(benchInstance, selectedCase.Parameters.Items.Select(x => x.Value).ToArray());
                 repeat--;
             }
-        };
+        }
 
         setupAction.InvokeSingle();
 
diff --git a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs
index 469328d48..dbb1eba63 100644
--- a/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs
+++ b/src/Autofac/Core/Activators/Reflection/DefaultConstructorFinder.cs
@@ -45,14 +45,7 @@ public ConstructorInfo[] FindConstructors(Type targetType)
 
     private static ConstructorInfo[] GetDefaultPublicConstructors(Type type)
     {
-        var retval = ReflectionCacheSet.Shared.Internal.DefaultPublicConstructors
+        return ReflectionCacheSet.Shared.Internal.DefaultPublicConstructors
             .GetOrAdd(type, t => t.GetDeclaredPublicConstructors());
-
-        if (retval.Length == 0)
-        {
-            throw new NoConstructorsFoundException(type);
-        }
-
-        return retval;
     }
 }
diff --git a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs
index 9162b4fc5..ea2d23800 100644
--- a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs
+++ b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundException.cs
@@ -13,61 +13,82 @@ public class NoConstructorsFoundException : Exception
     /// <summary>
     /// Initializes a new instance of the <see cref="NoConstructorsFoundException"/> class.
     /// </summary>
-    /// <param name="offendingType">The <see cref="System.Type"/> whose constructor was not found.</param>
-    public NoConstructorsFoundException(Type offendingType)
-        : this(offendingType, FormatMessage(offendingType))
+    /// <param name="offendingType">The <see cref="Type"/> whose constructor was not found.</param>
+    /// <param name="constructorFinder">The <see cref="IConstructorFinder"/> that was used to scan for the constructors.</param>
+    public NoConstructorsFoundException(Type offendingType, IConstructorFinder constructorFinder)
+        : this(offendingType, constructorFinder, FormatMessage(offendingType, constructorFinder))
     {
     }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="NoConstructorsFoundException"/> class.
     /// </summary>
-    /// <param name="offendingType">The <see cref="System.Type"/> whose constructor was not found.</param>
+    /// <param name="offendingType">The <see cref="Type"/> whose constructor was not found.</param>
+    /// <param name="constructorFinder">The <see cref="IConstructorFinder"/> that was used to scan for the constructors.</param>
     /// <param name="message">Exception message.</param>
-    public NoConstructorsFoundException(Type offendingType, string message)
+    public NoConstructorsFoundException(Type offendingType, IConstructorFinder constructorFinder, string message)
         : base(message)
     {
         OffendingType = offendingType ?? throw new ArgumentNullException(nameof(offendingType));
+        ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder));
     }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="NoConstructorsFoundException"/> class.
     /// </summary>
-    /// <param name="offendingType">The <see cref="System.Type"/> whose constructor was not found.</param>
+    /// <param name="offendingType">The <see cref="Type"/> whose constructor was not found.</param>
+    /// <param name="constructorFinder">The <see cref="IConstructorFinder"/> that was used to scan for the constructors.</param>
     /// <param name="innerException">The inner exception.</param>
-    public NoConstructorsFoundException(Type offendingType, Exception innerException)
-        : this(offendingType, FormatMessage(offendingType), innerException)
+    public NoConstructorsFoundException(Type offendingType, IConstructorFinder constructorFinder, Exception innerException)
+        : this(offendingType, constructorFinder, FormatMessage(offendingType, constructorFinder), innerException)
     {
     }
 
     /// <summary>
     /// Initializes a new instance of the <see cref="NoConstructorsFoundException"/> class.
     /// </summary>
-    /// <param name="offendingType">The <see cref="System.Type"/> whose constructor was not found.</param>
+    /// <param name="offendingType">The <see cref="Type"/> whose constructor was not found.</param>
+    /// <param name="constructorFinder">The <see cref="IConstructorFinder"/> that was used to scan for the constructors.</param>
     /// <param name="message">Exception message.</param>
     /// <param name="innerException">The inner exception.</param>
-    public NoConstructorsFoundException(Type offendingType, string message, Exception innerException)
+    public NoConstructorsFoundException(Type offendingType, IConstructorFinder constructorFinder, string message, Exception innerException)
         : base(message, innerException)
     {
         OffendingType = offendingType ?? throw new ArgumentNullException(nameof(offendingType));
+        ConstructorFinder = constructorFinder ?? throw new ArgumentNullException(nameof(constructorFinder));
     }
 
+    /// <summary>
+    /// Gets the finder used when locating constructors.
+    /// </summary>
+    /// <value>
+    /// An <see cref="IConstructorFinder"/> that was used when scanning the
+    /// <see cref="OffendingType"/> to find constructors.
+    /// </value>
+    public IConstructorFinder ConstructorFinder { get; private set; }
+
     /// <summary>
     /// Gets the type without found constructors.
     /// </summary>
     /// <value>
-    /// A <see cref="System.Type"/> that was processed by an <see cref="IConstructorFinder"/>
-    /// or similar mechanism and was determined to have no available constructors.
+    /// A <see cref="Type"/> that was processed by the
+    /// <see cref="ConstructorFinder"/> and was determined to have no available
+    /// constructors.
     /// </value>
     public Type OffendingType { get; private set; }
 
-    private static string FormatMessage(Type offendingType)
+    private static string FormatMessage(Type offendingType, IConstructorFinder constructorFinder)
     {
         if (offendingType == null)
         {
             throw new ArgumentNullException(nameof(offendingType));
         }
 
-        return string.Format(CultureInfo.CurrentCulture, NoConstructorsFoundExceptionResources.Message, offendingType.FullName);
+        if (constructorFinder == null)
+        {
+            throw new ArgumentNullException(nameof(constructorFinder));
+        }
+
+        return string.Format(CultureInfo.CurrentCulture, NoConstructorsFoundExceptionResources.Message, offendingType.FullName, constructorFinder.GetType().FullName);
     }
 }
diff --git a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundExceptionResources.resx b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundExceptionResources.resx
index 6ebd2a7e4..03fa3f43c 100644
--- a/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundExceptionResources.resx
+++ b/src/Autofac/Core/Activators/Reflection/NoConstructorsFoundExceptionResources.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.
     -->
@@ -118,6 +118,6 @@
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
   <data name="Message" xml:space="preserve">
-    <value>No accessible constructors were found for the type '{0}'.</value>
+    <value>No constructors on type '{0}' can be found with the constructor finder '{1}'.</value>
   </data>
-</root>
\ No newline at end of file
+</root>
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
index 3dd6f8467..0561d060b 100644
--- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
+++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
@@ -83,7 +83,7 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic
 
         if (availableConstructors.Length == 0)
         {
-            throw new NoConstructorsFoundException(_implementationType, string.Format(CultureInfo.CurrentCulture, ReflectionActivatorResources.NoConstructorsAvailable, _implementationType, ConstructorFinder));
+            throw new NoConstructorsFoundException(_implementationType, ConstructorFinder);
         }
 
         var binders = new ConstructorBinder[availableConstructors.Length];
@@ -131,7 +131,7 @@ private void UseSingleConstructorActivation(IResolvePipelineBuilder pipelineBuil
                 // This is not going to happen, because there is only 1 constructor, that constructor has no parameters,
                 // so there are no conditions under which GetConstructorInvoker will return null in this path.
                 // Throw an error here just in case (and to satisfy nullability checks).
-                throw new NoConstructorsFoundException(_implementationType, string.Format(CultureInfo.CurrentCulture, ReflectionActivatorResources.NoConstructorsAvailable, _implementationType, ConstructorFinder));
+                throw new NoConstructorsFoundException(_implementationType, ConstructorFinder);
             }
 
             // If there are no arguments to the constructor, bypass all argument binding and pre-bind the constructor.
diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx b/src/Autofac/Core/Activators/Reflection/ReflectionActivatorResources.resx
index 120f587f3..947acb4d6 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.
     -->
@@ -120,9 +120,6 @@
   <data name="ConstructorSelectorCannotSelectAnInvalidBinding" xml:space="preserve">
     <value>The constructor selector provided by '{0}' selected a binding that cannot be instantiated. Selectors must return a constructor where 'CanInstantiate' is true.</value>
   </data>
-  <data name="NoConstructorsAvailable" xml:space="preserve">
-    <value>No constructors on type '{0}' can be found with the constructor finder '{1}'.</value>
-  </data>
   <data name="NoConstructorsBindable" xml:space="preserve">
     <value>None of the constructors found with '{0}' on type '{1}' can be invoked with the available services and parameters:{2}</value>
   </data>
diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs
index c5c05c226..7195926b4 100644
--- a/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs
+++ b/src/Autofac/Features/OpenGenerics/OpenGenericDecoratorRegistrationSource.cs
@@ -64,9 +64,13 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun
             throw new ArgumentNullException(nameof(registrationAccessor));
         }
 
-        if (OpenGenericServiceBinder.TryBindOpenGenericService(service, _registrationData.Services, _activatorData.ImplementationType, out Type? constructedImplementationType, out Service[]? services))
+        if (service is not IServiceWithType swt)
+        {
+            return Enumerable.Empty<IComponentRegistration>();
+        }
+
+        if (OpenGenericServiceBinder.TryBindOpenGenericTypedService(swt, _registrationData.Services, _activatorData.ImplementationType, out Type? constructedImplementationType, out Service[]? services))
         {
-            var swt = (IServiceWithType)service;
             var fromService = _activatorData.FromService.ChangeType(swt.ServiceType);
 
             return registrationAccessor(fromService)
diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs
index 0f4c0f1ef..4cd32cc35 100644
--- a/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs
+++ b/src/Autofac/Features/OpenGenerics/OpenGenericDelegateRegistrationSource.cs
@@ -47,7 +47,12 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun
             throw new ArgumentNullException(nameof(registrationAccessor));
         }
 
-        if (OpenGenericServiceBinder.TryBindOpenGenericDelegate(service, _registrationData.Services, _activatorData.Factory, out var constructedFactory, out Service[]? services))
+        if (service is not IServiceWithType swt)
+        {
+            yield break;
+        }
+
+        if (OpenGenericServiceBinder.TryBindOpenGenericDelegateService(swt, _registrationData.Services, _activatorData.Factory, out var constructedFactory, out Service[]? services))
         {
             // Pass the pipeline builder from the original registration to the 'CreateRegistration'.
             // So the original registration will contain all of the pipeline stages originally added, plus anything we want to add.
diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs
index 73bd1b1c3..bd0a9426b 100644
--- a/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs
+++ b/src/Autofac/Features/OpenGenerics/OpenGenericRegistrationSource.cs
@@ -59,7 +59,12 @@ public IEnumerable<IComponentRegistration> RegistrationsFor(Service service, Fun
             throw new ArgumentNullException(nameof(registrationAccessor));
         }
 
-        if (OpenGenericServiceBinder.TryBindOpenGenericService(service, _registrationData.Services, _activatorData.ImplementationType, out Type? constructedImplementationType, out Service[]? services))
+        if (service is not IServiceWithType swt)
+        {
+            yield break;
+        }
+
+        if (OpenGenericServiceBinder.TryBindOpenGenericTypedService(swt, _registrationData.Services, _activatorData.ImplementationType, out Type? constructedImplementationType, out Service[]? services))
         {
             // Pass the pipeline builder from the original registration to the 'CreateRegistration'.
             // So the original registration will contain all of the pipeline stages originally added, plus anything we want to add.
diff --git a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs
index e7de4266f..670c596e6 100644
--- a/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs
+++ b/src/Autofac/Features/OpenGenerics/OpenGenericServiceBinder.cs
@@ -12,60 +12,6 @@ namespace Autofac.Features.OpenGenerics;
 /// </summary>
 internal static class OpenGenericServiceBinder
 {
-    /// <summary>
-    /// Given a closed generic service (that is being requested), creates a closed generic implementation type
-    /// and associated services from the open generic implementation and services.
-    /// </summary>
-    /// <param name="closedService">The closed generic service to bind.</param>
-    /// <param name="configuredOpenGenericServices">The set of configured open generic services.</param>
-    /// <param name="openGenericImplementationType">The implementation type of the open generic.</param>
-    /// <param name="constructedImplementationType">The built closed generic implementation type.</param>
-    /// <param name="constructedServices">The built closed generic services.</param>
-    /// <returns>True if the closed generic service can be bound. False otherwise.</returns>
-    public static bool TryBindOpenGenericService(
-        Service closedService,
-        IEnumerable<Service> configuredOpenGenericServices,
-        Type openGenericImplementationType,
-        [NotNullWhen(returnValue: true)] out Type? constructedImplementationType,
-        [NotNullWhen(returnValue: true)] out Service[]? constructedServices)
-    {
-        if (closedService is IServiceWithType swt)
-        {
-            return TryBindOpenGenericTypedService(swt, configuredOpenGenericServices, openGenericImplementationType, out constructedImplementationType, out constructedServices);
-        }
-
-        constructedImplementationType = null;
-        constructedServices = null;
-        return false;
-    }
-
-    /// <summary>
-    /// Given a closed generic service (that is being requested), creates a closed generic implementation type
-    /// and associated services from the open generic implementation and services.
-    /// </summary>
-    /// <param name="closedService">The closed generic service to bind.</param>
-    /// <param name="configuredOpenGenericServices">The set of configured open generic services.</param>
-    /// <param name="openGenericFactory">Delegate responsible for generating an instance of a closed generic based on the open generic type being registered.</param>
-    /// <param name="constructedFactory">The built closed generic implementation type.</param>
-    /// <param name="constructedServices">The built closed generic services.</param>
-    /// <returns>True if the closed generic service can be bound. False otherwise.</returns>
-    public static bool TryBindOpenGenericDelegate(
-        Service closedService,
-        IEnumerable<Service> configuredOpenGenericServices,
-        Func<IComponentContext, Type[], IEnumerable<Parameter>, object> openGenericFactory,
-        [NotNullWhen(returnValue: true)] out Func<IComponentContext, IEnumerable<Parameter>, object>? constructedFactory,
-        [NotNullWhen(returnValue: true)] out Service[]? constructedServices)
-    {
-        if (closedService is IServiceWithType swt)
-        {
-            return TryBindOpenGenericDelegateService(swt, configuredOpenGenericServices, openGenericFactory, out constructedFactory, out constructedServices);
-        }
-
-        constructedFactory = null;
-        constructedServices = null;
-        return false;
-    }
-
     /// <summary>
     /// Given a closed generic service (that is being requested), creates a closed generic implementation type
     /// and associated services from the open generic implementation and services.
@@ -76,6 +22,7 @@ public static bool TryBindOpenGenericDelegate(
     /// <param name="constructedImplementationType">The built closed generic implementation type.</param>
     /// <param name="constructedServices">The built closed generic services.</param>
     /// <returns>True if the closed generic service can be bound. False otherwise.</returns>
+    [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")]
     public static bool TryBindOpenGenericTypedService(
         IServiceWithType serviceWithType,
         IEnumerable<Service> configuredOpenGenericServices,
@@ -88,7 +35,7 @@ public static bool TryBindOpenGenericTypedService(
             var definitionService = (IServiceWithType)serviceWithType.ChangeType(serviceWithType.ServiceType.GetGenericTypeDefinition());
             var serviceGenericArguments = serviceWithType.ServiceType.GetGenericArguments();
 
-            if (configuredOpenGenericServices.Cast<IServiceWithType>().Any(s => s.Equals(definitionService)))
+            if (configuredOpenGenericServices.OfType<IServiceWithType>().Any(s => s.Equals(definitionService)))
             {
                 var implementorGenericArguments = TryMapImplementationGenericArguments(
                     openGenericImplementationType, serviceWithType.ServiceType, definitionService.ServiceType, serviceGenericArguments);
@@ -99,7 +46,7 @@ public static bool TryBindOpenGenericTypedService(
                     var constructedImplementationTypeTmp = openGenericImplementationType.MakeGenericType(implementorGenericArguments!);
 
                     var implementedServices = configuredOpenGenericServices
-                        .Cast<IServiceWithType>()
+                        .OfType<IServiceWithType>()
                         .Where(s => s.ServiceType.GetGenericArguments().Length == serviceGenericArguments.Length)
                         .Select(s => new { ServiceWithType = s, GenericService = s.ServiceType.MakeGenericType(serviceGenericArguments) })
                         .Where(p => p.GenericService.IsAssignableFrom(constructedImplementationTypeTmp))
@@ -131,6 +78,7 @@ public static bool TryBindOpenGenericTypedService(
     /// <param name="constructedFactory">The built closed generic implementation type.</param>
     /// <param name="constructedServices">The built closed generic services.</param>
     /// <returns>True if the closed generic service can be bound. False otherwise.</returns>
+    [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")]
     public static bool TryBindOpenGenericDelegateService(
         IServiceWithType serviceWithType,
         IEnumerable<Service> configuredOpenGenericServices,
@@ -143,7 +91,7 @@ public static bool TryBindOpenGenericDelegateService(
             var definitionService = (IServiceWithType)serviceWithType.ChangeType(serviceWithType.ServiceType.GetGenericTypeDefinition());
             var serviceGenericArguments = serviceWithType.ServiceType.GetGenericArguments();
 
-            if (configuredOpenGenericServices.Cast<IServiceWithType>().Any(s => s.Equals(definitionService)))
+            if (configuredOpenGenericServices.OfType<IServiceWithType>().Any(s => s.Equals(definitionService)))
             {
                 constructedFactory = (ctx, parameters) => openGenericFactory(ctx, serviceGenericArguments, parameters);
 
@@ -257,6 +205,7 @@ private static Type[] GetInterfaces(Type implementationType, Type serviceType) =
             .Where(i => i.Name == serviceType.Name && i.Namespace == serviceType.Namespace)
             .ToArray();
 
+    [SuppressMessage("CA1851", "CA1851", Justification = "The CPU cost in enumerating the list of services is low, while allocating a new list saves little in CPU but costs a lot in allocations.")]
     private static Type? TryFindServiceArgumentForImplementationArgumentDefinition(Type implementationGenericArgumentDefinition, IEnumerable<KeyValuePair<Type, Type>> serviceArgumentDefinitionToArgument)
     {
         var matchingRegularType = serviceArgumentDefinitionToArgument


Test patch:
diff --git a/test/Autofac.Test/Core/Activators/Reflection/DefaultConstructorFinderTests.cs b/test/Autofac.Test/Core/Activators/Reflection/DefaultConstructorFinderTests.cs
index 18c3f812c..ca21ab499 100644
--- a/test/Autofac.Test/Core/Activators/Reflection/DefaultConstructorFinderTests.cs
+++ b/test/Autofac.Test/Core/Activators/Reflection/DefaultConstructorFinderTests.cs
@@ -34,6 +34,16 @@ public void CanFindNonPublicConstructorsUsingFinderFunction()
         Assert.Contains(privateConstructor, constructors);
     }
 
+    [Fact]
+    public void SupportsZeroPublicConstructorTypes()
+    {
+        var finder = new DefaultConstructorFinder();
+        var targetType = typeof(NoPublicConstructors);
+        var constructors = finder.FindConstructors(targetType).ToList();
+
+        Assert.Empty(constructors);
+    }
+
     internal class HasConstructors
     {
         public HasConstructors()
@@ -44,4 +54,11 @@ private HasConstructors(int value)
         {
         }
     }
+
+    internal class NoPublicConstructors
+    {
+        private NoPublicConstructors()
+        {
+        }
+    }
 }