When is a custom attribute's constructor run?
Master System Design with Codemia
Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.
Introduction
A common misconception in C# is that a custom attribute's constructor runs the moment the attribute is applied to a class, method, or property. In reality, the constructor is only invoked when something actively reads the attribute through reflection, typically via GetCustomAttributes(). This lazy-instantiation design means attributes have zero runtime cost until they are explicitly inspected. Understanding this timing is crucial for writing correct attribute-based frameworks and avoiding unexpected behavior.
Defining a Custom Attribute
To create a custom attribute, you derive from System.Attribute and optionally define a constructor to accept metadata values:
The Console.WriteLine inside the constructor helps you observe exactly when instantiation happens.
When the Constructor Actually Runs
Applying the attribute to a class does not trigger the constructor:
At this point, the attribute metadata is stored in the assembly's IL (Intermediate Language) as a blob of bytes, but no AuditAttribute object has been created. The constructor runs only when you retrieve the attribute through reflection:
The output is:
Each GetCustomAttributes call instantiates a fresh attribute object. If you call it twice, the constructor runs twice -- there is no caching by default.
Scanning Attributes Across an Assembly
Frameworks like ASP.NET and xUnit scan entire assemblies for attributes at startup. You can do the same:
This pattern is how dependency injection containers discover services, how test runners find test fixtures, and how serialization libraries detect configuration. In all cases, the attribute constructor only runs during the reflection scan, not when the decorated class is compiled or loaded.
Checking for an Attribute Without Instantiation
If you only need to know whether an attribute is present -- without running its constructor -- use Attribute.IsDefined():
This reads the metadata from the IL without creating an instance. It is faster and avoids any side effects the constructor might have.
Common Pitfalls
- Assuming the constructor runs at decoration time. The attribute constructor does not execute when you compile or load the class. It only runs when reflection reads it via
GetCustomAttributes. Placing initialization logic in the constructor that you expect to "always run" will silently do nothing unless something inspects the attribute. - Putting expensive work in the attribute constructor. Because
GetCustomAttributescreates a new instance on every call, expensive operations (database lookups, file I/O) in the constructor will run repeatedly. Move heavy logic out of the constructor and into the consuming framework. - Forgetting that each
GetCustomAttributescall creates a new instance. Two calls return two distinct objects. If you mutate a property on one instance, the change is not reflected in the next retrieval. Treat attribute instances as short-lived, read-only metadata. - Not specifying
AttributeUsageon your custom attribute. Without[AttributeUsage(...)], the attribute can be applied anywhere and multiple times, which may not match your intent. Always specify valid targets and whether multiple instances are allowed. - Relying on constructor side effects for application behavior. Since the constructor only runs during reflection, any side effect (logging, registration, counter increment) depends on whether and when something reads the attribute. This makes behavior unpredictable and hard to test.
Summary
- A custom attribute's constructor runs only when reflection reads it (for example, via
GetCustomAttributes()), not when the attribute is applied to a code element. - Each call to
GetCustomAttributescreates a new instance of the attribute, invoking the constructor each time. - Use
Attribute.IsDefined()to check for an attribute's presence without instantiating it. - Keep attribute constructors lightweight -- avoid side effects and expensive operations.
- Always declare
[AttributeUsage]to constrain where and how many times your attribute can be applied.

