Concrete custom attribute class is not declared NotInheritable

ID

vbnet.maintainability.avoid_unsealed_attributes

Severity

low

Remediation Complexity

trivial

Remediation Risk

low

Remediation Effort

low

Resource

Api Design

Language

VB.NET

Description

Reports a class that inherits from a type whose name ends in Attribute - Attribute itself, System.Attribute, or an existing attribute such as ValidationAttribute - and is declared neither NotInheritable nor MustInherit. A type explicitly designed as a base for other attributes is expected to say so with MustInherit, and is not reported.

Rationale

An attribute is metadata, not a class hierarchy, and leaving one open creates a distinction the consuming code has to be aware of but almost never is. A derived attribute is a different type: GetCustomAttribute(Of LoggedAttribute) finds a VerboseLoggedAttribute because it is assignable, while a lookup or a Select Case written against the exact type does not - so the same annotation is honoured on one code path and ignored on another, depending on how the author of each path happened to ask. It also commits the type to being subclassable: once someone derives from it, the constructors and the property set are a public contract that cannot be tightened, and adding a required constructor parameter breaks code outside the project. NotInheritable costs one keyword, is the shape almost every attribute wants, and removes both questions.

The following code illustrates the pattern detected by this rule:

Namespace Acme.Diagnostics

    ' FLAGGED: Concrete custom attribute class is not declared NotInheritable
    Public Class LoggedAttribute
        Inherits Attribute

        Public Property Channel As String

    End Class

Remediation

Add NotInheritable to the class declaration. If the type really is meant to be a base for a family of attributes, declare it MustInherit instead and make the concrete leaves NotInheritable, so the intent is explicit in either case. Where behaviour genuinely needs to vary, prefer a property on one NotInheritable attribute over a family of derived attributes.

' Before: open to subclassing, so lookups by exact type behave inconsistently
Public Class LoggedAttribute
    Inherits Attribute

' After
Public NotInheritable Class LoggedAttribute
    Inherits Attribute

Configuration

This detector does not need any configuration.