Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/Autofac/Core/Activators/DefaultPropertySelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;

using Autofac.Util;

namespace Autofac.Core;

/// <summary>
Expand Down Expand Up @@ -56,8 +58,7 @@ public virtual bool InjectProperty(PropertyInfo propertyInfo, object instance)
return false;
}

#if NET7_0_OR_GREATER
if (propertyInfo.GetCustomAttribute<RequiredMemberAttribute>() 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
Expand All @@ -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)
{
Expand Down
6 changes: 3 additions & 3 deletions src/Autofac/Core/Activators/Reflection/ConstructorBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Linq.Expressions;
using System.Reflection;

using Autofac.Util;

namespace Autofac.Core.Activators.Reflection;

/// <summary>
Expand All @@ -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<SetsRequiredMembersAttribute>() 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.
Expand Down
6 changes: 3 additions & 3 deletions src/Autofac/Core/Activators/Reflection/InjectableProperty.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
using System.Reflection;
using System.Runtime.CompilerServices;

using Autofac.Util;

namespace Autofac.Core.Activators.Reflection;

/// <summary>
Expand All @@ -26,9 +28,7 @@ public InjectableProperty(PropertyInfo prop)

_setterParameter = _setter.GetParameters()[0];

#if NET7_0_OR_GREATER
IsRequired = prop.GetCustomAttribute<RequiredMemberAttribute>() is not null;
#endif
IsRequired = prop.HasRequiredMemberAttribute();
}

/// <summary>
Expand Down
12 changes: 5 additions & 7 deletions src/Autofac/Core/Activators/Reflection/ReflectionActivator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System.Runtime.CompilerServices;
using System.Text;
using Autofac.Core.Resolving.Pipeline;
using Autofac.Util;

namespace Autofac.Core.Activators.Reflection;

Expand Down Expand Up @@ -78,26 +79,23 @@ 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<RequiredMemberAttribute>() is not null)
if (currentType.HasRequiredMemberAttribute())
{
return true;
}
}

return false;
});
#else
_anyRequiredMembers = false;
#endif

if (_anyRequiredMembers || _configuredProperties.Length > 0)
{
Expand Down
4 changes: 0 additions & 4 deletions src/Autofac/Core/InternalReflectionCaches.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,12 +59,10 @@ internal class InternalReflectionCaches
/// </summary>
public ReflectionCacheDictionary<Type, Type> GenericTypeDefinitionByType { get; }

#if NET7_0_OR_GREATER
/// <summary>
/// Gets a cache used by <see cref="ReflectionActivator"/>.
/// </summary>
public ReflectionCacheDictionary<Type, bool> HasRequiredMemberAttribute { get; }
#endif

/// <summary>
/// Initializes a new instance of the <see cref="InternalReflectionCaches"/> class.
Expand All @@ -85,8 +83,6 @@ public InternalReflectionCaches(ReflectionCacheSet set)
AutowiringInjectableProperties = set.GetOrCreateCache<ReflectionCacheDictionary<Type, IReadOnlyList<PropertyInfo>>>(nameof(AutowiringInjectableProperties));
DefaultPublicConstructors = set.GetOrCreateCache<ReflectionCacheDictionary<Type, ConstructorInfo[]>>(nameof(DefaultPublicConstructors));
GenericTypeDefinitionByType = set.GetOrCreateCache<ReflectionCacheDictionary<Type, Type>>(nameof(GenericTypeDefinitionByType));
#if NET7_0_OR_GREATER
HasRequiredMemberAttribute = set.GetOrCreateCache<ReflectionCacheDictionary<Type, bool>>(nameof(HasRequiredMemberAttribute));
#endif
}
}
50 changes: 50 additions & 0 deletions src/Autofac/Util/ReflectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,54 @@ public static ConstructorInfo GetConstructor<TDeclaring>(

return callExpression.Constructor!;
}

/// <summary>
/// Checks if a provided member has a <c>RequiredMemberAttribute</c>.
/// </summary>
/// <remarks>
/// <para>
/// On NET7+ this would <em>typically</em> be the framework supplied <c>RequiredMemberAttribute</c>, <em>but</em> internally the compiler
/// <em>only</em> requires an attribute with that specific type <em>name</em>, not that specific type <em>reference</em>.
/// </para>
/// <para>
/// This could very well be an internally defined custom polyfill attribute using that type name, so this
/// check is done <em>only</em> via type <em>name</em>, not reference.
/// </para>
/// </remarks>
/// <param name="memberInfo">Member to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="memberInfo"/> carries a <see cref="MemberInfo.CustomAttributes">CustomAttributeData</see> with
/// a type <em>name</em> of <c>System.Runtime.CompilerServices.RequiredAttribute</c>; <see langword="false" /> otherwise.
/// </returns>
public static bool HasRequiredMemberAttribute(
this MemberInfo memberInfo)
{
return memberInfo.CustomAttributes.Any(
cad => cad.AttributeType.FullName == "System.Runtime.CompilerServices.RequiredMemberAttribute");
}

