Skip to content

Preserve attribute inheritance behavior in the MA0179 code fix - #1457

Merged
meziantou merged 1 commit into
mainfrom
feature/ma0179-attribute-inheritance-5e3f40
Sep 11, 2026
Merged

meziantou merged 1 commit into
mainfrom
feature/ma0179-attribute-inheritance-5e3f40

Conversation

@meziantou

Copy link
Copy Markdown
Owner

Problem

The MA0179 code fix replaced instance GetCustomAttributes calls with Attribute.IsDefined and copied the inherit argument unchanged. The two APIs don't handle inherit the same way on properties and events:

  • the instance MemberInfo.GetCustomAttributes(Type, bool) and MemberInfo.GetCustomAttributes(bool) methods ignore inherit for properties and events
  • Attribute.IsDefined(MemberInfo, Type, bool) also searches the overridden properties and events when inherit is true
[AttributeUsage(AttributeTargets.Property, Inherited = true)]
public class MarkerAttribute : Attribute { }
public class Base { [Marker] public virtual int Value => 0; }
public class Derived : Base { public override int Value => 0; }

// false
typeof(Derived).GetProperty("Value").GetCustomAttributes(typeof(MarkerAttribute), true).Length > 0
// fix output before this PR: true
Attribute.IsDefined(typeof(Derived).GetProperty("Value"), typeof(MarkerAttribute), true)

I ran this on .NET 10 for each member kind:

Member instance GetCustomAttributes(t, true) instance IsDefined(t, true) Attribute.IsDefined(m, t, true)
Property false false true
Event false false true
Method true true true
Type true true true

The extension (CustomAttributeExtensions) and static Attribute.GetCustomAttribute(s) calls were not affected: they already search inherited attributes the same way as Attribute.IsDefined.

Fix

For instance GetCustomAttributes calls, the fix now produces the instance MemberInfo.IsDefined(Type, bool) when:

  • the static type of the receiver can be a PropertyInfo or EventInfo at runtime (for example MemberInfo, PropertyInfo or EventInfo), and
  • inherit is not the constant false.

MemberInfo.IsDefined(Type, bool) gives the same result as the original call. In all other cases (inherit: false, receivers such as Type, MethodInfo, FieldInfo, Assembly, Module, and the extension and static calls), the fix still produces Attribute.IsDefined.

// before
_ = member.GetCustomAttributes(typeof(ObsoleteAttribute), inherit: true).Length > 0;
// after the fix
_ = member.IsDefined(typeof(ObsoleteAttribute), inherit: true);

Tests

  • Three existing fix tests expected Attribute.IsDefined(member, ..., inherit: true) for a MemberInfo receiver, which is the buggy output. They now expect the instance IsDefined.
  • New cases:
    • PropertyInfo and EventInfo with inherit: true
    • a non-constant inherit variable
    • PropertyInfo with inherit: false, which still gets Attribute.IsDefined
    • Type, MethodInfo and FieldInfo with inherit: true, which still get Attribute.IsDefined
    • the extension GetCustomAttributes(Type) on a PropertyInfo, which still gets Attribute.IsDefined
  • Without the fixer change, 6 of these tests fail. With it, all 43 UseAttributeIsDefinedAnalyzerTests pass on Roslyn 4.8, 4.14, 5.0, 5.6 and 5.9.
  • dotnet run --project src/DocumentationGenerator makes no further changes.

Notes for reviewers

  • docs/Rules/MA0179.md has a new section describing this behavior.
  • The code fix title ("Use Attribute.IsDefined") and the diagnostic message are unchanged, even when the fix produces member.IsDefined(...). Showing a different title would mean building the replacement before the fix is registered, which is a larger refactor of the fixer. I left it out of this PR.

The instance MemberInfo.GetCustomAttributes(Type, bool) and
MemberInfo.GetCustomAttributes(bool) methods ignore 'inherit' for
properties and events, while Attribute.IsDefined(MemberInfo, Type, bool)
walks the chain of overridden properties and events. Copying the
'inherit' argument into Attribute.IsDefined could therefore turn a
false result into true.

The code fix now uses the instance MemberInfo.IsDefined(Type, bool) when
the receiver can be a PropertyInfo or EventInfo and 'inherit' is not the
constant false. Other cases still use Attribute.IsDefined.
@meziantou
meziantou merged commit 0d04f52 into main Sep 11, 2026
13 checks passed
@meziantou
meziantou deleted the feature/ma0179-attribute-inheritance-5e3f40 branch September 11, 2026 19:20
This was referenced Sep 11, 2026
This was referenced Sep 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant