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 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 /// /// Initializes a new instance of the class. /// - /// The whose constructor was not found. - public NoConstructorsFoundException(Type offendingType) - : this(offendingType, FormatMessage(offendingType)) + /// The whose constructor was not found. + /// The that was used to scan for the constructors. + public NoConstructorsFoundException(Type offendingType, IConstructorFinder constructorFinder) + : this(offendingType, constructorFinder, FormatMessage(offendingType, constructorFinder)) { } /// /// Initializes a new instance of the class. /// - /// The whose constructor was not found. + /// The whose constructor was not found. + /// The that was used to scan for the constructors. /// Exception message. - 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)); } /// /// Initializes a new instance of the class. /// - /// The whose constructor was not found. + /// The whose constructor was not found. + /// The that was used to scan for the constructors. /// The inner exception. - 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) { } /// /// Initializes a new instance of the class. /// - /// The whose constructor was not found. + /// The whose constructor was not found. + /// The that was used to scan for the constructors. /// Exception message. /// The inner exception. - 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)); } + /// + /// Gets the finder used when locating constructors. + /// + /// + /// An that was used when scanning the + /// to find constructors. + /// + public IConstructorFinder ConstructorFinder { get; private set; } + /// /// Gets the type without found constructors. /// /// - /// A that was processed by an - /// or similar mechanism and was determined to have no available constructors. + /// A that was processed by the + /// and was determined to have no available + /// constructors. /// 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 @@  - @@ -118,6 +118,6 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - No accessible constructors were found for the type '{0}'. + No constructors on type '{0}' can be found with the constructor finder '{1}'. - \ No newline at end of file + 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 @@  - @@ -120,9 +120,6 @@ The constructor selector provided by '{0}' selected a binding that cannot be instantiated. Selectors must return a constructor where 'CanInstantiate' is true. - - No constructors on type '{0}' can be found with the constructor finder '{1}'. - None of the constructors found with '{0}' on type '{1}' can be invoked with the available services and parameters:{2} 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 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(); + } + + 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 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 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; /// internal static class OpenGenericServiceBinder { - /// - /// 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. - /// - /// The closed generic service to bind. - /// The set of configured open generic services. - /// The implementation type of the open generic. - /// The built closed generic implementation type. - /// The built closed generic services. - /// True if the closed generic service can be bound. False otherwise. - public static bool TryBindOpenGenericService( - Service closedService, - IEnumerable 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; - } - - /// - /// 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. - /// - /// The closed generic service to bind. - /// The set of configured open generic services. - /// Delegate responsible for generating an instance of a closed generic based on the open generic type being registered. - /// The built closed generic implementation type. - /// The built closed generic services. - /// True if the closed generic service can be bound. False otherwise. - public static bool TryBindOpenGenericDelegate( - Service closedService, - IEnumerable configuredOpenGenericServices, - Func, object> openGenericFactory, - [NotNullWhen(returnValue: true)] out Func, 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; - } - /// /// 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( /// The built closed generic implementation type. /// The built closed generic services. /// True if the closed generic service can be bound. False otherwise. + [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 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().Any(s => s.Equals(definitionService))) + if (configuredOpenGenericServices.OfType().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() + .OfType() .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( /// The built closed generic implementation type. /// The built closed generic services. /// True if the closed generic service can be bound. False otherwise. + [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 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().Any(s => s.Equals(definitionService))) + if (configuredOpenGenericServices.OfType().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> serviceArgumentDefinitionToArgument) { var matchingRegularType = serviceArgumentDefinitionToArgument 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() + { + } + } }