/// <summary>
/// Checks if a constructor has a <c>SetsRequiredMembersAttribute</c>.
/// </summary>
/// <remarks>
/// <para>
/// On NET7+ this would <em>typically</em> be the framework supplied <c>SetsRequiredMembersAttribute</c>, <em>but</em> internally the compiler
/// <em>only</em> requires an attribute with that specific type <em>name</em>, not that specific type <em>reference</em>.
/// </para>
/// <para>
/// This could very well be an internally defined custom polyfill attribute using that type name, so this
/// check is done <em>only</em> via type <em>name</em>, not reference.
/// </para>
/// </remarks>
/// <param name="constructorInfo">Constructor to check.</param>
/// <returns>
/// <see langword="true" /> if <paramref name="constructorInfo"/> carries a <see cref="MemberInfo.CustomAttributes">CustomAttributeData</see> with
/// a type <em>name</em> of <c>System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute</c>; <see langword="false" /> otherwise.
/// </returns>
public static bool HasSetsRequiredMembersAttribute(
this ConstructorInfo constructorInfo)
{
return constructorInfo.CustomAttributes.Any(
cad => cad.AttributeType.FullName == "System.Diagnostics.CodeAnalysis.SetsRequiredMembersAttribute");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,11 @@
<None Remove="TestResults\**" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shims\required\*.cs" Link="Util\shims\required\%(FileName).cs" />
<None Include="..\Shims\required\README.md" Link="Util\shims\required\README.md" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@

namespace Autofac.Specification.Test.Features;

#if NET7_0_OR_GREATER

public class RequiredPropertyTests
{
[Fact]
Expand Down Expand Up @@ -351,5 +349,3 @@ private class ServiceC
{
}
}

#endif
5 changes: 5 additions & 0 deletions test/Autofac.Test/Autofac.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@
<None Remove="TestResults\**" />
</ItemGroup>

<ItemGroup>
<Compile Include="..\Shims\required\*.cs" Link="Util\shims\required\%(FileName).cs" />
<None Include="..\Shims\required\README.md" Link="Util\shims\required\README.md" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<PrivateAssets>all</PrivateAssets>
Expand Down
6 changes: 0 additions & 6 deletions test/Autofac.Test/Core/DefaultPropertySelectorTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,15 @@ 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)
{
var finder = new DefaultPropertySelector(preserveSetValue);

var instance = new HasProperties
{
#if NET7_0_OR_GREATER
PublicRequiredProperty = new(),
#endif
};
var property = instance.GetType().GetProperty(propertyName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

Expand Down Expand Up @@ -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();

Expand Down
7 changes: 7 additions & 0 deletions test/Shims/required/.editorconfig
Original file line number Diff line number Diff line change
@@ -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
41 changes: 41 additions & 0 deletions test/Shims/required/CompilerFeatureRequiredAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Indicates that compiler support for a particular feature is required for the location where this attribute is applied.
/// </summary>
[AttributeUsage(AttributeTargets.All, AllowMultiple = true, Inherited = false)]
internal sealed class CompilerFeatureRequiredAttribute : Attribute
{
public CompilerFeatureRequiredAttribute(string featureName)
{
FeatureName = featureName;
}

/// <summary>
/// The name of the compiler feature.
/// </summary>
public string FeatureName { get; }

/// <summary>
/// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand <see cref="FeatureName"/>.
/// </summary>
public bool IsOptional { get; init; }

/// <summary>
/// The <see cref="FeatureName"/> used for the ref structs C# feature.
/// </summary>
public const string RefStructs = nameof(RefStructs);

/// <summary>
/// The <see cref="FeatureName"/> used for the required members C# feature.
/// </summary>
public const string RequiredMembers = nameof(RequiredMembers);
}
}

#endif
10 changes: 10 additions & 0 deletions test/Shims/required/README.md
Original file line number Diff line number Diff line change
@@ -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)



22 changes: 22 additions & 0 deletions test/Shims/required/RequiredMemberAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>Specifies that a type has required members or that a member is required.</summary>
[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
22 changes: 22 additions & 0 deletions test/Shims/required/SetsRequiredMembersAttribute.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Specifies that this constructor sets all required members for the current type, and callers
/// do not need to set any required members themselves.
/// </summary>
[AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false, Inherited = false)]
#if SYSTEM_PRIVATE_CORELIB
public
#else
internal
#endif
sealed class SetsRequiredMembersAttribute : Attribute
{ }
}

#endif