Back to Blog
C#

C# AttributeUsage: Controlling Custom Attribute Application

c# attributeusage: Learn how to use AttributeUsage to define valid targets, allow multiple instances, and control inheritance for custom attributes in C#.

C# AttributesAttributeUsageCustom AttributesReflection.NET
Illustration of C# AttributeUsage controlling where custom attributes can be applied, with targets like classes and methods shown as bounded regions.

When you define a custom attribute in C#, the compiler does not automatically know where that attribute is allowed to appear. Without an AttributeUsage declaration, a custom attribute can be applied almost anywhere, which often leads to misuse at compile time or runtime surprises. c# attributeusage is the metadata that tells the compiler and reflection APIs which declaration targets are valid, whether the attribute can be repeated, and whether it is inherited by derived classes.

What AttributeUsage Controls

The AttributeUsage attribute is applied to an attribute class to describe three things:

  • ValidOn: which kinds of program elements (classes, methods, properties, parameters, etc.) the attribute can be attached to.
  • AllowMultiple: whether more than one instance of the attribute can be placed on the same element.
  • Inherited: whether the attribute is automatically inherited by derived classes or overridden members.

These three properties are defined in the AttributeUsageAttribute class, which is part of the System namespace. When you apply AttributeUsage to your custom attribute class, you are essentially declaring a contract that the compiler enforces at compile time and that reflection code can rely on at runtime.

Applying AttributeUsage to a Custom Attribute

To apply AttributeUsage, you place it on the attribute class itself, just like any other attribute. The AttributeTargets enum provides a set of flags that specify valid targets. Here is a minimal example:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public sealed class LogAttribute : Attribute { public string Level { get; } public LogAttribute(string level) => Level = level; }

In this example, LogAttribute can be applied only to classes and methods. Attempting to use it on a property, field, or parameter produces a compiler error. The AttributeTargets enum is a [Flags] enum, so you can combine values with the bitwise OR operator. The default value of ValidOn is AttributeTargets.All, which means every target is allowed if you omit AttributeUsage entirely. That default is rarely what you want for a well-designed custom attribute.

Using ValidOn to Restrict Targets

The ValidOn property is the first parameter of the AttributeUsage constructor. It accepts a combination of AttributeTargets values. The enum includes common targets such as Class, Struct, Method, Property, Field, Parameter, ReturnValue, Interface, Enum, and Delegate. You can also use All to allow every target, or Assembly and Module for assembly-level attributes.

Choosing the right targets is a design decision. A validation attribute that is only meaningful on properties should not be allowed on classes. Restricting targets catches mistakes at compile time rather than forcing reflection code to handle unexpected placements. For example:

[AttributeUsage(AttributeTargets.Property)] public sealed class RequiredAttribute : Attribute { public string ErrorMessage { get; set; } }

Now [Required] can only decorate properties. If you try to apply it to a method, the compiler reports an error. This makes the attribute's intended usage explicit and prevents misuse across a codebase.

AllowMultiple and Its Effect on Reflection

The AllowMultiple property controls whether the same attribute can appear more than once on a single element. By default, it is false, meaning that applying the attribute twice causes a compile-time error. When you set AllowMultiple = true, you allow repeated usage. This is useful for attributes that represent a list of configuration entries, such as a list of allowed roles or a set of validation rules.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = true)] public sealed class AuthorizeRoleAttribute : Attribute { public string Role { get; } public AuthorizeRoleAttribute(string role) => Role = role; }

With this definition, you can write:

[AuthorizeRole("Admin")] [AuthorizeRole("Manager")] public void DeleteRecord() { }

When you retrieve attributes via reflection, GetCustomAttributes returns an array that contains both instances. If AllowMultiple is false, the reflection API still returns an array, but the compiler prevents the duplicate placement in source code. The AllowMultiple setting also affects how the runtime stores and returns attribute instances, so it is important to set it correctly for the semantics you need.

Inherited and Derived Class Behavior

The Inherited property determines whether an attribute applied to a base class or a base method is automatically inherited by derived classes or overrides. The default is true. For example:

[AttributeUsage(AttributeTargets.Class, Inherited = true)] public sealed class AuditAttribute : Attribute { } [Audit] public class BaseController { } public class DerivedController : BaseController { }

Here, DerivedController is also considered to have AuditAttribute when you query it with reflection, even though it is not directly decorated. If you set Inherited = false, the derived class does not inherit the attribute. For methods, inheritance works through overriding. If a base method has an attribute and a derived method overrides it, the attribute is inherited unless Inherited is false. This behavior is important for frameworks that scan attributes to enable features such as authorization or validation. Misunderstanding inheritance can lead to attributes being unexpectedly present on derived types or methods.

Common Mistakes and Compiler Errors

A frequent mistake is forgetting to apply AttributeUsage at all. Without it, the attribute can be applied to any target, and AllowMultiple defaults to false. This often results in runtime reflection code having to handle unexpected placements or duplicate instances. Another mistake is using an invalid combination of AttributeTargets flags, such as trying to apply a class-only attribute to a parameter. The compiler catches this immediately, but the error message can be confusing if you do not realize that the attribute's AttributeUsage is the source of the restriction.

Another issue arises when you set AllowMultiple = true but the attribute constructor is not designed to differentiate instances. If you need multiple instances, each one typically needs a meaningful parameter to distinguish it. Without that, reflection code may not be able to tell why one instance differs from another. Also, be aware that AttributeUsage itself cannot be applied to non-attribute classes; the compiler enforces that the target class derives from Attribute.

Runtime and Reflection Considerations

When you query attributes at runtime, the AttributeUsage metadata influences what reflection returns. For example, GetCustomAttributes respects the Inherited property. If you need to know whether an attribute is present on a derived class only because of inheritance, you can pass inherit: false to the reflection method to ignore inherited attributes. This is a common pattern in framework code that needs to distinguish direct decoration from inherited decoration.

The AllowMultiple property also affects how reflection materializes attribute instances. When AllowMultiple is true, each occurrence is a separate instance. When it is false, only one instance can exist per element, and reflection returns that single instance in an array. This distinction matters when you write code that iterates over attributes and expects a certain count.

From a performance perspective, reflection-based attribute lookup is not free. The runtime caches attribute data for types and members, but the first query may incur metadata parsing. The AttributeUsage settings do not change the cost of reflection significantly; the main performance concern is how often you call GetCustomAttributes in hot paths. If you need to check an attribute frequently, consider caching the result in a static dictionary rather than querying reflection each time. This is a maintainability and performance tradeoff that applies to any attribute-heavy design.

Finally, remember that AttributeUsage is compile-time metadata. It is not enforced at runtime by the CLR beyond what reflection reports. If you misuse an attribute through reflection—for example, by constructing an instance manually and attaching it to an invalid target—the runtime does not prevent it. The AttributeUsage contract is primarily a compile-time tool, so keep that in mind when designing frameworks that rely on attributes.

c# attributeusage: Practical Usage and Code Examples | RYUSLOG DEV