diff --git a/src/Autofac/Core/Activators/DefaultPropertySelector.cs b/src/Autofac/Core/Activators/DefaultPropertySelector.cs index 93da7a8ef..8365f22ff 100644 --- a/src/Autofac/Core/Activators/DefaultPropertySelector.cs +++ b/src/Autofac/Core/Activators/DefaultPropertySelector.cs @@ -4,6 +4,8 @@ using System.Reflection; using System.Runtime.CompilerServices; +using Autofac.Util; + namespace Autofac.Core; /// @@ -56,8 +58,7 @@ public virtual bool InjectProperty(PropertyInfo propertyInfo, object instance) return false; } -#if NET7_0_OR_GREATER - if (propertyInfo.GetCustomAttribute() is not null) + if (propertyInfo.HasRequiredMemberAttribute()) { // The default property selector should not inject required properties, // to avoid duplication with the injection automatically applied inside the @@ -66,7 +67,6 @@ public virtual bool InjectProperty(PropertyInfo propertyInfo, object instance) // set the required properties in order to create the instance. return false; } -#endif if (PreserveSetValues && propertyInfo.CanRead) { diff --git a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs index bb70bb5b3..73aa29128 100644 --- a/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs +++ b/src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs @@ -4,6 +4,8 @@ using System.Linq.Expressions; using System.Reflection; +using Autofac.Util; + namespace Autofac.Core.Activators.Reflection; /// @@ -26,9 +28,7 @@ public ConstructorBinder(ConstructorInfo constructorInfo) Constructor = constructorInfo ?? throw new ArgumentNullException(nameof(constructorInfo)); _constructorArgs = constructorInfo.GetParameters(); -#if NET7_0_OR_GREATER - SetsRequiredMembers = constructorInfo.GetCustomAttribute() is not null; -#endif + SetsRequiredMembers = constructorInfo.HasSetsRequiredMembersAttribute(); // If any of the parameters are unsafe, do not create an invoker, and store the parameter // that broke the rule. diff --git a/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs index 0df27775e..de3ca9365 100644 --- a/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs +++ b/src/Autofac/Core/Activators/Reflection/InjectableProperty.cs @@ -4,6 +4,8 @@ using System.Reflection; using System.Runtime.CompilerServices; +using Autofac.Util; + namespace Autofac.Core.Activators.Reflection; /// @@ -26,9 +28,7 @@ public InjectableProperty(PropertyInfo prop) _setterParameter = _setter.GetParameters()[0]; -#if NET7_0_OR_GREATER - IsRequired = prop.GetCustomAttribute() is not null; -#endif + IsRequired = prop.HasRequiredMemberAttribute(); } /// diff --git a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs index ef88d2a4a..a957ba501 100644 --- a/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs +++ b/src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs @@ -6,6 +6,7 @@ using System.Runtime.CompilerServices; using System.Text; using Autofac.Core.Resolving.Pipeline; +using Autofac.Util; namespace Autofac.Core.Activators.Reflection; @@ -78,16 +79,16 @@ 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. + // The RequiredMemberAttribute (may)* have Inherit = false on its AttributeUsage options, + // so walk the tree. + // (*): see `HasRequiredMemberAttribute` doc for why we dont really know much about the concrete attribute. _anyRequiredMembers = ReflectionCacheSet.Shared.Internal.HasRequiredMemberAttribute.GetOrAdd( _implementationType, static t => { for (var currentType = t; currentType is not null && currentType != typeof(object); currentType = currentType.BaseType) { - if (currentType.GetCustomAttribute() is not null) + if (currentType.HasRequiredMemberAttribute()) { return true; } @@ -95,9 +96,6 @@ public void ConfigurePipeline(IComponentRegistryServices componentRegistryServic return false; }); -#else - _anyRequiredMembers = false; -#endif if (_anyRequiredMembers || _configuredProperties.Length > 0) { diff --git a/src/Autofac/Core/InternalReflectionCaches.cs b/src/Autofac/Core/InternalReflectionCaches.cs index b1068941d..f68c41767 100644 --- a/src/Autofac/Core/InternalReflectionCaches.cs +++ b/src/Autofac/Core/InternalReflectionCaches.cs @@ -59,12 +59,10 @@ internal class InternalReflectionCaches /// public ReflectionCacheDictionary GenericTypeDefinitionByType { get; } -#if NET7_0_OR_GREATER /// /// Gets a cache used by . /// public ReflectionCacheDictionary HasRequiredMemberAttribute { get; } -#endif /// /// Initializes a new instance of the class. @@ -85,8 +83,6 @@ public InternalReflectionCaches(ReflectionCacheSet set) AutowiringInjectableProperties = set.GetOrCreateCache>>(nameof(AutowiringInjectableProperties)); DefaultPublicConstructors = set.GetOrCreateCache>(nameof(DefaultPublicConstructors)); GenericTypeDefinitionByType = set.GetOrCreateCache>(nameof(GenericTypeDefinitionByType)); -#if NET7_0_OR_GREATER HasRequiredMemberAttribute = set.GetOrCreateCache>(nameof(HasRequiredMemberAttribute)); -#endif } } diff --git a/src/Autofac/Util/ReflectionExtensions.cs b/src/Autofac/Util/ReflectionExtensions.cs index 58c4c1ef1..51682e937 100644 --- a/src/Autofac/Util/ReflectionExtensions.cs +++ b/src/Autofac/Util/ReflectionExtensions.cs @@ -121,4 +121,54 @@ public static ConstructorInfo GetConstructor( return callExpression.Constructor!; } + + /// + /// Checks if a provided member has a RequiredMemberAttribute. + /// + /// + /// + /// On NET7+ this would typically be the framework supplied RequiredMemberAttribute, but internally the compiler + /// only requires an attribute with that specific type name, not that specific type reference. + /// + /// + /// This could very well be an internally defined custom polyfill attribute using that type name, so this + /// check is done only via type name, not reference. + /// + /// + /// Member to check. + /// + /// if carries a CustomAttributeData with + /// a type name of System.Runtime.CompilerServices.RequiredAttribute; otherwise. + /// + public static bool HasRequiredMemberAttribute( + this MemberInfo memberInfo) + { + return memberInfo.CustomAttributes.Any( + cad => cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute"); + } + + /// + /// Checks if a constructor has a SetsRequiredMembersAttribute. + /// + /// + /// + /// On NET7+ this would typically be the framework supplied SetsRequiredMembersAttribute, but internally the compiler + /// only requires an attribute with that specific type name, not that specific type reference. + /// + /// + /// This could very well be an internally defined custom polyfill attribute using that type name, so this + /// check is done only via type name, not reference. + /// + /// + /// Constructor to check. + /// + /// if carries a CustomAttributeData with + /// a type name of System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute; otherwise. + /// + public static bool HasSetsRequiredMembersAttribute( + this ConstructorInfo constructorInfo) + { + return constructorInfo.CustomAttributes.Any( + cad => cad.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute"); + } } diff --git a/test/Autofac.Specification.Test/Autofac.Specification.Test.csproj b/test/Autofac.Specification.Test/Autofac.Specification.Test.csproj index 103bf7d9a..96da580f2 100644 --- a/test/Autofac.Specification.Test/Autofac.Specification.Test.csproj +++ b/test/Autofac.Specification.Test/Autofac.Specification.Test.csproj @@ -35,6 +35,11 @@ + + + + + all diff --git a/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs index 95f49497e..5f5bc70f9 100644 --- a/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs +++ b/test/Autofac.Specification.Test/Features/RequiredPropertyTests.cs @@ -6,8 +6,6 @@ namespace Autofac.Specification.Test.Features; -#if NET7_0_OR_GREATER - public class RequiredPropertyTests { [Fact] @@ -351,5 +349,3 @@ private class ServiceC { } } - -#endif diff --git a/test/Autofac.Test/Autofac.Test.csproj b/test/Autofac.Test/Autofac.Test.csproj index 4a55d5af4..e3889a407 100644 --- a/test/Autofac.Test/Autofac.Test.csproj +++ b/test/Autofac.Test/Autofac.Test.csproj @@ -36,6 +36,11 @@ + + + + + all diff --git a/test/Autofac.Test/Core/DefaultPropertySelectorTests.cs b/test/Autofac.Test/Core/DefaultPropertySelectorTests.cs index ed30cde76..72f42612f 100644 --- a/test/Autofac.Test/Core/DefaultPropertySelectorTests.cs +++ b/test/Autofac.Test/Core/DefaultPropertySelectorTests.cs @@ -18,9 +18,7 @@ public class DefaultPropertySelectorTests [InlineData(true, "PublicPropertyNoSet", false)] [InlineData(true, "PublicPropertyThrowsOnGet", false)] [InlineData(false, "PublicPropertyThrowsOnGet", true)] -#if NET7_0_OR_GREATER [InlineData(false, "PublicRequiredProperty", false)] -#endif [Theory] public void DefaultTests(bool preserveSetValue, string propertyName, bool expected) { @@ -28,9 +26,7 @@ public void DefaultTests(bool preserveSetValue, string propertyName, bool expect var instance = new HasProperties { -#if NET7_0_OR_GREATER PublicRequiredProperty = new(), -#endif }; var property = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); @@ -61,9 +57,7 @@ public Test PublicPropertyThrowsOnGet } } -#if NET7_0_OR_GREATER public required Test PublicRequiredProperty { get; set; } -#endif public Test PublicPropertyWithDefault { get; set; } = new Test(); diff --git a/test/Shims/required/.editorconfig b/test/Shims/required/.editorconfig new file mode 100644 index 000000000..0f8b08818 --- /dev/null +++ b/test/Shims/required/.editorconfig @@ -0,0 +1,7 @@ +[*.cs] +# dont complain about .NET Foundation license header +dotnet_diagnostic.SA1636.severity = none +# dont enforce documentation styles for imported shims +dotnet_diagnostic.SA1623.severity = none +# dont enforce coding styles for imported shims +dotnet_diagnostic.SA1502.severity = none \ No newline at end of file diff --git a/test/Shims/required/CompilerFeatureRequiredAttribute.cs b/test/Shims/required/CompilerFeatureRequiredAttribute.cs new file mode 100644 index 000000000..6bcb72a09 --- /dev/null +++ b/test/Shims/required/CompilerFeatureRequiredAttribute.cs @@ -0,0 +1,41 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET7_0_OR_GREATER + +namespace System.Runtime.CompilerServices +{ + /// + /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. + /// + [AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)] + internal sealed class CompilerFeatureRequiredAttribute : Attribute + { + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } + + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; init; } + + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof(RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof(RequiredMembers); + } +} + +#endif diff --git a/test/Shims/required/README.md b/test/Shims/required/README.md new file mode 100644 index 000000000..2fa666fb4 --- /dev/null +++ b/test/Shims/required/README.md @@ -0,0 +1,10 @@ +# shim files for the `required` feature + +Functional polyfill copied from https://github.com/dotnet/runtime[^1] to allow use of `required` keyword in frameworks below .NET7 (if `LangVersion`is set high enough to offer that feature.) + +--- + +[^1]:[SetsRequiredMemberAttribute.cs](https://github.com/dotnet/runtime/blob/c1ab6c5b2524880ed9368841a0a5d8ead78ea09d/src/libraries/System.Private.CoreLib/src/System/Diagnostics/CodeAnalysis/SetsRequiredMembersAttribute.cs), [RequiredMemberAttribute.cs](https://github.com/dotnet/runtime/blob/c1ab6c5b2524880ed9368841a0a5d8ead78ea09d/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/RequiredMemberAttribute.cs), [CompilerFeatureRequiredAttribute.cs](https://github.com/dotnet/runtime/blob/c1ab6c5b2524880ed9368841a0a5d8ead78ea09d/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/CompilerFeatureRequiredAttribute.cs) + + + diff --git a/test/Shims/required/RequiredMemberAttribute.cs b/test/Shims/required/RequiredMemberAttribute.cs new file mode 100644 index 000000000..d183db3cf --- /dev/null +++ b/test/Shims/required/RequiredMemberAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET7_0_OR_GREATER + +using System.ComponentModel; + +namespace System.Runtime.CompilerServices +{ + /// Specifies that a type has required members or that a member is required. + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + [EditorBrowsable(EditorBrowsableState.Never)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class RequiredMemberAttribute : Attribute + { } +} + +#endif diff --git a/test/Shims/required/SetsRequiredMembersAttribute.cs b/test/Shims/required/SetsRequiredMembersAttribute.cs new file mode 100644 index 000000000..3e7c090a1 --- /dev/null +++ b/test/Shims/required/SetsRequiredMembersAttribute.cs @@ -0,0 +1,22 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if !NET7_0_OR_GREATER + +namespace System.Diagnostics.CodeAnalysis +{ + /// + /// Specifies that this constructor sets all required members for the current type, and callers + /// do not need to set any required members themselves. + /// + [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)] +#if SYSTEM_PRIVATE_CORELIB + public +#else + internal +#endif + sealed class SetsRequiredMembersAttribute : Attribute + { } +} + +#endif